New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Disable an input handler dynamically (in case there is only one item to select from) #3347
Comments
An additional related functionality I recently thought of would be showing an accepted input handler for a pre-selected value. For example, I have a command that accepts 3 arguments, two operands and the operator's string representation. (Input handlers skipped for brevity.) import sublime_plugin
import operator
OPERATORS = {
'+': operator.add,
'-': operator.sub,
'*': operator.mul,
'/': operator.truediv,
}
class BinaryArithmeticCommand(sublime_plugin.TextCommand):
def run(self, edit, operand1, operator, operand2):
result = OPERATORS[operator](float(operand1), float(operand2))
for region in self.view.sel():
self.view.replace(edit, region, str(result)) If I called, |
This behaviour can be achieved without any workarounds:
|
I finally found some time again for this problem. Turns out, the fundamental problem was that I was trying to execute the command directly as opposed to calling it through I also realized I don't mention this in the guide page I made for docs.sublimetext.io, so I'll add this there as well. |
Full plugin code for future references: import sublime_plugin
num_items = 1
class TestCommand(sublime_plugin.WindowCommand):
def input(self, args):
global num_items
if 'value' not in args and num_items > 1:
return SimpleListInputHandler('value', list(range(num_items)))
def run(self, **args):
global num_items
value = args.get('value')
if value is None:
if num_items == 1:
value = 0
else:
print("no arg provided")
return
print("Performing task on", value)
num_items += 1
class SimpleListInputHandler(sublime_plugin.ListInputHandler):
def __init__(self, param_name, items):
self.param_name = param_name
self.items = items
def name(self):
return self.param_name
def placeholder(self):
return self.param_name
def list_items(self):
return list(map(str, self.items)) How to call it directly: window.run_command("show_overlay", {"overlay": "command_palette", "command": "test", "args": {"value": 1}}) |
Problem description
The base situation is that I want to show an input handler based on a dynamic condition, that being a list of dynamic length. The following three conditions are possible:
Implementing the third case is trivial, so I'll skip that here.
The first case can be achieved by implementing the required logic in an
is_enabled
method that would prevent from the command being callable at all. To prevent redundant computations, the results of the computation can be stored in an instance attribute. However, this function is executed as soon as the command is to be made available in a menu or the command palette, which means it's not possible to give the user an explanation for why the command would not be able to run.The second case has no direct solution, currently. Input handlers are only called if calling the command's
run
method fails with aTypeError
indicating that a parameter was missing, so the argument cannot have a default value. However, it is not possible withininput()
to modfiy the arguments or specify a value to be used directly without causing the input handler to be displayed.Workaround
A working workaround is to raise a crafted
TypeError
from your command'srun
method after you made the computation and determined dynamically that you need input from the user. An example is provided below.Indicentally, this workaround also allows the comman to just return early in case a precondition is not met while showing a message to the user, unlike in
is_enabled
. Also,is_enabled
has an open issue with not being called with the given args in all situations (#3249).Preferred solution
Allow adding arguments to the command call within
input()
by returning a dict of keys to be added (and modified) instead of only InputHandler instances.Example code
The workaround is volatile. Not cleaning up the cached
handler
attribute means thatrun
is not run when the command is invoked from the command line, because that checksinput
before runningrun
, where we would reset the attribute otherwise.Cleaning the attribute before returning from
input()
causes an infinite recursion and crashes the plugin host, becausesublime_api.can_accept_input(self.name(), args)
makes a call and checksinput()
before opening the command palette for it, which callsinput()
again.A command palette entry must exist. (I used
{ "caption": "Test Command", "command": "test" },
.)As an aside, when the list returned in
list_items()
consists of integers and not string, the error in the console sayswhich is not at all what was happening, as the sequence definitely had 2 values. In fact, the code works fine if there is only 1 value. It's just that with 0 values that weird things happen (and I get the previously mentioned infinite recursion.)
Edit: looks like the 0 case is described in #3105.
The text was updated successfully, but these errors were encountered: