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
|