From fe1438b06234f8e5ecd4caa7eedfeec585b6f77c Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 14 Apr 2024 22:21:34 +0200 Subject: Adding upstream version 1.3.0. Signed-off-by: Daniel Baumann --- examples/servers/.vscode/launch.json | 20 ++ examples/servers/.vscode/settings.json | 21 ++ examples/servers/code_actions.py | 74 ++++++ examples/servers/inlay_hints.py | 116 +++++++++ examples/servers/json_server.py | 387 ++++++++++++++++++++++++++++ examples/servers/workspace/Untitled-1.ipynb | 44 ++++ examples/servers/workspace/sums.txt | 7 + examples/servers/workspace/test.json | 3 + 8 files changed, 672 insertions(+) create mode 100644 examples/servers/.vscode/launch.json create mode 100644 examples/servers/.vscode/settings.json create mode 100644 examples/servers/code_actions.py create mode 100644 examples/servers/inlay_hints.py create mode 100644 examples/servers/json_server.py create mode 100644 examples/servers/workspace/Untitled-1.ipynb create mode 100644 examples/servers/workspace/sums.txt create mode 100644 examples/servers/workspace/test.json (limited to 'examples/servers') diff --git a/examples/servers/.vscode/launch.json b/examples/servers/.vscode/launch.json new file mode 100644 index 0000000..f124186 --- /dev/null +++ b/examples/servers/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + "configurations": [ + { + "name": "pygls: Debug Server", + "type": "python", + "request": "attach", + "connect": { + "host": "${config:pygls.server.debugHost}", + "port": "${config:pygls.server.debugPort}" + }, + "pathMappings": [ + { + "localRoot": "${workspaceFolder}", + "remoteRoot": "." + } + ], + "justMyCode": false + } + ] +} diff --git a/examples/servers/.vscode/settings.json b/examples/servers/.vscode/settings.json new file mode 100644 index 0000000..c2b0d23 --- /dev/null +++ b/examples/servers/.vscode/settings.json @@ -0,0 +1,21 @@ +{ + // Uncomment to override Python interpreter used. + // "pygls.server.pythonPath": "/path/to/python", + "pygls.server.debug": false, + // "pygls.server.debugHost": "localhost", + // "pygls.server.debugPort": 5678, + "pygls.server.launchScript": "json_server.py", + "pygls.trace.server": "off", + "pygls.client.documentSelector": [ + { + "scheme": "file", + "language": "json" + } + // Uncomment to use code_actions or inlay_hints servers + // { + // "scheme": "file", + // "language": "plaintext" + // } + ], + // "pygls.jsonServer.exampleConfiguration": "some value here", +} diff --git a/examples/servers/code_actions.py b/examples/servers/code_actions.py new file mode 100644 index 0000000..ec0382e --- /dev/null +++ b/examples/servers/code_actions.py @@ -0,0 +1,74 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ +import re +from pygls.server import LanguageServer +from lsprotocol.types import ( + TEXT_DOCUMENT_CODE_ACTION, + CodeAction, + CodeActionKind, + CodeActionOptions, + CodeActionParams, + Position, + Range, + TextEdit, + WorkspaceEdit, +) + + +ADDITION = re.compile(r"^\s*(\d+)\s*\+\s*(\d+)\s*=(?=\s*$)") +server = LanguageServer("code-action-server", "v0.1") + + +@server.feature( + TEXT_DOCUMENT_CODE_ACTION, + CodeActionOptions(code_action_kinds=[CodeActionKind.QuickFix]), +) +def code_actions(params: CodeActionParams): + items = [] + document_uri = params.text_document.uri + document = server.workspace.get_document(document_uri) + + start_line = params.range.start.line + end_line = params.range.end.line + + lines = document.lines[start_line : end_line + 1] + for idx, line in enumerate(lines): + match = ADDITION.match(line) + if match is not None: + range_ = Range( + start=Position(line=start_line + idx, character=0), + end=Position(line=start_line + idx, character=len(line) - 1), + ) + + left = int(match.group(1)) + right = int(match.group(2)) + answer = left + right + + text_edit = TextEdit(range=range_, new_text=f"{line.strip()} {answer}!") + + action = CodeAction( + title=f"Evaluate '{match.group(0)}'", + kind=CodeActionKind.QuickFix, + edit=WorkspaceEdit(changes={document_uri: [text_edit]}), + ) + items.append(action) + + return items + + +if __name__ == "__main__": + server.start_io() diff --git a/examples/servers/inlay_hints.py b/examples/servers/inlay_hints.py new file mode 100644 index 0000000..e9924dd --- /dev/null +++ b/examples/servers/inlay_hints.py @@ -0,0 +1,116 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ +import re +from typing import Optional + +from lsprotocol import types + +from pygls.server import LanguageServer + +NUMBER = re.compile(r"\d+") +COMMENT = re.compile(r"^#$") + + +server = LanguageServer( + name="inlay-hint-server", + version="v0.1", + notebook_document_sync=types.NotebookDocumentSyncOptions( + notebook_selector=[ + types.NotebookDocumentSyncOptionsNotebookSelectorType2( + cells=[ + types.NotebookDocumentSyncOptionsNotebookSelectorType2CellsType( + language="python" + ) + ] + ) + ] + ), +) + + +def parse_int(chars: str) -> Optional[int]: + try: + return int(chars) + except Exception: + return None + + +@server.feature(types.TEXT_DOCUMENT_INLAY_HINT) +def inlay_hints(params: types.InlayHintParams): + items = [] + document_uri = params.text_document.uri + document = server.workspace.get_text_document(document_uri) + + start_line = params.range.start.line + end_line = params.range.end.line + + lines = document.lines[start_line : end_line + 1] + for lineno, line in enumerate(lines): + match = COMMENT.match(line) + if match is not None: + nb = server.workspace.get_notebook_document(cell_uri=document_uri) + if nb is not None: + idx = 0 + for idx, cell in enumerate(nb.cells): + if cell.document == document_uri: + break + + items.append( + types.InlayHint( + label=f"notebook: {nb.uri}, cell {idx+1}", + kind=types.InlayHintKind.Type, + padding_left=False, + padding_right=True, + position=types.Position(line=lineno, character=match.end()), + ) + ) + + for match in NUMBER.finditer(line): + if not match: + continue + + number = parse_int(match.group(0)) + if number is None: + continue + + binary_num = bin(number).split("b")[1] + items.append( + types.InlayHint( + label=f":{binary_num}", + kind=types.InlayHintKind.Type, + padding_left=False, + padding_right=True, + position=types.Position(line=lineno, character=match.end()), + ) + ) + + return items + + +@server.feature(types.INLAY_HINT_RESOLVE) +def inlay_hint_resolve(hint: types.InlayHint): + try: + n = int(hint.label[1:], 2) + hint.tooltip = f"Binary representation of the number: {n}" + except Exception: + pass + + return hint + + +if __name__ == "__main__": + server.start_io() diff --git a/examples/servers/json_server.py b/examples/servers/json_server.py new file mode 100644 index 0000000..98905e4 --- /dev/null +++ b/examples/servers/json_server.py @@ -0,0 +1,387 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ +import argparse +import asyncio +import json +import re +import time +import uuid +from json import JSONDecodeError +from typing import Optional + +from lsprotocol import types as lsp + +from pygls.server import LanguageServer + +COUNT_DOWN_START_IN_SECONDS = 10 +COUNT_DOWN_SLEEP_IN_SECONDS = 1 + + +class JsonLanguageServer(LanguageServer): + CMD_COUNT_DOWN_BLOCKING = "countDownBlocking" + CMD_COUNT_DOWN_NON_BLOCKING = "countDownNonBlocking" + CMD_PROGRESS = "progress" + CMD_REGISTER_COMPLETIONS = "registerCompletions" + CMD_SHOW_CONFIGURATION_ASYNC = "showConfigurationAsync" + CMD_SHOW_CONFIGURATION_CALLBACK = "showConfigurationCallback" + CMD_SHOW_CONFIGURATION_THREAD = "showConfigurationThread" + CMD_UNREGISTER_COMPLETIONS = "unregisterCompletions" + + CONFIGURATION_SECTION = "pygls.jsonServer" + + def __init__(self, *args): + super().__init__(*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=[":"]), +) +def completions(params: Optional[lsp.CompletionParams] = None) -> lsp.CompletionList: + """Returns completion items.""" + return lsp.CompletionList( + is_incomplete=False, + items=[ + lsp.CompletionItem(label='"'), + lsp.CompletionItem(label="["), + lsp.CompletionItem(label="]"), + lsp.CompletionItem(label="{"), + lsp.CompletionItem(label="}"), + ], + ) + + +@json_server.command(JsonLanguageServer.CMD_COUNT_DOWN_BLOCKING) +def count_down_10_seconds_blocking(ls, *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}") + 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): + """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}") + 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_SEMANTIC_TOKENS_FULL, + lsp.SemanticTokensLegend(token_types=["operator"], token_modifiers=[]), +) +def semantic_tokens(ls: JsonLanguageServer, params: lsp.SemanticTokensParams): + """See https://microsoft.github.io/language-server-protocol/specification#textDocument_semanticTokens + for details on how semantic tokens are encoded.""" + + TOKENS = re.compile('".*"(?=:)') + + uri = params.text_document.uri + doc = ls.workspace.get_document(uri) + + last_line = 0 + last_start = 0 + + data = [] + + for lineno, line in enumerate(doc.lines): + last_start = 0 + + for match in TOKENS.finditer(line): + start, end = match.span() + data += [(lineno - last_line), (start - last_start), (end - start), 0, 0] + + last_line = lineno + last_start = start + + return lsp.SemanticTokens(data=data) + + +@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) + # Begin + ls.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(): + # ... and stop the computation if client cancelled + return + ls.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")) + + +@json_server.command(JsonLanguageServer.CMD_REGISTER_COMPLETIONS) +async def register_completions(ls: JsonLanguageServer, *args): + """Register completions method on the client.""" + params = lsp.RegistrationParams( + registrations=[ + lsp.Registration( + id=str(uuid.uuid4()), + method=lsp.TEXT_DOCUMENT_COMPLETION, + register_options={"triggerCharacters": "[':']"}, + ) + ] + ) + 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 + ) + + +@json_server.command(JsonLanguageServer.CMD_SHOW_CONFIGURATION_ASYNC) +async def show_configuration_async(ls: JsonLanguageServer, *args): + """Gets exampleConfiguration from the client settings using coroutines.""" + try: + config = await ls.get_configuration_async( + lsp.WorkspaceConfigurationParams( + items=[ + lsp.ConfigurationItem( + scope_uri="", section=JsonLanguageServer.CONFIGURATION_SECTION + ) + ] + ) + ) + + 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_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( + lsp.WorkspaceConfigurationParams( + items=[ + lsp.ConfigurationItem( + scope_uri="", section=JsonLanguageServer.CONFIGURATION_SECTION + ) + ] + ), + _config_callback, + ) + + +@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 thread pool.""" + try: + config = ls.get_configuration( + lsp.WorkspaceConfigurationParams( + 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 + ) + + +def add_arguments(parser): + parser.description = "simple json server example" + + parser.add_argument("--tcp", action="store_true", help="Use TCP server") + parser.add_argument("--ws", action="store_true", help="Use WebSocket server") + parser.add_argument("--host", default="127.0.0.1", help="Bind to this address") + parser.add_argument("--port", type=int, default=2087, help="Bind to this port") + + +def main(): + parser = argparse.ArgumentParser() + add_arguments(parser) + args = parser.parse_args() + + if args.tcp: + json_server.start_tcp(args.host, args.port) + elif args.ws: + json_server.start_ws(args.host, args.port) + else: + json_server.start_io() + + +if __name__ == "__main__": + main() diff --git a/examples/servers/workspace/Untitled-1.ipynb b/examples/servers/workspace/Untitled-1.ipynb new file mode 100644 index 0000000..d45e746 --- /dev/null +++ b/examples/servers/workspace/Untitled-1.ipynb @@ -0,0 +1,44 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "12\n", + "#" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "mykey": 3 + }, + "outputs": [], + "source": [ + "#" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.11.4" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/servers/workspace/sums.txt b/examples/servers/workspace/sums.txt new file mode 100644 index 0000000..fcbc410 --- /dev/null +++ b/examples/servers/workspace/sums.txt @@ -0,0 +1,7 @@ +1 + 1 = + + +2 + 3 = + + +6 + 6 = diff --git a/examples/servers/workspace/test.json b/examples/servers/workspace/test.json new file mode 100644 index 0000000..21da3b2 --- /dev/null +++ b/examples/servers/workspace/test.json @@ -0,0 +1,3 @@ +{ + "key": "value" +} -- cgit v1.2.3