diff --git a/README.md b/README.md index 085efa81..ffc939f5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ _pygls_ (pronounced like "pie glass") is a pythonic generic implementation of th ## Quickstart ```python -from pygls.server import LanguageServer +from pygls.lsp.server import LanguageServer from lsprotocol.types import ( TEXT_DOCUMENT_COMPLETION, CompletionItem, diff --git a/docs/source/user-guide.rst b/docs/source/user-guide.rst index 4c6e2016..4e3e3c1b 100644 --- a/docs/source/user-guide.rst +++ b/docs/source/user-guide.rst @@ -30,7 +30,7 @@ The code snippet below shows how to start the server in *TCP* mode. .. code:: python - from pygls.server import LanguageServer + from pygls.lsp.server import LanguageServer server = LanguageServer('example-server', 'v0.1') @@ -48,7 +48,7 @@ The code snippet below shows how to start the server in *STDIO* mode. .. code:: python - from pygls.server import LanguageServer + from pygls.lsp.server import LanguageServer server = LanguageServer('example-server', 'v0.1') @@ -66,7 +66,7 @@ The code snippet below shows how to start the server in *WEBSOCKET* mode. .. code:: python - from pygls.server import LanguageServer + from pygls.lsp.server import LanguageServer server = LanguageServer('example-server', 'v0.1') @@ -89,7 +89,7 @@ is the minimal setup to setup logging in *pygls*: import logging - from pygls.server import LanguageServer + from pygls.lsp.server import LanguageServer logging.basicConfig(filename='pygls.log', filemode='w', level=logging.DEBUG) diff --git a/examples/hello-world/main.py b/examples/hello-world/main.py index 340e5611..b3970fcf 100644 --- a/examples/hello-world/main.py +++ b/examples/hello-world/main.py @@ -1,4 +1,4 @@ -from pygls.server import LanguageServer +from pygls.lsp.server import LanguageServer from lsprotocol import types server = LanguageServer("example-server", "v0.1") diff --git a/examples/servers/code_actions.py b/examples/servers/code_actions.py index 989df818..5516d314 100644 --- a/examples/servers/code_actions.py +++ b/examples/servers/code_actions.py @@ -24,8 +24,9 @@ This server scans the document for incomplete sums e.g. ``1 + 1 =`` and returns a code action which, when invoked will fill in the answer. """ + import re -from pygls.server import LanguageServer +from pygls.lsp.server import LanguageServer from lsprotocol.types import ( TEXT_DOCUMENT_CODE_ACTION, CodeAction, diff --git a/examples/servers/code_lens.py b/examples/servers/code_lens.py index 85af5687..dcbc7aad 100644 --- a/examples/servers/code_lens.py +++ b/examples/servers/code_lens.py @@ -28,12 +28,13 @@ front, this example demonstrates how the ``codeLens/resolve`` can be used to defer this computation until it is actually necessary. """ + import logging import re from lsprotocol import types -from pygls.server import LanguageServer +from pygls.lsp.server import LanguageServer ADDITION = re.compile(r"^\s*(\d+)\s*\+\s*(\d+)\s*=(?=\s*$)") server = LanguageServer("code-lens-server", "v1") @@ -115,7 +116,8 @@ def evaluate_sum(ls: LanguageServer, args): answer = arguments["left"] + arguments["right"] edit = types.TextDocumentEdit( text_document=types.OptionalVersionedTextDocumentIdentifier( - uri=arguments["uri"], version=document.version + uri=arguments["uri"], + version=document.version, ), edits=[ types.TextEdit( @@ -129,7 +131,11 @@ def evaluate_sum(ls: LanguageServer, args): ) # Apply the edit. - ls.apply_edit(types.WorkspaceEdit(document_changes=[edit])) + ls.workspace_apply_edit( + types.ApplyWorkspaceEditParams( + edit=types.WorkspaceEdit(document_changes=[edit]), + ), + ) if __name__ == "__main__": diff --git a/examples/servers/colors.py b/examples/servers/colors.py index 0e6cc206..28884c97 100644 --- a/examples/servers/colors.py +++ b/examples/servers/colors.py @@ -43,12 +43,13 @@ This server implements the requests defined above for CSS's hex color code syntax (``#000`` and ``#000000``). """ + import logging import re from lsprotocol import types -from pygls.server import LanguageServer +from pygls.lsp.server import LanguageServer COLOR = re.compile(r"""\#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})(?!\w)""") server = LanguageServer("color-server", "v1") diff --git a/examples/servers/formatting.py b/examples/servers/formatting.py index ee7beffb..5c736d26 100644 --- a/examples/servers/formatting.py +++ b/examples/servers/formatting.py @@ -33,6 +33,7 @@ expect (fixes welcome!), but it should be enough to demonstrate the expected interaction between client and server. """ + import logging from typing import Dict from typing import List @@ -41,7 +42,7 @@ import attrs from lsprotocol import types -from pygls.server import LanguageServer +from pygls.lsp.server import LanguageServer from pygls.workspace import TextDocument diff --git a/examples/servers/goto.py b/examples/servers/goto.py index c77d5288..f2e081bf 100644 --- a/examples/servers/goto.py +++ b/examples/servers/goto.py @@ -32,12 +32,13 @@ This means the choices of what the example server below will return results for are completely arbitrary. """ + import logging import re from lsprotocol import types -from pygls.server import LanguageServer +from pygls.lsp.server import LanguageServer from pygls.workspace import TextDocument ARGUMENT = re.compile(r"(?P\w+): (?P\w+)") diff --git a/examples/servers/hover.py b/examples/servers/hover.py index fee94d3d..90aa3e5a 100644 --- a/examples/servers/hover.py +++ b/examples/servers/hover.py @@ -23,12 +23,13 @@ displaying a table how the selected date would be formatted in each of the supported formats. """ + import logging from datetime import datetime from lsprotocol import types -from pygls.server import LanguageServer +from pygls.lsp.server import LanguageServer DATE_FORMATS = [ "%H:%M:%S", diff --git a/examples/servers/inlay_hints.py b/examples/servers/inlay_hints.py index 6c143737..4e961090 100644 --- a/examples/servers/inlay_hints.py +++ b/examples/servers/inlay_hints.py @@ -28,11 +28,12 @@ uses the ``inlayHint/resolve`` to demonstrate how you can defer expensive computations to when they are required. """ + import re from typing import Optional from lsprotocol import types -from pygls.server import LanguageServer +from pygls.lsp.server import LanguageServer NUMBER = re.compile(r"\d+") server = LanguageServer("inlay-hint-server", "v1") diff --git a/examples/servers/json_server.py b/examples/servers/json_server.py index bc540037..1255d007 100644 --- a/examples/servers/json_server.py +++ b/examples/servers/json_server.py @@ -30,17 +30,17 @@ Eventually this example will be broken up in smaller, more focused examples and how to guides. """ + import argparse import asyncio -import json import time import uuid -from json import JSONDecodeError +from functools import partial from typing import Optional from lsprotocol import types as lsp -from pygls.server import LanguageServer +from pygls.lsp.server import LanguageServer COUNT_DOWN_START_IN_SECONDS = 10 COUNT_DOWN_SLEEP_IN_SECONDS = 1 @@ -65,85 +65,6 @@ def __init__(self, *args): json_server = JsonLanguageServer("pygls-json-example", "v0.1") -def _validate(ls, params): - ls.show_message_log("Validating json...") - - text_doc = ls.workspace.get_document(params.text_document.uri) - - source = text_doc.source - diagnostics = _validate_json(source) if source else [] - - ls.publish_diagnostics(text_doc.uri, diagnostics) - - -def _validate_json(source): - """Validates json file.""" - diagnostics = [] - - try: - json.loads(source) - except JSONDecodeError as err: - msg = err.msg - col = err.colno - line = err.lineno - - d = lsp.Diagnostic( - range=lsp.Range( - start=lsp.Position(line=line - 1, character=col - 1), - end=lsp.Position(line=line - 1, character=col), - ), - message=msg, - source=type(json_server).__name__, - ) - - diagnostics.append(d) - - return diagnostics - - -@json_server.feature( - lsp.TEXT_DOCUMENT_DIAGNOSTIC, - lsp.DiagnosticOptions( - identifier="jsonServer", - inter_file_dependencies=True, - workspace_diagnostics=True, - ), -) -def text_document_diagnostic( - params: lsp.DocumentDiagnosticParams, -) -> lsp.DocumentDiagnosticReport: - """Returns diagnostic report.""" - document = json_server.workspace.get_document(params.text_document.uri) - return lsp.RelatedFullDocumentDiagnosticReport( - items=_validate_json(document.source), - kind=lsp.DocumentDiagnosticReportKind.Full, - ) - - -@json_server.feature(lsp.WORKSPACE_DIAGNOSTIC) -def workspace_diagnostic( - params: lsp.WorkspaceDiagnosticParams, -) -> lsp.WorkspaceDiagnosticReport: - """Returns diagnostic report.""" - documents = json_server.workspace.text_documents.keys() - - if len(documents) == 0: - items = [] - else: - first = list(documents)[0] - document = json_server.workspace.get_document(first) - items = [ - lsp.WorkspaceFullDocumentDiagnosticReport( - uri=document.uri, - version=document.version, - items=_validate_json(document.source), - kind=lsp.DocumentDiagnosticReportKind.Full, - ) - ] - - return lsp.WorkspaceDiagnosticReport(items=items) - - @json_server.feature( lsp.TEXT_DOCUMENT_COMPLETION, lsp.CompletionOptions(trigger_characters=[","], all_commit_characters=[":"]), @@ -163,76 +84,61 @@ def completions(params: Optional[lsp.CompletionParams] = None) -> lsp.Completion @json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_BLOCKING) -def count_down_10_seconds_blocking(ls, *args): +def count_down_10_seconds_blocking(ls: JsonLanguageServer, *args): """Starts counting down and showing message synchronously. It will `block` the main thread, which can be tested by trying to show completion items. """ for i in range(COUNT_DOWN_START_IN_SECONDS): - ls.show_message(f"Counting down... {COUNT_DOWN_START_IN_SECONDS - i}") + ls.window_show_message( + lsp.ShowMessageParams( + message=f"Counting down... {COUNT_DOWN_START_IN_SECONDS - i}", + type=lsp.MessageType.Info, + ), + ) time.sleep(COUNT_DOWN_SLEEP_IN_SECONDS) @json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_NON_BLOCKING) -async def count_down_10_seconds_non_blocking(ls, *args): +async def count_down_10_seconds_non_blocking(ls: JsonLanguageServer, *args): """Starts counting down and showing message asynchronously. It won't `block` the main thread, which can be tested by trying to show completion items. """ for i in range(COUNT_DOWN_START_IN_SECONDS): - ls.show_message(f"Counting down... {COUNT_DOWN_START_IN_SECONDS - i}") + ls.window_show_message( + lsp.ShowMessageParams( + message=f"Counting down... {COUNT_DOWN_START_IN_SECONDS - i}", + type=lsp.MessageType.Info, + ), + ) await asyncio.sleep(COUNT_DOWN_SLEEP_IN_SECONDS) -@json_server.feature(lsp.TEXT_DOCUMENT_DID_CHANGE) -def did_change(ls, params: lsp.DidChangeTextDocumentParams): - """Text document did change notification.""" - _validate(ls, params) - - -@json_server.feature(lsp.TEXT_DOCUMENT_DID_CLOSE) -def did_close(server: JsonLanguageServer, params: lsp.DidCloseTextDocumentParams): - """Text document did close notification.""" - server.show_message("Text Document Did Close") - - -@json_server.feature(lsp.TEXT_DOCUMENT_DID_OPEN) -async def did_open(ls, params: lsp.DidOpenTextDocumentParams): - """Text document did open notification.""" - ls.show_message("Text Document Did Open") - _validate(ls, params) - - -@json_server.feature(lsp.TEXT_DOCUMENT_INLINE_VALUE) -def inline_value(params: lsp.InlineValueParams): - """Returns inline value.""" - return [lsp.InlineValueText(range=params.range, text="Inline value")] - - @json_server.command(JsonLanguageServer.CMD_PROGRESS) async def progress(ls: JsonLanguageServer, *args): """Create and start the progress on the client.""" token = str(uuid.uuid4()) # Create - await ls.progress.create_async(token) + await ls.work_done_progress.create_async(token) # Begin - ls.progress.begin( + ls.work_done_progress.begin( token, lsp.WorkDoneProgressBegin(title="Indexing", percentage=0, cancellable=True), ) # Report for i in range(1, 10): # Check for cancellation from client - if ls.progress.tokens[token].cancelled(): + if ls.work_done_progress.tokens[token].cancelled(): # ... and stop the computation if client cancelled return - ls.progress.report( + ls.work_done_progress.report( token, lsp.WorkDoneProgressReport(message=f"{i * 10}%", percentage=i * 10), ) await asyncio.sleep(2) # End - ls.progress.end(token, lsp.WorkDoneProgressEnd(message="Finished")) + ls.work_done_progress.end(token, lsp.WorkDoneProgressEnd(message="Finished")) @json_server.command(JsonLanguageServer.CMD_REGISTER_COMPLETIONS) @@ -247,59 +153,104 @@ async def register_completions(ls: JsonLanguageServer, *args): ) ] ) - response = await ls.register_capability_async(params) - if response is None: - ls.show_message("Successfully registered completions method") - else: - ls.show_message( - "Error happened during completions registration.", lsp.MessageType.Error + + try: + await ls.client_register_capability_async(params) + ls.window_show_message( + lsp.ShowMessageParams( + message="Successfully registered completions method", + type=lsp.MessageType.Info, + ), + ) + except Exception: + ls.window_show_message( + lsp.ShowMessageParams( + message="Error happened during completions registration.", + type=lsp.MessageType.Error, + ), ) -@json_server.command(JsonLanguageServer.CMD_SHOW_CONFIGURATION_ASYNC) -async def show_configuration_async(ls: JsonLanguageServer, *args): - """Gets exampleConfiguration from the client settings using coroutines.""" +@json_server.command(JsonLanguageServer.CMD_UNREGISTER_COMPLETIONS) +async def unregister_completions(ls: JsonLanguageServer, *args): + """Unregister completions method on the client.""" + params = lsp.UnregistrationParams( + unregisterations=[ + lsp.Unregistration( + id=str(uuid.uuid4()), + method=lsp.TEXT_DOCUMENT_COMPLETION, + ), + ], + ) + try: - config = await ls.get_configuration_async( - lsp.ConfigurationParams( - items=[ - lsp.ConfigurationItem( - scope_uri="", section=JsonLanguageServer.CONFIGURATION_SECTION - ) - ] - ) + await ls.client_unregister_capability_async(params) + ls.window_show_message( + lsp.ShowMessageParams( + message="Successfully unregistered completions method", + type=lsp.MessageType.Info, + ), + ) + except Exception: + ls.window_show_message( + lsp.ShowMessageParams( + message="Error happened during completions unregistration.", + type=lsp.MessageType.Error, + ), ) + +def handle_config(ls: JsonLanguageServer, config): + """Handle the configuration sent by the client.""" + try: example_config = config[0].get("exampleConfiguration") - ls.show_message(f"jsonServer.exampleConfiguration value: {example_config}") + ls.window_show_message( + lsp.ShowMessageParams( + message=f"jsonServer.exampleConfiguration value: {example_config}", + type=lsp.MessageType.Info, + ), + ) except Exception as e: - ls.show_message_log(f"Error ocurred: {e}") + ls.window_log_message( + lsp.LogMessageParams( + message=f"Error ocurred: {e}", + type=lsp.MessageType.Error, + ), + ) + + +@json_server.command(JsonLanguageServer.CMD_SHOW_CONFIGURATION_ASYNC) +async def show_configuration_async(ls: JsonLanguageServer, *args): + """Gets exampleConfiguration from the client settings using coroutines.""" + config = await ls.workspace_configuration_async( + lsp.ConfigurationParams( + items=[ + lsp.ConfigurationItem( + scope_uri="", + section=JsonLanguageServer.CONFIGURATION_SECTION, + ), + ] + ) + ) + handle_config(ls, config) @json_server.command(JsonLanguageServer.CMD_SHOW_CONFIGURATION_CALLBACK) def show_configuration_callback(ls: JsonLanguageServer, *args): """Gets exampleConfiguration from the client settings using callback.""" - def _config_callback(config): - try: - example_config = config[0].get("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( + ls.workspace_configuration( lsp.ConfigurationParams( items=[ lsp.ConfigurationItem( - scope_uri="", section=JsonLanguageServer.CONFIGURATION_SECTION - ) + scope_uri="", + section=JsonLanguageServer.CONFIGURATION_SECTION, + ), ] ), - _config_callback, + callback=partial(handle_config, ls), ) @@ -307,42 +258,17 @@ def _config_callback(config): @json_server.command(JsonLanguageServer.CMD_SHOW_CONFIGURATION_THREAD) def show_configuration_thread(ls: JsonLanguageServer, *args): """Gets exampleConfiguration from the client settings using thread pool.""" - try: - config = ls.get_configuration( - lsp.ConfigurationParams( - items=[ - lsp.ConfigurationItem( - scope_uri="", section=JsonLanguageServer.CONFIGURATION_SECTION - ) - ] - ) - ).result(2) - - example_config = config[0].get("exampleConfiguration") - - ls.show_message(f"jsonServer.exampleConfiguration value: {example_config}") - - except Exception as e: - ls.show_message_log(f"Error ocurred: {e}") - - -@json_server.command(JsonLanguageServer.CMD_UNREGISTER_COMPLETIONS) -async def unregister_completions(ls: JsonLanguageServer, *args): - """Unregister completions method on the client.""" - params = lsp.UnregistrationParams( - unregisterations=[ - lsp.Unregistration( - id=str(uuid.uuid4()), method=lsp.TEXT_DOCUMENT_COMPLETION - ) - ] - ) - response = await ls.unregister_capability_async(params) - if response is None: - ls.show_message("Successfully unregistered completions method") - else: - ls.show_message( - "Error happened during completions unregistration.", lsp.MessageType.Error + config = ls.workspace_configuration( + lsp.ConfigurationParams( + items=[ + lsp.ConfigurationItem( + scope_uri="", + section=JsonLanguageServer.CONFIGURATION_SECTION, + ), + ], ) + ).result(2) + handle_config(ls, config) def add_arguments(parser): diff --git a/examples/servers/links.py b/examples/servers/links.py index 8857d196..99bacddb 100644 --- a/examples/servers/links.py +++ b/examples/servers/links.py @@ -33,7 +33,7 @@ from lsprotocol import types -from pygls.server import LanguageServer +from pygls.lsp.server import LanguageServer LINK = re.compile(r"<(\w+):([^>]+)>") server = LanguageServer("links-server", "v1") diff --git a/examples/servers/publish_diagnostics.py b/examples/servers/publish_diagnostics.py index 9e887dbf..3eb8ef6b 100644 --- a/examples/servers/publish_diagnostics.py +++ b/examples/servers/publish_diagnostics.py @@ -24,12 +24,13 @@ This server scans a document for sums e.g. ``1 + 2 = 3``, highlighting any that are either missing answers (warnings) or incorrect (errors). """ + import logging import re from lsprotocol import types -from pygls.server import LanguageServer +from pygls.lsp.server import LanguageServer from pygls.workspace import TextDocument ADDITION = re.compile(r"^\s*(\d+)\s*\+\s*(\d+)\s*=\s*(\d+)?$") @@ -89,7 +90,13 @@ def did_open(ls: PublishDiagnosticServer, params: types.DidOpenTextDocumentParam ls.parse(doc) for uri, (version, diagnostics) in ls.diagnostics.items(): - ls.publish_diagnostics(uri=uri, version=version, diagnostics=diagnostics) + ls.text_document_publish_diagnostics( + types.PublishDiagnosticsParams( + uri=uri, + version=version, + diagnostics=diagnostics, + ) + ) @server.feature(types.TEXT_DOCUMENT_DID_CHANGE) @@ -99,7 +106,13 @@ def did_change(ls: PublishDiagnosticServer, params: types.DidOpenTextDocumentPar ls.parse(doc) for uri, (version, diagnostics) in ls.diagnostics.items(): - ls.publish_diagnostics(uri=uri, version=version, diagnostics=diagnostics) + ls.text_document_publish_diagnostics( + types.PublishDiagnosticsParams( + uri=uri, + version=version, + diagnostics=diagnostics, + ) + ) if __name__ == "__main__": diff --git a/examples/servers/pull_diagnostics.py b/examples/servers/pull_diagnostics.py index 637f347f..b33e77fe 100644 --- a/examples/servers/pull_diagnostics.py +++ b/examples/servers/pull_diagnostics.py @@ -26,12 +26,13 @@ This server scans a document for sums e.g. ``1 + 2 = 3``, highlighting any that are either missing answers (warnings) or incorrect (errors). """ + import logging import re from lsprotocol import types -from pygls.server import LanguageServer +from pygls.lsp.server import LanguageServer from pygls.workspace import TextDocument ADDITION = re.compile(r"^\s*(\d+)\s*\+\s*(\d+)\s*=\s*(\d+)?$") diff --git a/examples/servers/rename.py b/examples/servers/rename.py index c27314f4..e0c1fc29 100644 --- a/examples/servers/rename.py +++ b/examples/servers/rename.py @@ -29,13 +29,14 @@ a real server would have to check to make sure it only renames symbols in the relevant scope. """ + import logging import re from typing import List from lsprotocol import types -from pygls.server import LanguageServer +from pygls.lsp.server import LanguageServer from pygls.workspace import TextDocument ARGUMENT = re.compile(r"(?P\w+): (?P\w+)") diff --git a/examples/servers/semantic_tokens.py b/examples/servers/semantic_tokens.py index 54e2c3ba..7c446181 100644 --- a/examples/servers/semantic_tokens.py +++ b/examples/servers/semantic_tokens.py @@ -42,7 +42,7 @@ import attrs from lsprotocol import types -from pygls.server import LanguageServer +from pygls.lsp.server import LanguageServer from pygls.workspace import TextDocument diff --git a/examples/servers/symbols.py b/examples/servers/symbols.py index 5ff76cba..4d0a7fbf 100644 --- a/examples/servers/symbols.py +++ b/examples/servers/symbols.py @@ -33,12 +33,13 @@ .. literalinclude:: ../../../examples/servers/workspace/code.txt :language: none """ + import logging import re from lsprotocol import types -from pygls.server import LanguageServer +from pygls.lsp.server import LanguageServer from pygls.workspace import TextDocument ARGUMENT = re.compile(r"(?P\w+)(: ?(?P\w+))?")