diff options
Diffstat (limited to '')
-rw-r--r-- | docs/Makefile | 19 | ||||
-rw-r--r-- | docs/assets/hello-world-completion.png | bin | 0 -> 3076 bytes | |||
-rw-r--r-- | docs/make.bat | 35 | ||||
-rw-r--r-- | docs/requirements.txt | 220 | ||||
-rw-r--r-- | docs/source/conf.py | 288 | ||||
-rw-r--r-- | docs/source/index.rst | 51 | ||||
-rw-r--r-- | docs/source/pages/getting_started.rst | 94 | ||||
-rw-r--r-- | docs/source/pages/migrating-to-v1.rst | 241 | ||||
-rw-r--r-- | docs/source/pages/reference.rst | 8 | ||||
-rw-r--r-- | docs/source/pages/reference/clients.rst | 8 | ||||
-rw-r--r-- | docs/source/pages/reference/protocol.rst | 11 | ||||
-rw-r--r-- | docs/source/pages/reference/servers.rst | 11 | ||||
-rw-r--r-- | docs/source/pages/reference/types.rst | 10 | ||||
-rw-r--r-- | docs/source/pages/reference/workspace.rst | 9 | ||||
-rw-r--r-- | docs/source/pages/testing.rst | 24 | ||||
-rw-r--r-- | docs/source/pages/tutorial.rst | 207 | ||||
-rw-r--r-- | docs/source/pages/user-guide.rst | 536 |
17 files changed, 1772 insertions, 0 deletions
diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..69fe55e --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,19 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file diff --git a/docs/assets/hello-world-completion.png b/docs/assets/hello-world-completion.png Binary files differnew file mode 100644 index 0000000..669b263 --- /dev/null +++ b/docs/assets/hello-world-completion.png diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..4d9eb83 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..2cc6cd4 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,220 @@ +alabaster==0.7.13 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3 \ + --hash=sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2 +attrs==23.1.0 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \ + --hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015 +babel==2.12.1 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610 \ + --hash=sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455 +cattrs==23.1.2 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:b2bb14311ac17bed0d58785e5a60f022e5431aca3932e3fc5cc8ed8639de50a4 \ + --hash=sha256:db1c821b8c537382b2c7c66678c3790091ca0275ac486c76f3c8f3920e83c657 +certifi==2023.7.22 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \ + --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 +charset-normalizer==3.2.0 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96 \ + --hash=sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c \ + --hash=sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710 \ + --hash=sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706 \ + --hash=sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020 \ + --hash=sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252 \ + --hash=sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad \ + --hash=sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329 \ + --hash=sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a \ + --hash=sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f \ + --hash=sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6 \ + --hash=sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4 \ + --hash=sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a \ + --hash=sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46 \ + --hash=sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2 \ + --hash=sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23 \ + --hash=sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace \ + --hash=sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd \ + --hash=sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982 \ + --hash=sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10 \ + --hash=sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2 \ + --hash=sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea \ + --hash=sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09 \ + --hash=sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5 \ + --hash=sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149 \ + --hash=sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489 \ + --hash=sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9 \ + --hash=sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80 \ + --hash=sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592 \ + --hash=sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3 \ + --hash=sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6 \ + --hash=sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed \ + --hash=sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c \ + --hash=sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200 \ + --hash=sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a \ + --hash=sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e \ + --hash=sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d \ + --hash=sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6 \ + --hash=sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623 \ + --hash=sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669 \ + --hash=sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3 \ + --hash=sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa \ + --hash=sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9 \ + --hash=sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2 \ + --hash=sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f \ + --hash=sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1 \ + --hash=sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4 \ + --hash=sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a \ + --hash=sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8 \ + --hash=sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3 \ + --hash=sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029 \ + --hash=sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f \ + --hash=sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959 \ + --hash=sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22 \ + --hash=sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7 \ + --hash=sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952 \ + --hash=sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346 \ + --hash=sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e \ + --hash=sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d \ + --hash=sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299 \ + --hash=sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd \ + --hash=sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a \ + --hash=sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3 \ + --hash=sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037 \ + --hash=sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94 \ + --hash=sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c \ + --hash=sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858 \ + --hash=sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a \ + --hash=sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449 \ + --hash=sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c \ + --hash=sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918 \ + --hash=sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1 \ + --hash=sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c \ + --hash=sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac \ + --hash=sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa +colorama==0.4.6 ; python_full_version >= "3.7.9" and python_version < "4" and sys_platform == "win32" \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 +docutils==0.18.1 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c \ + --hash=sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06 +exceptiongroup==1.1.3 ; python_full_version >= "3.7.9" and python_version < "3.11" \ + --hash=sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9 \ + --hash=sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3 +idna==3.4 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ + --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 +imagesize==1.4.1 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b \ + --hash=sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a +importlib-metadata==6.7.0 ; python_full_version >= "3.7.9" and python_version < "3.10" \ + --hash=sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4 \ + --hash=sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5 +jinja2==3.1.2 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ + --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 +lsprotocol==2023.0.0a3 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:2896c5a30c34846e3d5687e35715961f49bf7b92a36e4fb2b707ff65f19087f7 \ + --hash=sha256:d704e4e00419f74bece9795de4b34d02aa555fc0131fec49f59ac9eb46816e51 +markupsafe==2.1.3 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \ + --hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \ + --hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \ + --hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \ + --hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \ + --hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \ + --hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \ + --hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \ + --hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \ + --hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \ + --hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \ + --hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \ + --hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \ + --hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \ + --hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \ + --hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \ + --hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \ + --hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \ + --hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \ + --hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \ + --hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \ + --hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \ + --hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \ + --hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \ + --hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \ + --hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \ + --hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \ + --hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \ + --hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \ + --hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \ + --hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \ + --hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \ + --hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \ + --hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \ + --hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \ + --hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \ + --hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \ + --hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \ + --hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \ + --hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \ + --hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \ + --hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \ + --hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \ + --hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \ + --hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \ + --hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \ + --hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \ + --hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \ + --hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \ + --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2 +packaging==23.1 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \ + --hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f +pygments==2.16.1 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692 \ + --hash=sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29 +pytz==2023.3 ; python_full_version >= "3.7.9" and python_version < "3.9" \ + --hash=sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588 \ + --hash=sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb +requests==2.31.0 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ + --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 +snowballstemmer==2.2.0 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 \ + --hash=sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a +sphinx-rtd-theme==1.3.0 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0 \ + --hash=sha256:590b030c7abb9cf038ec053b95e5380b5c70d61591eb0b552063fbe7c41f0931 +sphinx==5.3.0 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d \ + --hash=sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5 +sphinxcontrib-applehelp==1.0.2 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a \ + --hash=sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58 +sphinxcontrib-devhelp==1.0.2 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e \ + --hash=sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4 +sphinxcontrib-htmlhelp==2.0.0 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07 \ + --hash=sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2 +sphinxcontrib-jquery==4.1 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a \ + --hash=sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae +sphinxcontrib-jsmath==1.0.1 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178 \ + --hash=sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8 +sphinxcontrib-qthelp==1.0.3 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72 \ + --hash=sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6 +sphinxcontrib-serializinghtml==1.1.5 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd \ + --hash=sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952 +typeguard==3.0.2 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:bbe993854385284ab42fd5bd3bee6f6556577ce8b50696d6cb956d704f286c8e \ + --hash=sha256:fee5297fdb28f8e9efcb8142b5ee219e02375509cd77ea9d270b5af826358d5a +typing-extensions==4.7.1 ; python_full_version >= "3.7.9" and python_version < "3.11" \ + --hash=sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36 \ + --hash=sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2 +urllib3==2.0.4 ; python_full_version >= "3.7.9" and python_version < "4" \ + --hash=sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11 \ + --hash=sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4 +zipp==3.15.0 ; python_full_version >= "3.7.9" and python_version < "3.10" \ + --hash=sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b \ + --hash=sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556 diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..b3b3bfc --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,288 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) +import importlib.metadata +import re +from docutils import nodes + + +# -- Project information ----------------------------------------------------- + +project = "pygls" +copyright = "Open Law Library" +author = "Open Law Library" + +# The short X.Y version +version = importlib.metadata.version("pygls") +# The full version, including alpha/beta/rc tags +release = version + +title = "pygls Documentation" +description = "a pythonic generic language server" + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.napoleon", +] + +autodoc_member_order = "groupwise" +autodoc_typehints = "description" +autodoc_typehints_description_target = "all" + +intersphinx_mapping = { + "python": ("https://docs.python.org/3/", None), +} + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = ".rst" + +# The master toctree document. +master_doc = "index" + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = "en" + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = None + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinx_rtd_theme" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +# html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = "pyglsdoc" + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, "pygls.tex", title, author, "manual"), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(master_doc, "pygls", description, [author], 1)] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, "pygls", title, author, "pygls", description, "Miscellaneous"), +] + + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ["search.html"] + + +def lsp_role(name, rawtext, text, lineno, inliner, options={}, content=[]): + """Link to sections within the lsp specification.""" + + anchor = text.replace("$/", "").replace("/", "_") + ref = f"https://microsoft.github.io/language-server-protocol/specification.html#{anchor}" + + node = nodes.reference(rawtext, text, refuri=ref, **options) + return [node], [] + + +CODE_FENCE_PATTERN = re.compile(r"```(\w+)?") +LINK_PATTERN = re.compile(r"\{@link ([^}]+)\}") +LITERAL_PATTERN = re.compile(r"(?<![`:])`([^`]+)`(?!_)") +MD_LINK_PATTERN = re.compile(r"\[`?([^\]]+?)`?\]\(([^)]+)\)") +SINCE_PATTERN = re.compile(r"@since ([\d\.]+)") + + +def process_docstring(app, what, name, obj, options, lines): + """Fixup LSP docstrings so that they work with reStructuredText syntax + + - Replaces ``@since <version>`` with ``**LSP v<version>**`` + + - Replaces ``{@link <item>}`` with ``:class:`~lsprotocol.types.<item>` `` + + - Replaces markdown hyperlink with reStructuredText equivalent + + - Replaces inline markdown code (single "`") with reStructuredText inline code + (double "`") + + - Inserts the required newline before a bulleted list + + - Replaces code fences with code blocks + + - Fixes indentation + """ + + line_breaks = [] + code_fences = [] + + for i, line in enumerate(lines): + if line.startswith("- "): + line_breaks.append(i) + + # Does the line need dedenting? + if line.startswith(" " * 4) and not lines[i - 1].startswith(" "): + # Be sure to modify the original list *and* the line the rest of the + # loop will use. + line = lines[i][4:] + lines[i] = line + + if (match := SINCE_PATTERN.search(line)) is not None: + start, end = match.span() + lines[i] = "".join([line[:start], f"**LSP v{match.group(1)}**", line[end:]]) + + if (match := LINK_PATTERN.search(line)) is not None: + start, end = match.span() + item = match.group(1) + + lines[i] = "".join( + [line[:start], f":class:`~lsprotocol.types.{item}`", line[end:]] + ) + + if (match := MD_LINK_PATTERN.search(line)) is not None: + start, end = match.span() + text = match.group(1) + target = match.group(2) + + line = "".join([line[:start], f"`{text} <{target}>`__", line[end:]]) + lines[i] = line + + if (match := LITERAL_PATTERN.search(line)) is not None: + start, end = match.span() + lines[i] = "".join([line[:start], f"`{match.group(0)}` ", line[end:]]) + + if (match := CODE_FENCE_PATTERN.match(line)) is not None: + open_ = len(code_fences) % 2 == 0 + lang = match.group(1) or "" + + if open_: + code_fences.append((i, lang)) + line_breaks.extend([i, i + 1]) + else: + code_fences.append(i) + + # Rewrite fenced code blocks + open_ = -1 + for fence in code_fences: + if isinstance(fence, tuple): + open_ = fence[0] + 1 + lines[fence[0]] = f".. code-block:: {fence[1]}" + else: + # Indent content + for j in range(open_, fence): + lines[j] = f" {lines[j]}" + + lines[fence] = "" + + # Insert extra line breaks + for offset, line in enumerate(line_breaks): + lines.insert(line + offset, "") + + +def setup(app): + app.add_role("lsp", lsp_role) + app.connect("autodoc-process-docstring", process_docstring) diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..3fc264c --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,51 @@ +.. pygls documentation master file, created by + sphinx-quickstart on Sun Nov 25 16:16:27 2018. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +*pygls* +======= + +`pygls`_ (pronounced like “pie glass”) is a generic implementation of +the `Language Server Protocol`_ written in the Python programming language. It +allows you to write your own `language server`_ in just a few lines of code. + +Features +-------- + +- cross-platform support +- TCP/IP and STDIO communication +- runs in asyncio event loop +- register LSP features and custom commands as: + + - asynchronous functions (coroutines) + - synchronous functions + - functions that will be executed in separate thread + +- thread management +- in-memory workspace with *full* and *incremental* document updates +- type-checking +- good test coverage + +Python Versions +--------------- + +*pygls* works with Python 3.8+. + +User Guide +---------- + +.. toctree:: + :maxdepth: 2 + + pages/getting_started + pages/tutorial + pages/user-guide + pages/testing + pages/migrating-to-v1 + pages/reference + + +.. _Language Server Protocol: https://microsoft.github.io/language-server-protocol/specification +.. _Language server: https://langserver.org/ +.. _pygls: https://github.com/openlawlibrary/pygls diff --git a/docs/source/pages/getting_started.rst b/docs/source/pages/getting_started.rst new file mode 100644 index 0000000..e73d20f --- /dev/null +++ b/docs/source/pages/getting_started.rst @@ -0,0 +1,94 @@ +Getting Started +=============== + +This document explains how to install *pygls* and get started writing language +servers that are based on it. + +.. note:: + + Before going any further, if you are not familiar with *language servers* + and *Language Server Protocol*, we recommend reading following articles: + + - `Language Server Protocol Overview <https://microsoft.github.io/language-server-protocol/overview>`_ + - `Language Server Protocol Specification <https://microsoft.github.io/language-server-protocol/specification>`_ + - `Language Server Protocol SDKs <https://microsoft.github.io/language-server-protocol/implementors/sdks/>`_ + + +Installation +------------ + +To get the latest release from *PyPI*, simply run: + +.. code:: console + + pip install pygls + +Alternatively, *pygls* source code can be downloaded from our `GitHub`_ page and installed with following command: + +.. code:: console + + pip install git+https://github.com/openlawlibrary/pygls + +Quick Start +----------- + +Spin the Server Up +~~~~~~~~~~~~~~~~~~ + +*pygls* is a language server that can be started without writing any additional +code: + +.. code:: python + + from pygls.server import LanguageServer + + server = LanguageServer('example-server', 'v0.1') + + server.start_tcp('127.0.0.1', 8080) + +After running the code above, server will start listening for incoming +``Json RPC`` requests on ``http://127.0.0.1:8080``. + +Register Features and Commands +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +*pygls* comes with an API for registering additional features like +``code completion``, ``find all references``, ``go to definition``, etc. + +.. code:: python + + @server.feature(TEXT_DOCUMENT_COMPLETION, CompletionOptions(trigger_characters=[','])) + def completions(params: CompletionParams): + """Returns completion items.""" + return CompletionList( + is_incomplete=False, + items=[ + CompletionItem(label='Item1'), + CompletionItem(label='Item2'), + CompletionItem(label='Item3'), + ] + ) + +… as well as custom commands: + +.. code:: python + + @server.command('myVerySpecialCommandName') + def cmd_return_hello_world(ls, *args): + return 'Hello World!' + +See the :mod:`lsprotocol.types` module for the complete and canonical list of available features. + +Tutorial +-------- + +We recommend completing the :ref:`tutorial <tutorial>`, especially if you +haven't worked with language servers before. + +User Guide +---------- + +To reveal the full potential of *pygls* (``thread management``, ``coroutines``, +``multi-root workspace``, ``TCP/STDIO communication``, etc.) keep reading. + +.. _GitHub: https://github.com/openlawlibrary/pygls diff --git a/docs/source/pages/migrating-to-v1.rst b/docs/source/pages/migrating-to-v1.rst new file mode 100644 index 0000000..527a810 --- /dev/null +++ b/docs/source/pages/migrating-to-v1.rst @@ -0,0 +1,241 @@ +Migrating to v1.0 +================= + +The most notable change of the ``v1.0`` release of ``pygls`` is the removal of its hand written LSP type and method definitions in favour of relying on the types provided by the `lsprotocol`_ library which are automatically generated from the LSP specification. +As as side effect this has also meant the removal of `pydantic`_ as a dependency, since ``lsprotocol`` uses `attrs`_ and `cattrs`_ for serialisation and validation. + +This guide outlines how to adapt an existing server to the breaking changes introduced in this release. + +Known Migrations +---------------- +You may find insight and inspiration from these projects that have already successfully migrated to v1: + +* `jedi-language-server`_ +* `vscode-ruff`_ +* `esbonio`_ +* `yara-language-server`_ + +Updating Imports +---------------- + +The ``pygls.lsp.methods`` and ``pygls.lsp.types`` modules no longer exist. +Instead, all types and method names should now be imported from the ``lsprotocol.types`` module. + +Additionally, the following types and constants have been renamed. + +================================================================== ============== +pygls lsprotocol +================================================================== ============== +``CODE_ACTION`` ``TEXT_DOCUMENT_CODE_ACTION`` +``CODE_LENS`` ``TEXT_DOCUMENT_CODE_LENS`` +``COLOR_PRESENTATION`` ``TEXT_DOCUMENT_COLOR_PRESENTATION`` +``COMPLETION`` ``TEXT_DOCUMENT_COMPLETION`` +``DECLARATION`` ``TEXT_DOCUMENT_DECLARATION`` +``DEFINITION`` ``TEXT_DOCUMENT_DEFINITION`` +``DOCUMENT_COLOR`` ``TEXT_DOCUMENT_DOCUMENT_COLOR`` +``DOCUMENT_HIGHLIGHT`` ``TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT`` +``DOCUMENT_LINK`` ``TEXT_DOCUMENT_DOCUMENT_LINK`` +``DOCUMENT_SYMBOL`` ``TEXT_DOCUMENT_DOCUMENT_SYMBOL`` +``FOLDING_RANGE`` ``TEXT_DOCUMENT_FOLDING_RANGE`` +``FORMATTING`` ``TEXT_DOCUMENT_FORMATTING`` +``HOVER`` ``TEXT_DOCUMENT_HOVER`` +``IMPLEMENTATION`` ``TEXT_DOCUMENT_IMPLEMENTATION`` +``LOG_TRACE_NOTIFICATION`` ``LOG_TRACE`` +``ON_TYPE_FORMATTING`` ``TEXT_DOCUMENT_ON_TYPE_FORMATTING`` +``PREPARE_RENAME`` ``TEXT_DOCUMENT_PREPARE_RENAME`` +``PROGRESS_NOTIFICATION`` ``PROGRESS`` +``RANGE_FORMATTING`` ``TEXT_DOCUMENT_RANGE_FORMATTING`` +``REFERENCES`` ``TEXT_DOCUMENT_REFERENCES`` +``RENAME`` ``TEXT_DOCUMENT_RENAME`` +``SELECTION_RANGE`` ``TEXT_DOCUMENT_SELECTION_RANGE`` +``SET_TRACE_NOTIFICATION`` ``SET_TRACE`` +``SIGNATURE_HELP`` ``TEXT_DOCUMENT_SIGNATURE_HELP`` +``TEXT_DOCUMENT_CALL_HIERARCHY_INCOMING_CALLS`` ``CALL_HIERARCHY_INCOMING_CALLS`` +``TEXT_DOCUMENT_CALL_HIERARCHY_OUTGOING_CALLS`` ``CALL_HIERARCHY_OUTGOING_CALLS`` +``TEXT_DOCUMENT_CALL_HIERARCHY_PREPARE`` ``TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY`` +``TYPE_DEFINITION`` ``TEXT_DOCUMENT_TYPE_DEFINITION`` +``WORKSPACE_FOLDERS`` ``WORKSPACE_WORKSPACE_FOLDERS`` +``ApplyWorkspaceEditResponse`` ``ApplyWorkspaceEditResult`` +``ClientInfo`` ``InitializeParamsClientInfoType`` +``CodeActionDisabled`` ``CodeActionDisabledType`` +``CodeActionLiteralSupportActionKindClientCapabilities`` ``CodeActionClientCapabilitiesCodeActionLiteralSupportTypeCodeActionKindType`` +``CodeActionLiteralSupportClientCapabilities`` ``CodeActionClientCapabilitiesCodeActionLiteralSupportType`` +``CompletionItemClientCapabilities`` ``CompletionClientCapabilitiesCompletionItemType`` +``CompletionItemKindClientCapabilities`` ``CompletionClientCapabilitiesCompletionItemKindType`` +``CompletionTagSupportClientCapabilities`` ``CompletionClientCapabilitiesCompletionItemTypeTagSupportType`` +``DocumentSymbolCapabilitiesTagSupport`` ``DocumentSymbolClientCapabilitiesTagSupportType`` +``InsertTextModeSupportClientCapabilities`` ``CompletionClientCapabilitiesCompletionItemTypeInsertTextModeSupportType`` +``MarkedStringType`` ``MarkedString`` +``MarkedString`` ``MarkedString_Type1`` +``PrepareRename`` ``PrepareRenameResult_Type1`` +``PublishDiagnosticsTagSupportClientCapabilities`` ``PublishDiagnosticsClientCapabilitiesTagSupportType`` +``ResolveSupportClientCapabilities`` ``CodeActionClientCapabilitiesResolveSupportType`` +``SemanticTokensRequestsFull`` ``SemanticTokensRegistrationOptionsFullType1`` +``SemanticTokensRequests`` ``SemanticTokensClientCapabilitiesRequestsType`` +``ServerInfo`` ``InitializeResultServerInfoType`` +``ShowMessageRequestActionItem`` ``ShowMessageRequestClientCapabilitiesMessageActionItemType`` +``SignatureHelpInformationClientCapabilities`` ``SignatureHelpClientCapabilitiesSignatureInformationType`` +``SignatureHelpInformationParameterInformationClientCapabilities`` ``SignatureHelpClientCapabilitiesSignatureInformationTypeParameterInformationType`` +``TextDocumentContentChangeEvent`` ``TextDocumentContentChangeEvent_Type1`` +``TextDocumentContentChangeTextEvent`` ``TextDocumentContentChangeEvent_Type2`` +``TextDocumentSyncOptionsServerCapabilities`` ``TextDocumentSyncOptions`` +``Trace`` ``TraceValues`` +``URI`` ``str`` +``WorkspaceCapabilitiesSymbolKind`` ``WorkspaceSymbolClientCapabilitiesSymbolKindType`` +``WorkspaceCapabilitiesTagSupport`` ``WorkspaceSymbolClientCapabilitiesTagSupportType`` +``WorkspaceFileOperationsServerCapabilities`` ``FileOperationOptions`` +``WorkspaceServerCapabilities`` ``ServerCapabilitiesWorkspaceType`` +================================================================== ============== + +Custom Models +------------- + +One of the most obvious changes is the switch to `attrs`_ and `cattrs`_ for serialization and deserialisation. +This means that any custom models used by your language server will need to be converted to an ``attrs`` style class. + +.. code-block:: python + + # Before + from pydantic import BaseModel, Field + + class ExampleConfig(BaseModel): + build_dir: Optional[str] = Field(None, alias="buildDir") + + builder_name: str = Field("html", alias="builderName") + + conf_dir: Optional[str] = Field(None, alias="confDir") + +.. code-block:: python + + # After + import attrs + + @attrs.define + class ExampleConfig: + build_dir: Optional[str] = attrs.field(default=None) + + builder_name: str = attrs.field(default="html") + + conf_dir: Optional[str] = attrs.field(default=None) + + +Pygls provides a default `converter`_ that it will use when converting your models to/from JSON, which should be sufficient for most scenarios. + +.. code-block:: pycon + + >>> from pygls.protocol import default_converter + >>> converter = default_converter() + + >>> config = ExampleConfig(builder_name='epub', conf_dir='/path/to/conf') + >>> converter.unstructure(config) + {'builderName': 'epub', 'confDir': '/path/to/conf'} # Note how snake_case is converted to camelCase + + >>> converter.structure({'builderName': 'epub', 'confDir': '/path/to/conf'}, ExampleConfig) + ExampleConfig(build_dir=None, builder_name='epub', conf_dir='/path/to/conf') + +However, depending on the complexity of your type definitions you may find the default converter fail to parse some of your types. + +.. code-block:: pycon + + >>> from typing import Literal, Union + + >>> @attrs.define + ... class ExampleConfig: + ... num_jobs: Union[Literal["auto"], int] = attrs.field(default='auto') + ... + + >>> converter.structure({'numJobs': 'auto'}, ExampleConfig) + + Exception Group Traceback (most recent call last): + | File "<stdin>", line 1, in <module> + | File "/.../python3.10/site-packages/cattrs/converters.py", li + ne 309, in structure + | return self._structure_func.dispatch(cl)(obj, cl) + | File "<cattrs generated structure __main__.ExampleConfig-2>", line 10, in structure_ExampleConfig + | if errors: raise __c_cve('While structuring ' + 'ExampleConfig', errors, __cl) + | cattrs.errors.ClassValidationError: While structuring ExampleConfig (1 sub-exception) + +-+---------------- 1 ---------------- + | Traceback (most recent call last): + | File "<cattrs generated structure __main__.ExampleConfig-2>", line 6, in structure_ExampleConfig + | res['num_jobs'] = __c_structure_num_jobs(o['numJobs'], __c_type_num_jobs) + | File "/.../python3.10/site-packages/cattrs/converters.py", + line 377, in _structure_error + | raise StructureHandlerNotFoundError(msg, type_=cl) + | cattrs.errors.StructureHandlerNotFoundError: Unsupported type: typing.Union[typing.Literal['auto'], int]. + Register a structure hook for it. + | Structuring class ExampleConfig @ attribute num_jobs + +------------------------------------ + +In which case you can extend the converter provided by ``pygls`` with your own `structure hooks`_ + +.. code-block:: python + + from pygls.protocol import default_converter + + def custom_converter(): + converter = default_converter() + converter.register_structure_hook(Union[Literal['auto', int], lambda obj, _: obj) + + return converter + +You can then override the default converter used by ``pygls`` when constructing your language server instance + +.. code-block:: python + + server = LanguageServer( + name="my-language-server", version="v1.0", converter_factory=custom_converter + ) + +See the `hooks.py`_ module in ``lsprotocol`` for some example structure hooks + +Miscellaneous +------------- + +Mandatory ``name`` and ``version`` +"""""""""""""""""""""""""""""""""" + +It is now necessary to provide a name and version when constructing an instance of the ``LanguageServer`` class + +.. code-block:: python + + from pygls.server import LanguageServer + + server = LanguageServer(name="my-language-server", version="v1.0") + + +``ClientCapabilities.get_capability`` is now ``get_capability`` +""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + +.. code-block:: python + + # Before + from pygls.lsp.types import ClientCapabilities + + client_capabilities = ClientCapabilities() + commit_character_support = client_capabilities.get_capability( + "text_document.completion.completion_item.commit_characters_support", False + ) + +.. code-block:: python + + # After + from lsprotocol.types import ClientCapabilities + from pygls.capabilities import get_capability + + client_capabilities = ClientCapabilities() + commit_character_support = get_capability( + client_capabilities, + "text_document.completion.completion_item.commit_characters_support", + False + ) + +.. _attrs: https://www.attrs.org/en/stable/index.html +.. _cattrs: https://cattrs.readthedocs.io/en/stable/ +.. _converter: https://cattrs.readthedocs.io/en/stable/converters.html +.. _hooks.py: https://github.com/microsoft/lsprotocol/blob/main/lsprotocol/_hooks.py +.. _lsprotocol: https://github.com/microsoft/lsprotocol +.. _pydantic: https://pydantic-docs.helpmanual.io/ +.. _structure hooks: https://cattrs.readthedocs.io/en/stable/structuring.html#registering-custom-structuring-hooks +.. _jedi-language-server: https://github.com/pappasam/jedi-language-server/pull/230 +.. _yara-language-server: https://github.com/avast/yls/pull/34 +.. _vscode-ruff: https://github.com/charliermarsh/vscode-ruff/pull/37 +.. _esbonio: https://github.com/swyddfa/esbonio/pull/484 diff --git a/docs/source/pages/reference.rst b/docs/source/pages/reference.rst new file mode 100644 index 0000000..2b0aa56 --- /dev/null +++ b/docs/source/pages/reference.rst @@ -0,0 +1,8 @@ +API Reference +============= + +.. toctree:: + :glob: + :maxdepth: 2 + + reference/* diff --git a/docs/source/pages/reference/clients.rst b/docs/source/pages/reference/clients.rst new file mode 100644 index 0000000..b4c37ed --- /dev/null +++ b/docs/source/pages/reference/clients.rst @@ -0,0 +1,8 @@ +Clients +======= + +.. autoclass:: pygls.lsp.client.BaseLanguageClient + :members: + +.. autoclass:: pygls.client.JsonRPCClient + :members: diff --git a/docs/source/pages/reference/protocol.rst b/docs/source/pages/reference/protocol.rst new file mode 100644 index 0000000..e9bd2f7 --- /dev/null +++ b/docs/source/pages/reference/protocol.rst @@ -0,0 +1,11 @@ +Protocol +======== + + +.. autoclass:: pygls.protocol.LanguageServerProtocol + :members: + +.. autoclass:: pygls.protocol.JsonRPCProtocol + :members: + +.. autofunction:: pygls.protocol.default_converter diff --git a/docs/source/pages/reference/servers.rst b/docs/source/pages/reference/servers.rst new file mode 100644 index 0000000..2a664ed --- /dev/null +++ b/docs/source/pages/reference/servers.rst @@ -0,0 +1,11 @@ +Servers +======= + +.. autoclass:: pygls.server.LanguageServer + :members: + +.. autoclass:: pygls.server.Server + :members: + + + diff --git a/docs/source/pages/reference/types.rst b/docs/source/pages/reference/types.rst new file mode 100644 index 0000000..1cbcb1f --- /dev/null +++ b/docs/source/pages/reference/types.rst @@ -0,0 +1,10 @@ +Types +===== + +LSP type definitions in ``pygls`` are provided by the `lsprotocol <https://github.com/microsoft/lsprotocol>`__ library + +.. automodule:: lsprotocol.types + :members: + :undoc-members: + + diff --git a/docs/source/pages/reference/workspace.rst b/docs/source/pages/reference/workspace.rst new file mode 100644 index 0000000..1e392dc --- /dev/null +++ b/docs/source/pages/reference/workspace.rst @@ -0,0 +1,9 @@ +Workspace +========= + +.. autoclass:: pygls.workspace.TextDocument + :members: + +.. autoclass:: pygls.workspace.Workspace + :members: + diff --git a/docs/source/pages/testing.rst b/docs/source/pages/testing.rst new file mode 100644 index 0000000..9910835 --- /dev/null +++ b/docs/source/pages/testing.rst @@ -0,0 +1,24 @@ +.. _testing: + +Testing +======= + +Unit Tests +---------- + +Writing unit tests for registered features and commands are easy and you don't +have to mock the whole language server. If you skipped the advanced usage page, +take a look at :ref:`passing language server instance <passing-instance>` +section for more details. + +Integration Tests +----------------- + +Integration tests coverage includes the whole workflow, from sending the client +request, to getting the result from the server. Since the *Language Server +Protocol* defines bidirectional communication between the client and the +server, we used *pygls* to simulate the client and send desired requests to the +server. To get a better understanding of how to set it up, take a look at our test +`fixtures`_. + +.. _fixtures: https://github.com/openlawlibrary/pygls/blob/main/tests/conftest.py diff --git a/docs/source/pages/tutorial.rst b/docs/source/pages/tutorial.rst new file mode 100644 index 0000000..6db7f6c --- /dev/null +++ b/docs/source/pages/tutorial.rst @@ -0,0 +1,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 diff --git a/docs/source/pages/user-guide.rst b/docs/source/pages/user-guide.rst new file mode 100644 index 0000000..c0ce43e --- /dev/null +++ b/docs/source/pages/user-guide.rst @@ -0,0 +1,536 @@ +.. _user-guide: + +User Guide +========== + +Language Server +--------------- + +The language server is responsible for managing the connection with the client as well as sending and receiving messages over +the `Language Server Protocol <https://microsoft.github.io/language-server-protocol/>`__ +which is based on the `Json RPC protocol <https://www.jsonrpc.org/specification>`__. + +Connections +~~~~~~~~~~~ + +*pygls* supports :ref:`ls-tcp`, :ref:`ls-stdio` and :ref:`ls-websocket` connections. + +.. _ls-tcp: + +TCP +^^^ + +TCP connections are usually used while developing the language server. +This way the server can be started in *debug* mode separately and wait +for the client connection. + +.. note:: Server should be started **before** the client. + +The code snippet below shows how to start the server in *TCP* mode. + +.. code:: python + + from pygls.server import LanguageServer + + server = LanguageServer('example-server', 'v0.1') + + server.start_tcp('127.0.0.1', 8080) + +.. _ls-stdio: + +STDIO +^^^^^ + +STDIO connections are useful when client is starting the server as a child +process. This is the way to go in production. + +The code snippet below shows how to start the server in *STDIO* mode. + +.. code:: python + + from pygls.server import LanguageServer + + server = LanguageServer('example-server', 'v0.1') + + server.start_io() + +.. _ls-websocket: + +WEBSOCKET +^^^^^^^^^ + +WEBSOCKET connections are used when you want to expose language server to +browser based editors. + +The code snippet below shows how to start the server in *WEBSOCKET* mode. + +.. code:: python + + from pygls.server import LanguageServer + + server = LanguageServer('example-server', 'v0.1') + + server.start_ws('0.0.0.0', 1234) + +Logging +~~~~~~~ + +Logs are useful for tracing client requests, finding out errors and +measuring time needed to return results to the client. + +*pygls* uses built-in python *logging* module which has to be configured +before server is started. + +Official documentation about logging in python can be found +`here <https://docs.python.org/3/howto/logging-cookbook.html>`__. Below +is the minimal setup to setup logging in *pygls*: + +.. code:: python + + import logging + + from pygls.server import LanguageServer + + logging.basicConfig(filename='pygls.log', filemode='w', level=logging.DEBUG) + + server = LanguageServer('example-server', 'v0.1') + + server.start_io() + +Overriding ``LanguageServerProtocol`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you have a reason to override the existing ``LanguageServerProtocol`` class, +you can do that by inheriting the class and passing it to the ``LanguageServer`` +constructor. + +Custom Error Reporting +~~~~~~~~~~~~~~~~~~~~~~ + +The default :class:`~pygls.server.LanguageServer` will send a :lsp:`window/showMessage` notification to the client to display any uncaught exceptions in the server. +To override this behaviour define your own :meth:`~pygls.server.LanguageServer.report_server_error` method like so: + +.. code:: python + + class CustomLanguageServer(LanguageServer): + def report_server_error(self, error: Exception, source: Union[PyglsError, JsonRpcException]): + pass + +Handling Client Messages +------------------------ + +.. admonition:: Requests vs Notifications + + Unlike a *request*, a *notification* message has no ``id`` field and the server *must not* reply to it. + This means that, even if you return a result inside a handler function for a notification, the result won't be passed to the client. + + The ``Language Server Protocol``, unlike ``Json RPC``, allows bidirectional communication between the server and the client. + +For the majority of the time, a language server will be responding to requests and notifications sent from the client. +*pygls* refers to the handlers for all of these messages as *features* with one exception. + +The Language Server protocol allows a server to define named methods that a client can invoke by sending a :lsp:`workspace/executeCommand` request. +Unsurprisingly, *pygls* refers to these named methods a *commands*. + +*Built-In* Features +~~~~~~~~~~~~~~~~~~~ + +*pygls* comes with following predefined set of handlers for the following +`Language Server Protocol <https://microsoft.github.io/language-server-protocol/>`__ +(LSP) features: + +.. note:: + + *Built-in* features in most cases should *not* be overridden. + + If you need to do some additional processing of one of the messages listed below, register a feature with the same name and your handler will be called immediately after the corresponding built-in feature. + +**Lifecycle Messages** + +- The :lsp:`initialize` request is sent as a first request from client to the server to setup their communication. + *pygls* automatically computes registered LSP capabilities and sends them as part of the :class:`~lsprotocol.types.InitializeResult` response. + +- The :lsp:`shutdown` request is sent from the client to the server to ask the server to shutdown. + +- The :lsp:`exit` notification is sent from client to the server to ask the server to exit the process. + *pygls* automatically releases all resources and stops the process. + +**Text Document Synchronization** + +- The :lsp:`textDocument/didOpen` notification will tell *pygls* to create a document in the in-memory workspace which will exist as long as the document is opened in editor. + +- The :lsp:`textDocument/didChange` notification will tell *pygls* to update the document text. + *pygls* supports *full* and *incremental* document changes. + +- The :lsp:`textDocument/didClose` notification will tell *pygls* to remove a document from the in-memory workspace. + +**Notebook Document Synchronization** + +- The :lsp:`notebookDocument/didOpen` notification will tell *pygls* to create a notebook document in the in-memory workspace which will exist as long as the document is opened in editor. + +- The :lsp:`notebookDocument/didChange` notification will tell *pygls* to update the notebook document include its content, metadata, execution results and cell structure. + +- The :lsp:`notebookDocument/didClose` notification will tell *pygls* to remove the notebook from the in-memory workspace. + +**Miscellanous** + +- The :lsp:`workspace/didChangeWorkspaceFolders` notification will tell *pygls* to update in-memory workspace folders. + +- The :lsp:`workspace/executeCommand` request will tell *pygls* to execute a custom command. + +- The :lsp:`$/setTrace` notification tells *pygls* to update the server's :class:`TraceValue <lsprotocol.types.TraceValues>`. + +.. _ls-handlers: + +Registering Handlers +~~~~~~~~~~~~~~~~~~~~ + +.. seealso:: + + It's recommeded that you follow the :ref:`tutorial <tutorial>` before reading this section. + +- The :func:`~pygls.server.LanguageServer.feature` decorator is used to register a handler for a given LSP message. +- The :func:`~pygls.server.LanguageServer.command` decorator is used to register a named command. + +The following applies to both feature and command handlers. + +Language servers using *pygls* run in an *asyncio event loop*. +They *asynchronously* listen for incoming messages and, depending on the way handler is registered, apply different execution strategies to process the message. + +Depending on the use case, handlers can be registered in three different ways: + +- as an :ref:`async <ls-handler-async>` function +- as a :ref:`synchronous <ls-handler-sync>` function +- as a :ref:`threaded <ls-handler-thread>` function + +.. _ls-handler-async: + +*Asynchronous* Functions (*Coroutines*) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +*pygls* supports ``python 3.8+`` which has a keyword ``async`` to +specify coroutines. + +The code snippet below shows how to register a command as a coroutine: + +.. code:: python + + @json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_NON_BLOCKING) + async def count_down_10_seconds_non_blocking(ls, *args): + # Omitted + +Registering a *feature* as a coroutine is exactly the same. + +Coroutines are functions that are executed as tasks in *pygls*'s *event +loop*. They should contain at least one *await* expression (see +`awaitables <https://docs.python.org/3.5/glossary.html#term-awaitable>`__ +for details) which tells event loop to switch to another task while +waiting. This allows *pygls* to listen for client requests in a +*non blocking* way, while still only running in the *main* thread. + +Tasks can be canceled by the client if they didn't start executing (see +`Cancellation +Support <https://microsoft.github.io/language-server-protocol/specification#cancelRequest>`__). + +.. warning:: + + Using computation intensive operations will *block* the main thread and + should be *avoided* inside coroutines. Take a look at + `threaded functions <#threaded-functions>`__ for more details. + +.. _ls-handler-sync: + +*Synchronous* Functions +^^^^^^^^^^^^^^^^^^^^^^^ + +Synchronous functions are regular functions which *blocks* the *main* +thread until they are executed. + +`Built-in features <#built-in-features>`__ are registered as regular +functions to ensure correct state of language server initialization and +workspace. + +The code snippet below shows how to register a command as a regular +function: + +.. code:: python + + @json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_BLOCKING) + def count_down_10_seconds_blocking(ls, *args): + # Omitted + +Registering *feature* as a regular function is exactly the same. + +.. warning:: + + Using computation intensive operations will *block* the main thread and + should be *avoided* inside regular functions. Take a look at + `threaded functions <#threaded-functions>`__ for more details. + +.. _ls-handler-thread: + +*Threaded* Functions +^^^^^^^^^^^^^^^^^^^^ + +*Threaded* functions are just regular functions, but marked with +*pygls*'s ``thread`` decorator: + +.. code:: python + + # Decorator order is not important in this case + @json_server.thread() + @json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_BLOCKING) + def count_down_10_seconds_blocking(ls, *args): + # Omitted + +*pygls* uses its own *thread pool* to execute above function in *daemon* +thread and it is *lazy* initialized first time when function marked with +``thread`` decorator is fired. + +*Threaded* functions can be used to run blocking operations. If it has been a +while or you are new to threading in Python, check out Python's +``multithreading`` and `GIL <https://en.wikipedia.org/wiki/Global_interpreter_lock>`__ +before messing with threads. + +.. _passing-instance: + +Passing Language Server Instance +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Using language server methods inside registered features and commands are quite +common. We recommend adding language server as a **first parameter** of a +registered function. + +There are two ways of doing this: + +- **ls** (**l**\anguage **s**\erver) naming convention + +Add **ls** as first parameter of a function and *pygls* will automatically pass +the language server instance. + +.. code-block:: python + + @json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_BLOCKING) + def count_down_10_seconds_blocking(ls, *args): + # Omitted + + +- add **type** to first parameter + +Add the **LanguageServer** class or any class derived from it as a type to +first parameter of a function + +.. code-block:: python + + @json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_BLOCKING) + def count_down_10_seconds_blocking(ser: JsonLanguageServer, *args): + # Omitted + + +Using outer ``json_server`` instance inside registered function will make +writing unit :ref:`tests <testing>` more difficult. + +Communicating with the Client +----------------------------- + +.. important:: + + Most of the messages listed here cannot be sent until the LSP session has been initialized. + See the section on the :lsp:`initiaiize` request in the specification for more details. + +In addition to responding to requests, there are a number of additional messages a server can send to the client. + +Configuration +~~~~~~~~~~~~~ + +The :lsp:`workspace/configuration` request is sent from the server to the client in order to fetch configuration settings from the client. +Depending on how the handler is registered (see :ref:`here <ls-handlers>`) you can use the :meth:`~pygls.server.LanguageServer.get_configuration` or :meth:`~pygls.server.LanguageServer.get_configuration_async` methods to request configuration from the client: + +- *asynchronous* functions (*coroutines*) + + .. code:: python + + # await keyword tells event loop to switch to another task until notification is received + config = await ls.get_configuration( + WorkspaceConfigurationParams( + items=[ + ConfigurationItem(scope_uri='doc_uri_here', section='section') + ] + ) + ) + +- *synchronous* functions + + .. code:: python + + # callback is called when notification is received + def callback(config): + # Omitted + + params = WorkspaceConfigurationParams( + items=[ + ConfigurationItem(scope_uri='doc_uri_here', section='section') + ] + ) + config = ls.get_configuration(params, callback) + +- *threaded* functions + + .. code:: python + + # .result() will block the thread + config = ls.get_configuration( + WorkspaceConfigurationParams( + items=[ + ConfigurationItem(scope_uri='doc_uri_here', section='section') + ] + ) + ).result() + +Publish Diagnostics +~~~~~~~~~~~~~~~~~~~ + +:lsp:`textDocument/publishDiagnostics` notifications are sent from the server to the client to highlight errors or potential issues. e.g. syntax errors or unused variables. + +Usually this notification is sent after document is opened, or on document content change: + +.. code:: python + + @json_server.feature(TEXT_DOCUMENT_DID_OPEN) + async def did_open(ls, params: DidOpenTextDocumentParams): + """Text document did open notification.""" + ls.show_message("Text Document Did Open") + ls.show_message_log("Validating json...") + + # Get document from workspace + text_doc = ls.workspace.get_text_document(params.text_document.uri) + + diagnostic = Diagnostic( + range=Range( + start=Position(line-1, col-1), + end=Position(line-1, col) + ), + message="Custom validation message", + source="Json Server" + ) + + # Send diagnostics + ls.publish_diagnostics(text_doc.uri, [diagnostic]) + +Show Message +~~~~~~~~~~~~ + +:lsp:`window/showMessage` is a notification that is sent from the server to the client to display a prominant text message. e.g. VSCode will render this as a notification popup + +.. code:: python + + @json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_NON_BLOCKING) + async def count_down_10_seconds_non_blocking(ls, *args): + for i in range(10): + # Sends message notification to the client + ls.show_message(f"Counting down... {10 - i}") + await asyncio.sleep(1) + +Show Message Log +~~~~~~~~~~~~~~~~ + +:lsp:`window/logMessage` is a notification that is sent from the server to the client to display a discrete text message. e.g. VSCode will display the message in an :guilabel:`Output` channel. + +.. code:: python + + @json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_NON_BLOCKING) + async def count_down_10_seconds_non_blocking(ls, *args): + for i in range(10): + # Sends message log notification to the client + ls.show_message_log(f"Counting down... {10 - i}") + await asyncio.sleep(1) + +Workspace Edits +~~~~~~~~~~~~~~~ + +The :lsp:`workspace/applyEdit` request allows your language server to ask the client to modify particular documents in the client's workspace. + +.. code:: python + + def apply_edit(self, edit: WorkspaceEdit, label: str = None) -> ApplyWorkspaceEditResponse: + # Omitted + + def apply_edit_async(self, edit: WorkspaceEdit, label: str = None) -> ApplyWorkspaceEditResponse: + # Omitted + +Custom Notifications +~~~~~~~~~~~~~~~~~~~~ + +.. warning:: + + Custom notifications are not part of the LSP specification and dedicated support for your custom notification(s) will have to be added to each language client you intend to support. + +A custom notification can be sent to the client using the :meth:`~pygls.server.LanguageServer.send_notification` method + +.. code:: python + + server.send_notification('myCustomNotification', 'test data') + + +The Workspace +------------- + +The :class:`~pygls.workspace.Workspace` is a python object that holds information about workspace folders, opened documents and is responsible for synchronising server side document state with that of the client. + +**Text Documents** + +The :class:`~pygls.workspace.TextDocument` class is how *pygls* represents a text document. +Given a text document's uri the :meth:`~pygls.workspace.Workspace.get_text_document` method can be used to access the document itself: + +.. code:: python + + @json_server.feature(TEXT_DOCUMENT_DID_OPEN) + async def did_open(ls, params: DidOpenTextDocumentParams): + + # Get document from workspace + text_doc = ls.workspace.get_text_document(params.text_document.uri) + +**Notebook Documents** + +.. seealso:: + + See the section on :lsp:`notebookDocument/synchronization` in the specification for full details on how notebook documents are handled + +- A notebook's structure, metadata etc. is represented using the :class:`~lsprotocol.types.NotebookDocument` class from ``lsprotocol``. +- The contents of a single notebook cell is represented using a standard :class:`~pygls.workspace.TextDocument` + +In order to receive notebook documents from the client, your language server must provide an instance of :class:`~lsprotocol.types.NotebookDocumentSyncOptions` which declares the kind of notebooks it is interested in + +.. code-block:: python + + server = LanguageServer( + name="example-server", + version="v0.1", + notebook_document_sync=types.NotebookDocumentSyncOptions( + notebook_selector=[ + types.NotebookDocumentSyncOptionsNotebookSelectorType2( + cells=[ + types.NotebookDocumentSyncOptionsNotebookSelectorType2CellsType( + language="python" + ) + ] + ) + ] + ), + ) + +To access the contents of a notebook cell you would call the workspace's :meth:`~pygls.workspace.Workspace.get_text_document` method as normal. + +.. code-block:: python + + cell_doc = ls.workspace.get_text_document(cell_uri) + +To access the notebook itself call the workspace's :meth:`~pygls.workspace.Workspace.get_notebook_document` method with either the uri of the notebook *or* the uri of any of its cells. + +.. code-block:: python + + notebook_doc = ls.workspace.get_notebook_document(notebook_uri=notebook_uri) + + # -- OR -- + + notebook_doc = ls.workspace.get_notebook_document(cell_uri=cell_uri) |