The process of using various input interfaces of Sublime Text tends to be a callback hell, so I wrote it neatly using a generator! It is a story. I'm using the plug-in I'm writing now.
The Sublime Text 3 API can do what you want through a text editor. It is possible to accept various inputs such as displaying a quick panel with customized menu items and displaying a text input panel, but as you can see, the editor's while accepting input from various users Other operations aren't blocked, right? This is because, of course, the functions that operate these input reception interfaces are executed in a new thread, but if you want to interfere with the processing of the main thread from there, after the callback function passed in advance is input. Call with.
For example, in the quick panel display, it looks like this:
from sublime_plugin import WindowCommand
class ShowMenu(WindowCommand):
def run(self):
menu = ["A", "B", "C"]
def cb(index):
print(menu[index])
self.window.show_quick_panel(menu, cb)
Can display a menu containing the elements "A", "B", "C", and the cb ()
function is passed the index of the selected menu. Well, it's not a big deal because the contents of the callback are small if it is about this, but it becomes troublesome again if the number of rows of the callback and the number of variables passed to the callback increase.
Even more troublesome is when you have to call commands that wait for user input in succession, and when you try to call the next user input wait command with an appropriate callback in the callback ... For example:
class Auth(WindowCommand):
def run(self):
self.window.show_input_panel("username", "", cb1)
#When the input panel is displayed, the line on this side will continue without waiting for input.
#Write the process using input in the callback
def _cb1(username):
# show_input_panel()If it is as it is, the input character string will be displayed and it is really that, but for the time being I do not care about it
self.window.show_input_panel(
"password", "", functools.partial(_cb2, username))
def _cb2(username, password):
_auth(username, password)
def _auth(username, password):
#Authentication process using username and password
Well, I can write it, but it's not easy to read. It is tedious and unintuitive to have to separate as a function just to execute it as a callback, even though the unit to be cut out as a function is not appropriate. If there are a lot of parameters to be passed, it will be troublesome and maintainability will be reduced.
Can't you write more elegantly? ……Can write. With Python.
In principle, it would be nice if the execution of the function could be paused when it came to wait for input from the user. ――Yes, this is where the yield
statement comes into play. By calling the yield
statement, you can save the running state until thenext ()
function or thesend ()
function is called for the generator. If there is input from the user, it can be used in the expression in the generator by using the send ()
function. If not, just call next ()
. So, first of all, I will prepare a decorator like this.
def chain_callbacks(
f: Callable[..., Generator[Callable[Callable[...]], Any, Any]
) -> Callable[..., None]:
@wraps(f)
def wrapper(*args, **kwargs):
chain = f(*args, **kwargs)
try:
next_f = next(chain)
except StopIteration:
return
def cb(*args, **kwargs):
nonlocal next_f
try:
if len(args) + len(kwargs) != 0:
next_f = chain.send(*args, **kwargs)
else:
next_f = next(chain)
next_f(cb)
except StopIteration:
return
next_f(cb)
return wrapper
It's a little tricky, but you can use it to "inline" callbacks.
In the yield
statement, pass" a function that takes one callback function that you want the yield
statement and subsequent statements to be executed when called". If any value is passed to that callback function, it can be received as the return value of yield
.
from functools import partial
class Auth(WindowCommand):
@chain_callback
def run(self):
# `functools.partial()`With`on_done`Take only one argument
#In the form of a function, yield
username = yield partial(
self.window.show_input_panel, "username", "",
on_change=None, on_cancel=None)
password = yield partial(
self.window.show_input_panel, "password", "",
on_change=None, on_cancel=None)
#From here, use username and password to authenticate
This side is refreshing!
This kind of generator usage is also used by web frameworks like Twisted
and Tornado
.
Recommended Posts