Anaconda GUI Commands
Anaconda GUI commands lives into the commands
package of the anaconda plugin root directory. Each one is an isolated piece of code that normally depends in some way of the anaconda_lib
package where common functionallity is implemented.
note: this is true with the exception of the Vagrant related commands that are all contained into the commands/vagrant.py
file and the tests runner related commands that are contained into the commands/anaconda_test_runner.py
files because it made sense to put commands that are feature related into the same file.
Anatomy of an anaconda GUI command
Usually, anaconda GUI commands surprises new developers because their simplicity, that is because anaconda commands are really dumb and they know nothing about how to execute any of the actions related to them.
The only that an anaconda GUI command has to know how to do is to capture where the cursor is located into a buffer, the buffer itself and the name of the file being edited and pass that information to the anacoda's callback system that will handle the command trigger and delegate all the heavy process to the JsonServer while register a callback to process the response when it is ready to be delivered.
Every anaconda command has to know how to process the results from the callback system and how to present this information to the end user.
The ususal design of an anaconda GUI command is as follows:
Let me walk you into the code block by block
Here we import the Worker
, Calback
and some helpers being the prepare_send_data
the most important one. Worker
class instances knows how to communicate themselves with the JsonServer and execute commands on it as Workers
know how to speak Json with the anconda_server
.
We use a Callback
instance to register the command callback that has to be fired when the JsonServer returns back some data and the command is success (AnacondaWhatever.prepare_data
in this case).
The prepeare_send_data
helper is commonly used in all the commands and listeners to maintain the code clean and don't repeat code all around (remember, we have to maintain the code as DRY as possible always that it makes sense as it improves it's maintainability).
The is_python
helper is used to activate this command and make it available under Contextual Menu
and Command Palette
(or even to don't execute it if is called from the Sublime Text 3 console) only in Python files.
note: is a requirement that all the anaconda commands include a method to deactivate or activate them depending on the context, that is mandatory.
In this case, we need to inherit our class from the sublime_plugin.TextCommand
class as we need to write into a panel in the GUI (for detailed information about how to write code for Sublime Text 3, refer to it's API reference documentation).
We define a class level property called data
that will be used later.
We use the data
property to know when we have to fire the command execution into the JsonServer or process it results when they become available reusing the Sublime Text 3 plugin call functionallity itself.
We always enclose the code into a try except
block just to don't fail miserably in case that something goes wrong. As I already anoted before, we capture the current location of the cursor in the current buffer and store it in the loc
variable.
The jserver_command
is the command that we want to execute in the JsonServer context, this command is suposed to be handled by the jedi
handler (that's not true as this command does not exists but this is useful for our ilustrative purposes) so this is translated to
Hey JsonServer, use the jedi handler to execute the whatever command
Then we prepare the data that we are going to send to the JsonSever using the prepare_send_data
command that just prepare a common JSON structure for us represented as a Python dictionary.
This is an important piece of code here. We instantiate a new instance of the Worker
class and call it's execute method with two argumnets.
An instance of the Callback class with the
on_success
callback set asself.prepare_data
The data dictionary that was prepared in the previous block unpacked
If there is some exceptio we just logging the error in the Sublime Text 3 console. The else correspond to the first conditional check, this is hit when we have a result comming back from the JsonServer and our function is called again by the callback that we passed to the Callback
instance.
This piece of code is pretty straightforward, this method is called when there is a result available in the Callback
mechanism with a single parameters that is whatever response that came from the JsonServer after process our request.
We check that the request was sucessful and then set the value of the AnacondaWhatever.data
property with the data that the JsonServer returned back. If there is no data at all, we call the method self._show_status()
that just display an error message to the user in the status bar.
Otherwise we call the AnacondaWhatever
method again using the internal Sublime Text 3 API call, as the AnacondaWhatever.data
property is set, it will hit the else
conditional at the begining of the run
method and then the print_data
dummy method will be called.
note: the specific data structure that is returned back from the JsonServer will be explained in subsequent sections
When we write the data that came back from the JsonServer into the panel we set the AnacondaWhatever.data
as None
so subsequent calls to the AnacondaWhatever
command will trigger the call to the JsonServer, otherwise we would get the same panel again and again. This is how we reuse the built-in Sublime Text 3 plugin call mechanism in a way that allow us to be 100% asynchronous for real.
The is_enabled
and _show_status
methods doesn't deserve an explanation as they are common methods with no special code on them.
How are the commands injected into the plugin?
The anaconda main entry point (anaconda.py
file in the root directory) just includes a line that import all the symbols in the commands
package. The exported symbols in the package are controlled by the __init__.py
script into the commands
directory so when you add a new command, you have to make sure that it is being part of the __all__
list.
Anything that is not part of the __all__
list of exported objects will not be available in the plugin.
Last updated