1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
|
.. _tutorial:
Tutorial
========
In order to help you with using *pygls* in VSCode, we have created the `vscode-playground`_ extension.
.. note::
This extension is meant to provide an environment in which you can easily experiment with a *pygls* powered language server.
It is not necessary in order to use *pygls* with other text editors.
If you decide you want to publish your language server on the VSCode marketplace this
`template extension <https://github.com/microsoft/vscode-python-tools-extension-template>`__
from Microsoft a useful starting point.
Prerequisites
-------------
In order to setup and run the example VSCode extension, you need following software
installed:
* `Visual Studio Code <https://code.visualstudio.com/>`_ editor
* `Python 3.8+ <https://www.python.org/downloads/>`_
* `vscode-python <https://marketplace.visualstudio.com/items?itemName=ms-python.python>`_ extension
* A clone of the `pygls <https://github.com/openlawlibrary/pygls>`_ repository
.. note::
If you have created virtual environment, make sure that you have *pygls* installed
and `selected appropriate python interpreter <https://code.visualstudio.com/docs/python/environments>`_
for the *pygls* project.
Running the Example
-------------------
For a step-by-step guide on how to setup and run the example follow `README`_.
Hacking the Extension
---------------------
When you have successfully setup and run the extension, open `server.py`_ and
go through the code.
We have implemented following capabilities:
- ``textDocument/completion`` feature
- ``countDownBlocking`` command
- ``countDownNonBlocking`` command
- ``textDocument/didChange`` feature
- ``textDocument/didClose`` feature
- ``textDocument/didOpen`` feature
- ``showConfigurationAsync`` command
- ``showConfigurationCallback`` command
- ``showConfigurationThread`` command
When running the extension in *debug* mode, you can set breakpoints to see
when each of above mentioned actions gets triggered.
Visual Studio Code supports *Language Server Protocol*, which means, that every
action on the client-side, will result in sending request or notification to
the server via JSON RPC.
Debug Code Completions
~~~~~~~~~~~~~~~~~~~~~~
Set a breakpoint inside ``completion`` function and go back to opened *json*
file in your editor. Now press ``ctrl + space`` (``control + space`` on mac) to
show completion list and you will hit the breakpoint. When you continue
debugging, the completion list pop-up won't show up because it was closing when
the editor lost focus.
Similarly, you can debug any feature or command.
Keep the breakpoint and continue to the next section.
Blocking Command Test
~~~~~~~~~~~~~~~~~~~~~
In order to demonstrate you that blocking the language server will reject other
requests, we have registered a custom command which counts down 10 seconds and
sends notification messages to the client.
1. Press **F1**, find and run ``Count down 10 seconds [Blocking]`` command.
2. Try to show *code completions* while counter is still ticking.
Language server is **blocked**, because ``time.sleep`` is a
**blocking** operation. This is why you didn't hit the breakpoint this time.
.. hint::
To make this command **non blocking**, add ``@json_server.thread()``
decorator, like in code below:
.. code-block:: python
@json_server.thread()
@json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_BLOCKING)
def count_down_10_seconds_blocking(ls, *args):
# Omitted
*pygls* uses a **thread pool** to execute functions that are marked with
a ``thread`` decorator.
Non-Blocking Command Test
~~~~~~~~~~~~~~~~~~~~~~~~~
Python 3.4 introduced *asyncio* module which allows us to use asynchronous
functions (aka *coroutines*) and do `cooperative multitasking`_. Using the
`await` keyword inside your coroutine will give back control to the
scheduler and won't block the main thread.
1. Press **F1** and run the ``Count down 10 seconds [Non Blocking]`` command.
2. Try to show *code completions* while counter is still ticking.
Bingo! We hit the breakpoint! What just happened?
The language server was **not blocked** because we used ``asyncio.sleep`` this
time. The language server was executing *just* in the *main* thread.
Text Document Operations
~~~~~~~~~~~~~~~~~~~~~~~~
Opening and closing a JSON file will display appropriate notification message
in the bottom right corner of the window and the file content will be
validated. Validation will be performed on content changes, as well.
Show Configuration Data
~~~~~~~~~~~~~~~~~~~~~~~
There are *three* ways for getting configuration section from the client
settings.
.. note::
*pygls*' built-in coroutines are suffixed with *async* word, which means that
you have to use the *await* keyword in order to get the result (instead of
*asyncio.Future* object).
- **Get the configuration inside a coroutine**
.. code-block:: python
config = await ls.get_configuration_async(ConfigurationParams([
ConfigurationItem('', JsonLanguageServer.CONFIGURATION_SECTION)
]))
- **Get the configuration inside a normal function**
We already saw that we *don't* want to block the main thread. Sending the
configuration request to the client will result with the response from it, but
we don't know when. You have to pass *callback* function which will be
triggered once response from the client is received.
.. code-block:: python
def _config_callback(config):
try:
example_config = config[0].exampleConfiguration
ls.show_message(
f'jsonServer.exampleConfiguration value: {example_config}'
)
except Exception as e:
ls.show_message_log(f'Error ocurred: {e}')
ls.get_configuration(ConfigurationParams([
ConfigurationItem('', JsonLanguageServer.CONFIGURATION_SECTION)
]), _config_callback)
As you can see, the above code is hard to read.
- **Get the configuration inside a threaded function**
Blocking operations such as ``future.result(1)`` should not be used inside
normal functions, but to increase the code readability, you can add the
*thread* decorator to your function to use *pygls*' *thread pool*.
.. code-block:: python
@json_server.thread()
@json_server.command(JsonLanguageServer.CMD_SHOW_CONFIGURATION_THREAD)
def show_configuration_thread(ls: JsonLanguageServer, *args):
"""Gets exampleConfiguration from the client settings using a thread pool."""
try:
config = ls.get_configuration(ConfigurationParams([
ConfigurationItem('', JsonLanguageServer.CONFIGURATION_SECTION)
])).result(2)
# ...
This way you won't block the main thread. *pygls* will start a new thread when
executing the function.
Modify the Example
~~~~~~~~~~~~~~~~~~
We encourage you to continue to :ref:`user guide <user-guide>` and
modify this example.
.. _vscode-playground: https://github.com/openlawlibrary/pygls/blob/main/examples/vscode-playground
.. _README: https://github.com/openlawlibrary/pygls/blob/main/examples/vscode-playground/README.md
.. _server.py: https://github.com/openlawlibrary/pygls/blob/main/examples/servers/json_server.py
.. _cooperative multitasking: https://en.wikipedia.org/wiki/Cooperative_multitasking
|