From bc08fc1ebbad315584e42b1073909a758a107916 Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Sun, 16 Aug 2020 03:29:56 -0700 Subject: [PATCH 01/32] Add a code action to open documentation of system symbols * Update doc with the code action screenshot. * Re-enable openRef command. * Reorder the specification data types. * Use ConstructType instead of applying heads. --- README.md | 198 ++++++++++++--------- src/WolframLanguageServer/Server.wl | 41 ++++- src/WolframLanguageServer/Specification.wl | 170 +++++++++++------- src/WolframLanguageServer/TextDocument.wl | 52 +++++- src/WolframLanguageServer/Token.wl | 2 +- 5 files changed, 307 insertions(+), 156 deletions(-) diff --git a/README.md b/README.md index 0661f39..ab4efc8 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,14 @@ - [Installation](#installation) - [Run the Server](#run-the-server) - [Features](#features) + - [DocumentSymbol](#documentsymbol) + - [Hover](#hover) + - [Completion](#completion) + - [Diagnostics](#diagnostics) + - [Definition / References / Document Highlight](#definition--references--document-highlight) + - [Code Action](#code-action) + - [Document Color / Color Presentation](#document-color--color-presentation) + - [Notes](#notes) - [Contribute](#contribute) - [Design Principles](#design-principles) - [Todo list](#todo-list) @@ -24,17 +32,18 @@ **Wolfram Language Server (WLServer)** is an implementation of the Microsoft's [Language Server Protocol (LSP)](https://microsoft.github.io/language-server-protocol) for [Wolfram -Language](http://www.wolfram.com/language). This server is -implemented in Wolfram Language itself. +Language](http://www.wolfram.com/language). This server is implemented in +Wolfram Language itself. -Our current goal is to provide the experience as good as the Mathematica FrontEnd -with addition power from the editor. +Our current goal is to provide the experience as good as the Mathematica +FrontEnd with addition power from the editor. -We have provided the client-side code for VS Code [here](https://github.com/kenkangxgwe/vscode-lsp-wl), which is based on some slight -modifications of [Microsoft's LSP +We have provided the client-side code for VS Code +[here](https://github.com/kenkangxgwe/vscode-lsp-wl), which is based on some +slight modifications of [Microsoft's LSP example](https://github.com/Microsoft/vscode-extension-samples/tree/master/lsp-sample). -If you are using other editors supporting LSP, some adaptation to the -client would certainly work too. +If you are using other editors supporting LSP, some adaptation to the client +would certainly work too. ## Installation @@ -47,8 +56,8 @@ client would certainly work too. git clone https://github.com/kenkangxgwe/lsp-wl.git ``` -2. Install the dependent paclets with the correct versions (currently 1.0) -from the Wolfram kernel / Mathematica. +2. Install the dependent paclets with the correct versions (currently 1.0) from +the Wolfram kernel / Mathematica. (_This will cost some time for the first time_) : ``` mathematica PacletInstall["CodeParser"] @@ -56,11 +65,12 @@ from the Wolfram kernel / Mathematica. ``` 3. Install the client. Currently, we provide the VS Code extension on [Visual -Studio Marketplace: Wolfram Language Server](https://marketplace.visualstudio.com/items?itemName=lsp-wl.lsp-wl-client) +Studio Marketplace: Wolfram Language +Server](https://marketplace.visualstudio.com/items?itemName=lsp-wl.lsp-wl-client) 4. You may also want to install -[GitLink](https://github.com/WolframResearch/GitLink) packet in order to -check for updates. +[GitLink](https://github.com/WolframResearch/GitLink) packet in order to check +for updates. ## Run the Server @@ -78,11 +88,11 @@ The posible arguments for the server are - `--help, -h` to print help information. - `--socket=port` to assign the port to which the server will connect. (Default: `6536`) -- `--tcp-server=port` to assign the port at which the server will start. (Default: -`6536`) +- `--tcp-server=port` to assign the port at which the server will start. +(Default: `6536`) - `--pipe=pipename` to specify the pipe name for the server to connect to. -- `--log=level, -l level` to specify the logging level of the server. - (Levels: `error`, `warn`, `info`, `debug`. Default: `info`) +- `--log=level, -l level` to specify the logging level of the server. (Levels: + `error`, `warn`, `info`, `debug`. Default: `info`) - `--test, -t` to run the unit test for the server. If you want to run the server from Mathematica you can use the following code. @@ -99,85 +109,109 @@ This is a good way to see the results from the unit tests. ## Features -- **DocumentSymbol:** You may typeset your package in the same way that - Mathematica FrontEnd handles it: a cell begins with two lines of comments, - where the first line specifies the style of the cell and the second line names it. - So you may get the outline structure of the file. - - ``` mathematica - (* ::Title:: *) - (*Title of the file*) +### DocumentSymbol - (* ::Section:: *) - (*Section 1*) - ``` - - ![documentSymbol](images/documentSymbol.png) +You may typeset your package in the same way that Mathematica FrontEnd handles +it: a cell begins with two lines of comments, where the first line specifies the +style of the cell and the second line names it. So you may get the outline +structure of the file. + +``` mathematica +(* ::Title:: *) +(*Title of the file*) + +(* ::Section:: *) +(*Section 1*) +``` + +![documentSymbol](images/documentSymbol.png) + +### Hover + +Provide documentations for functions and variables from the ``System` `` +context, such as `String` and `$Path`, the `MessageName` and the special +numerical literals with `^^` or `*^`. -- **Hover:** Provide documentations for functions and variables from the - ``System` `` context, such as `String` and `$Path`, the `MessageName` and - the special numerical literals with `^^` or `*^`. +![hover](images/hover.png) - ![hover](images/hover.png) +### Completion -- **Completion:** The completion is shown by the client automatically. - Functions and system variables from the ``System` `` context that matches the - input would be displayed. To enter an unicode character, you may use the - leader key \\ followed by the alias just like esc in - Wolfram FrondEnd. E.g., `a` in the FrontEnd is input as `\a` in the - editor and the server will show you the available completions. +The completion is shown by the client automatically. Functions and system +variables from the ``System` `` context that matches the input would be +displayed. To enter an unicode character, you may use the leader key +\\ followed by the alias just like esc in Wolfram +FrondEnd. E.g., `a` in the FrontEnd is input as `\a` in the editor and the +server will show you the available completions. - ![completion-unicode](images/completion_alias.png) +![completion-unicode](images/completion_alias.png) -- **Completion Resolve:** Further information (such as documentation) would be - provided for the items in the list. +**Completion Resolve:** Further information (such as documentation) would be +provided for the items in the list. - ![completion](images/completion.png) +![completion](images/completion.png) -- **Diagnostics:** Syntax error would be underlined. This feature is powered - by [CodeParser](https://github.com/WolframResearch/codeparser) and - [CodeInspector](https://github.com/WolframResearch/codeinspector) paclets, - thank you [@bostick](https://github.com/bostick). +### Diagnostics - ![diagnostics](images/diagnostics.png) +Syntax error would be underlined. This feature is powered by +[CodeParser](https://github.com/WolframResearch/codeparser) and +[CodeInspector](https://github.com/WolframResearch/codeinspector) paclets, thank +you [@bostick](https://github.com/bostick). -- **Definition / References / DocumentHighlight:** It is now able to look up the - definition and references of a local variable in a scope such as `Module` or - pattern rules. +![diagnostics](images/diagnostics.png) - ![references](images/references.png) +### Definition / References / Document Highlight -- **Document Color / Color Presentation:** Both Named Colors and - Color Models with constant parameters are able to show and modify. - (_Experimental, may have performance issues._) +It is now able to look up the definition and references of a local variable in a +scope such as `Module` or pattern rules. - ![documentColor](images/documentColor.png) +![references](images/references.png) -This is an early release, so more features are on the way. Notice that, -syntax highlight will not be provided as long as it is excluded in the LSP, -but I believe there are plenty of good Mathematica highlighter available for -your editor. +### Code Action -Here is a full list of [LSP features](https://microsoft.github.io/language-server-protocol/specification). +Code action is now able to, + +- Open the documentation of system symbols in Mathematica (Not available for + Wolfram Engine). + ![documentation](images/codeActionSymbolDocumentation.png) + + +### Document Color / Color Presentation + +Both Named Colors and +Color Models with constant parameters are able to show and modify. +(_Experimental, may have performance issues._) + +![documentColor](images/documentColor.png) + +### Notes + +The project is under development, so more features are on the way. Notice that, +**syntax highlight** will not be provided as long as it is excluded from the +LSP, but I believe there are already plenty of good Mathematica highlighters +available for your editor. + +Here is a full list of [LSP +features](https://microsoft.github.io/language-server-protocol/specification). ## Contribute ### Design Principles 1. The files are located according to its context name. The `init.wls` is the - entry script that parses the commandline arguments, loads packages - and starts the server. + entry script that parses the commandline arguments, loads packages and starts + the server. This is intended to be different from a paclet, since it is not + intended to be normally used inside Mathematica / Wolfram Kernel. -2. We implemented an stateless server in ``WolframLanguageServer`Server` `` that - will parse and handle the messages. +2. We implemented a *stateless* (by passing the state around :)) server in + ``WolframLanguageServer`Server` `` that will parse and handle messages. -3. ``DataType` `` is a simple type system now extracted as a independent - package in the [Matypetica](https://github.com/kenkangxgwe/Matypetica) - library that supports pattern test on every field of a class. The operations - on the objects are designed to be immutable. +3. ``DataType` `` is a simple type system extracted as an independent package in + the [Matypetica](https://github.com/kenkangxgwe/Matypetica) library that + supports pattern test on every field of a data structure. The operations on + the data objects are designed to be immutable. -4. ``WolframLanguageServer`Test`* `` stores the unit tests for some of - the functions. +4. ``WolframLanguageServer`Test`* `` stores the unit tests for some of the + functions which are integrated into GitHub Action. ### Todo list @@ -188,8 +222,8 @@ It will be nice if you want to make a contribution to the following topic. but it fails to send responses back to the client. * It will be helpful to implement a stdio channel, ~so that the Mathematica - earlier than 11.2 will also be supported,~ but it is really hard to expose - the `stdin` channel. Hope this will be dicussed in future release of Wolfram + earlier than 11.2 will also be supported,~ but it is really hard to expose the + `stdin` channel. Hope this will be dicussed in future release of Wolfram kernel. * More editor clients are needed. You can feel free to open a repository and @@ -197,10 +231,12 @@ It will be nice if you want to make a contribution to the following topic. released. * Thanks to [CodeParser](https://github.com/WolframResearch/codeparser) and - [CodeInspector](https://github.com/WolframResearch/codeinspector) paclets, - we are able to parse the code and extract useful information. If you have - an idea about how to use these fantastic syntax tools to help the language - server add more features, please send us issues or pull requests. + [CodeInspector](https://github.com/WolframResearch/codeinspector) paclets, we + are able to parse the code and extract useful information. Please consider to + contribute to them as well. + + If you have ideas about how to use these fantastic language tools to help the + language server with more features, please send us issues or pull requests. If you want to help us with this project, feel free to fork and create a pull request. Do not forget to add unit tests if possible. @@ -208,9 +244,9 @@ request. Do not forget to add unit tests if possible. ## Donations :dollar: If you really like this project, please donate to us! **$5 (or equivalently -๏ฟฅ35)**. A cup of coffee :coffee: would certainly -brighten our day! Your donation would be the motivation for us to move forward, -thanks in advance :smile:. +๏ฟฅ35)**. A cup of coffee :coffee: would certainly brighten our day! Your +donation would be the motivation for us to move forward, thanks in advance +:smile:. - Paypal: qwe95123@126.com - Alipay (With QRCode): 13916018006 diff --git a/src/WolframLanguageServer/Server.wl b/src/WolframLanguageServer/Server.wl index 07e8220..5a5e1c9 100644 --- a/src/WolframLanguageServer/Server.wl +++ b/src/WolframLanguageServer/Server.wl @@ -89,13 +89,14 @@ ServerCapabilities = <| "definitionProvider" -> True, "referencesProvider" -> True, "documentSymbolProvider" -> True, + "codeActionProvider" -> True, "documentHighlightProvider" -> True, "colorProvider" -> True, - (* "executeCommandProvider" -> <| + "executeCommandProvider" -> <| "commands" -> { "openRef" } - |>, *) + |>, Nothing |> @@ -847,6 +848,12 @@ handleRequest["workspace/executeCommand", msg_, state_] := With[ Replace[command, { "dap-wl.runfile" -> ( LogInfo[StringJoin["executing ", command, "with arguments: ", ToString[args]]] + ), + "openRef" -> ( + args + // First + // SystemOpen + // UsingFrontEnd ) }]; @@ -1178,6 +1185,32 @@ getCache[method:"textDocument/documentSymbol", msg_, state_WorkState] := ( ) +(* ::Subsection:: *) +(*textDocument/codeAction*) + + +handleRequest["textDocument/codeAction", msg_, state_] := With[ + { + doc = state["openedDocs"][msg["params"]["textDocument"]["uri"]], + range = ConstructType[msg["params"]["range"], LspRange] + }, + + sendResponse[state["client"], <| + "id" -> msg["id"], + "result" -> ToAssociation[ + (* Command[<| + "title" -> "Open Documentation", + "command" -> "openRef", + "arguments" -> {"C:/Program Files/Wolfram Research/Mathematica/12.1/Documentation/English/System/ReferencePages/Symbols/List.nb"} + |>] *) + GetCodeActionsInRange[doc, range] + ] + |>]; + + {"Continue", state} +] + + (* ::Subsection:: *) (*textDocument/documentColor*) @@ -1237,8 +1270,8 @@ getScheduleTaskParameter[method:"textDocument/documentColor", msg_, state_WorkSt handleRequest["textDocument/colorPresentation", msg_, state_] := With[ { doc = state["openedDocs"][msg["params"]["textDocument"]["uri"]], - color = msg["params"]["color"] // LspColor, - range = msg["params"]["range"] // LspRange + color = ConstructType[msg["params"]["color"], LspColor], + range = ConstructType[msg["params"]["range"], LspRange] }, sendResponse[state["client"], <| diff --git a/src/WolframLanguageServer/Specification.wl b/src/WolframLanguageServer/Specification.wl index 5e134d0..a0e1d8a 100644 --- a/src/WolframLanguageServer/Specification.wl +++ b/src/WolframLanguageServer/Specification.wl @@ -12,22 +12,23 @@ ClearAll[Evaluate[Context[] <> "*"]]; LspPosition::usage = "is type of Position interface in LSP." LspRange::usage = "is type of Range interface in LSP." -LspLocation::usage = "is type of Location Interface in LSP." -TextEdit::usage = "is type of TextEdit Interface in LSP." -TextDocumentItem::usage = "is type of TextDocumentItem in LSP." +Location::usage = "is type of Location interface in LSP." +Command::usage = "is type of Command interface in LSP." +TextEdit::usage = "is type of TextEdit interface in LSP." +TextDocumentItem::usage = "is type of TextDocumentItem interface in LSP." +MarkupContent::usage = "is the type of MarkupContent interface in LSP." TextDocumentContentChangeEvent::usage = "is an event describing a change to a text document. If range and rangeLength are omitted \ the new text is considered to be the full content of the document." -MarkupContent::usage = "is the type of MarkupContent interface in LSP." +Diagnostic::usage = "is the type of Diagnostic interface in LSP." +DiagnosticRelatedInformation::usage = "is the type of DiagnosticRelatedInformation interface in LSP." Hover::usage = "is the type of Hover interface in LSP." SignatureHelp::usage = "is the type of SignatureHelp interface in LSP." SignatureInformation::usage = "is the type of SignatureInformation interface in LSP." ParameterInformation::usage = "is the type of ParameterInformation interface in LSP." DocumentSymbol::usage = "is the type of DocumentSymbol interface in LSP." -Diagnostic::usage = "is the type of Diagnostic interface in LSP." -DiagnosticRelatedInformation::usage = "is the type of DiagnosticRelatedInformation interface in LSP." CompletionItem::usage = "is the type of CompletionItem interface in LSP." -Location::usage = "is the type of Location interface in LSP." DocumentHighlight ::usage = "is the type of Location interface in LSP." +LspCodeAction::usage = "is the type of CodeAction interface in LSP." ColorInformation::usage = "is the type of ColorInformation interface in LSP." LspColor::usage = "is the type of Color interface in LSP." ColorPresentation::usage = "is the type of ColorPresentation interface in LSP." @@ -42,20 +43,6 @@ DocumentUri = String (* ::Section:: *) (*Enum Type*) -TextDocumentSyncKind = <| - "None" -> 0, - "Full" -> 1, - "Incremental" -> 2 -|> - -DiagnosticSeverity = <| - "Error" -> 1, - "Warning" -> 2, - "Information" -> 3, - "Hint" -> 4 -|> - - ErrorCodes = <| (* Defined by JSON RPC *) "ParseError" -> -32700, @@ -72,11 +59,23 @@ ErrorCodes = <| "ContentModified" -> -32801 |> -InsertTextFormat = <| - "PlainText" -> 1, - "Snippet" -> 2 +MarkupKind = <| + "PlainText" -> "plaintext", + "Markdown" -> "markdown" |> +TextDocumentSyncKind = <| + "None" -> 0, + "Full" -> 1, + "Incremental" -> 2 +|> + +DiagnosticSeverity = <| + "Error" -> 1, + "Warning" -> 2, + "Information" -> 3, + "Hint" -> 4 +|> CompletionTriggerKind = <| "Invoked" -> 1, @@ -84,6 +83,10 @@ CompletionTriggerKind = <| "TriggerForIncompleteCompletions" -> 3 |> +InsertTextFormat = <| + "PlainText" -> 1, + "Snippet" -> 2 +|> CompletionItemKind = <| "Text" -> 1, @@ -113,7 +116,6 @@ CompletionItemKind = <| "TypeParameter" -> 25 |> - SymbolKind = <| "File" -> 1, "Module" -> 2, @@ -143,18 +145,22 @@ SymbolKind = <| "TypeParameter" -> 26 |> - -MarkupKind = <| - "PlainText" -> "plaintext", - "Markdown" -> "markdown" -|> - DocumentHighlightKind = <| "Text" -> 1, "Read" -> 2, "Write" -> 3 |> +CodeActionKind = <| + "Empty" -> "", + "QuickFix" -> "quickfix", + "Refactor" -> "refactor", + "RefactorExtract" -> "refactor.extract", + "RefactorInline" -> "refactor.inline", + "RefactorRewrite" -> "refactor.rewrite", + "Source" -> "source", + "SourceOrganizeImports" -> "source.orgainizeImports" +|> (* ::Section:: *) (*Constants*) @@ -171,6 +177,11 @@ Needs["DataType`"] (* ::Section:: *) (*Server Communication Related Type*) + +(* ::Subsection:: *) +(*Basic Structures*) + + DeclareType[LspPosition, <| "line" -> _Integer, "character" -> _Integer @@ -181,11 +192,17 @@ DeclareType[LspRange, <| "end" -> _LspPosition |>] -DeclareType[LspLocation, <| +DeclareType[Location, <| "uri" -> _DocumentUri, "range" -> _LspRange |>] +DeclareType[Command, <| + "title" -> _String, + "command" -> _String, + "arguments" -> _List +|>] + DeclareType[TextEdit, <| "range" -> _LspRange, "newText" -> _String @@ -198,15 +215,57 @@ DeclareType[TextDocumentItem, <| "text" -> _String |>] +DeclareType[MarkupContent, <| + "kind" -> _?(MemberQ[MarkupKind, #]&), + "value" -> _String +|>] + + +(* ::Subsection:: *) +(*Text Synchronization*) + + DeclareType[TextDocumentContentChangeEvent, <| "range" -> _LspRange, "rangeLength" -> _Integer, "text" -> _String |>] -DeclareType[MarkupContent, <| - "kind" -> _String, - "value" -> _String + +(* ::Subsection:: *) +(*Diagnostics*) + + +DeclareType[Diagnostic, <| + "range" -> _LspRange, + "severity" -> _?(MemberQ[DiagnosticSeverity, #]&), + "code" -> _Integer|_String, + "source" -> _String, + "message" -> _String, + "relatedInformation" -> {___DiagnosticRelatedInformation} +|>] + +DeclareType[DiagnosticRelatedInformation, <| + "location" -> _Location, + "message" -> _String +|>] + + +(* ::Subsection:: *) +(*Language Features*) + + +DeclareType[CompletionItem, <| + "label" -> _String, + "kind" -> _Integer, + "detail" -> _String, + "documentation" -> _String | _MarkupContent, + "preselect" -> _?BooleanQ, + "filterText" -> _String, + "insertText" -> _String, + "insertTextFormat" -> _?(MemberQ[InsertTextFormat, #]&), + "textEdit" -> _TextEdit, + "commitCharacters" -> {___String} |>] DeclareType[Hover, <| @@ -234,51 +293,24 @@ DeclareType[ParameterInformation, <| DeclareType[DocumentSymbol, <| "name" -> _String, "detail" -> _String, - "kind" -> _Integer, + "kind" -> _?(MemberQ[SymbolKind, #]&), "deprecated" -> _?BooleanQ, "range" -> _LspRange, "selectionRange" -> _LspRange, "children" -> {___DocumentSymbol} |>] -DeclareType[Diagnostic, <| - "range" -> _LspRange, - "severity" -> _Integer, - "code" -> _Integer|_String, - "source" -> _String, - "message" -> _String, - "relatedInformation" -> {___DiagnosticRelatedInformation} -|>] - -DeclareType[DiagnosticRelatedInformation, <| - "location" -> _LspLocation, - "message" -> _String -|>] - - -DeclareType[CompletionItem, <| - "label" -> _String, - "kind" -> _Integer, - "detail" -> _String, - "documentation" -> _String | _MarkupContent, - "preselect" -> _?BooleanQ, - "filterText" -> _String, - "insertText" -> _String, - "insertTextFormat" -> _Integer, - "textEdit" -> _TextEdit, - "commitCharacters" -> {___String} -|>] - -DeclareType[Location, <| - "uri" -> DocumentUri, - "range" -> _LspRange -|>] - DeclareType[DocumentHighlight, <| "range" -> _LspRange, "kind" -> _Integer |>] +DeclareType[LspCodeAction, <| + "title" -> _String, + "kind" -> _?(MemberQ[CodeActionKind, #]&), + "command" -> _Command +|>] + DeclareType[ColorInformation, <| "range" -> _LspRange, "color" -> _LspColor diff --git a/src/WolframLanguageServer/TextDocument.wl b/src/WolframLanguageServer/TextDocument.wl index 0c7b59f..b282bab 100644 --- a/src/WolframLanguageServer/TextDocument.wl +++ b/src/WolframLanguageServer/TextDocument.wl @@ -23,6 +23,7 @@ FindDefinitions::usage = "FindDefinitions[doc_TextDocument, pos_LspPosition] giv FindReferences::usage = "FindReferences[doc_TextDocument, pos_LspPosition, o:OptionsPattern[]] gives the references of the symbol at the position." FindDocumentHighlight::usage = "FindDocumentHighlight[doc_TextDocument, pos_LspPosition] gives a list of DocumentHighlight." FindAllCodeRanges::usage = "FindAllCodeRanges[doc_TextDocument] returns a list of LspRange which locate all the code ranges (cells) in the given doc." +GetCodeActionsInRange::usage = "GetCodeActionsInRange[doc_TextDocument, range_LspRange] returns a list of CodeAction related to specified range." FindDocumentColor::usage = "FindDocumentColor[doc_TextDocument] gives a list of colors in the text document." GetColorPresentation::usage = "GetColorPresentation[doc_TextDocument, color_LspColor, range_LspRange] gives the RGBColor presentation of the color." @@ -1247,7 +1248,56 @@ FindTopLevelSymbols[node_, name_String] := ( ) -(* ::Subsection:: *) +(* ::Section:: *) +(*CodeAction*) + + +SymbolDocumentationPath = FileNameJoin[{$InstallationDirectory, "Documentation", "English", "System", "ReferencePages", "Symbols"}] + + +GetCodeActionsInRange[doc_TextDocument, range_LspRange] := With[ + { + startPos = {range["start"]["line"] + 1, range["start"]["character"] + 1}, + endPos = {range["end"]["line"] + 1, range["end"]["character"]} + }, + + GetCodeRangeAtPosition[doc, range["start"]] + // Replace[lineRange:{_Integer, _Integer} :> ( + CellToAST[doc, lineRange] + // (ast \[Function] ( + FirstCase[ + ast, + AstPattern["Token"][tokenString_]?(( + (* The token node overlaps the range *) + CompareNodePosition[#, startPos, -1] >= 0 && + CompareNodePosition[#, endPos, 1] <= 0 + )&) :> ( + FileNameJoin[{SymbolDocumentationPath, tokenString <> ".nb"}] + // If[FileExistsQ[#], + LspCodeAction[<| + "title" -> "Documentation: " <> tokenString, + "kind" -> CodeActionKind["Empty"], + "command" -> <| + "title" -> "Documentation: " <> tokenString, + "command" -> "openRef", + "arguments" -> {#} + |> + |>], + Missing["NotFound"] + ]& + ), + Missing["NotFound"], + AstLevelspec["DataWithSource"], + Heads -> False + ] + )) + )] + // List + // DeleteMissing +] + + +(* ::Section:: *) (*DocumentColor*) diff --git a/src/WolframLanguageServer/Token.wl b/src/WolframLanguageServer/Token.wl index 3b2e153..9504aa6 100644 --- a/src/WolframLanguageServer/Token.wl +++ b/src/WolframLanguageServer/Token.wl @@ -10,7 +10,7 @@ BeginPackage["WolframLanguageServer`Token`"] ClearAll[Evaluate[Context[] <> "*"]] -TokenDocumentation::usage = "TokenDocumentation[token_String, tag_String, o] returns the documentation for input token in Markdown format. +TokenDocumentation::usage = "TokenDocumentation[token_String, tag_String, o] returns the documentation for input token in specified format. The possible options are \"Format\" -> \"plaintext\" | \"markdown\" " From dd39da3edd679276cbbac857391588456fdfba4e Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Sun, 16 Aug 2020 03:31:52 -0700 Subject: [PATCH 02/32] Upload the CodeAction screenshot --- images/codeActionSymbolDocumentation.png | Bin 0 -> 31103 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/codeActionSymbolDocumentation.png diff --git a/images/codeActionSymbolDocumentation.png b/images/codeActionSymbolDocumentation.png new file mode 100644 index 0000000000000000000000000000000000000000..c17704b8e035945c89b2eb152caa59e34327f550 GIT binary patch literal 31103 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 ue>CI^)ewpbYDP~n Date: Tue, 25 Aug 2020 03:23:24 -0700 Subject: [PATCH 03/32] Use Withs to replace constant Blocks --- src/PatternTemplate.wl | 2 +- src/WolframLanguageServer/Server.wl | 12 ++++++------ src/WolframLanguageServer/Token.wl | 5 ++++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/PatternTemplate.wl b/src/PatternTemplate.wl index 915cbd3..8847dc0 100644 --- a/src/PatternTemplate.wl +++ b/src/PatternTemplate.wl @@ -57,7 +57,7 @@ PatternTemplateObject[templatedPattern_, o:OptionsPattern[]][newPatterns___] := // PatternTemplateObject[templatedPattern] ) -PatternTemplateObject[templatedPattern_, o:OptionsPattern[]][newPatterns_Association] := Block[ +PatternTemplateObject[templatedPattern_, o:OptionsPattern[]][newPatterns_Association] := With[ { newPatternTuples = Replace[ newPatterns, diff --git a/src/WolframLanguageServer/Server.wl b/src/WolframLanguageServer/Server.wl index 5a5e1c9..01a51e5 100644 --- a/src/WolframLanguageServer/Server.wl +++ b/src/WolframLanguageServer/Server.wl @@ -450,7 +450,7 @@ StreamPattern = ("stdio"|_SocketObject); SelectClient[connection_SocketObject] := ( - SelectFirst[connection["ConnectedClients"], SocketReadyQ] (* SocketWaitNext does not work in 11.3 *) + SelectFirst[connection["ConnectedClients"], SocketReadyQ] (* SocketWaitNext does not work in 11.3 *) // (If[MissingQ[#], Pause[1]; Return[SelectClient[connection]], @@ -746,7 +746,7 @@ cacheResponse[method_String, msg_][state_WorkState] = cacheResponse[method, msg, state] -sendCachedResult[method_String, msg_, state_WorkState] := Block[ +sendCachedResult[method_String, msg_, state_WorkState] := With[ { cache = getCache[method, msg, state] }, @@ -950,7 +950,7 @@ cacheResponse[method:"textDocument/signatureHelp", msg_, state_WorkState] := Wit ] -cacheAvailableQ[method:"textDocument/signatureHelp", msg_, state_WorkState] := Block[ +cacheAvailableQ[method:"textDocument/signatureHelp", msg_, state_WorkState] := With[ { cachedTime = getCache[method, msg, state]["cachedtime"] }, @@ -1164,7 +1164,7 @@ cacheResponse[method:"textDocument/documentSymbol", msg_, state_WorkState] := Wi ] -cacheAvailableQ[method:"textDocument/documentSymbol", msg_, state_WorkState] := Block[ +cacheAvailableQ[method:"textDocument/documentSymbol", msg_, state_WorkState] := With[ { cachedTime = getCache[method, msg, state]["cachedtime"] }, @@ -1241,7 +1241,7 @@ cacheResponse[method:"textDocument/documentColor", msg_, state_WorkState] := Wit ] -cacheAvailableQ[method:"textDocument/documentColor", msg_, state_WorkState] := Block[ +cacheAvailableQ[method:"textDocument/documentColor", msg_, state_WorkState] := With[ { cachedTime = getCache[method, msg, state]["cachedTime"] }, @@ -1639,7 +1639,7 @@ doNextScheduledTask[state_WorkState] := ( Pause[0.001]; {"Continue", state} ), - task_ServerTask :> Block[ + task_ServerTask :> With[ { newState = state // ReplaceKeyBy["scheduledTasks" -> Rest] }, diff --git a/src/WolframLanguageServer/Token.wl b/src/WolframLanguageServer/Token.wl index 9504aa6..efab189 100644 --- a/src/WolframLanguageServer/Token.wl +++ b/src/WolframLanguageServer/Token.wl @@ -190,7 +190,10 @@ GetUri[token_String, tag_String] := ( tag // Replace[{ "usage" :> ( - StringJoin["[*reference*](https://reference.wolfram.com/language/ref/", token, ".html)"] + StringJoin[ + "[*reference*](https://reference.wolfram.com/language/ref/", token, ".html) " + (* "[*document*](command:editor.action.addCommentLine)" *) + ] ), _ :> ( StringJoin["[*reference*](https://reference.wolfram.com/language/ref/message/", token, "/", tag,".html)"] From 5eca6b3d304ed70358f91e68f35b61a474f32866 Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Fri, 28 Aug 2020 01:55:08 -0700 Subject: [PATCH 04/32] Auto detect default language for documentation --- src/WolframLanguageServer/TextDocument.wl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WolframLanguageServer/TextDocument.wl b/src/WolframLanguageServer/TextDocument.wl index b282bab..a3f99c3 100644 --- a/src/WolframLanguageServer/TextDocument.wl +++ b/src/WolframLanguageServer/TextDocument.wl @@ -1252,7 +1252,7 @@ FindTopLevelSymbols[node_, name_String] := ( (*CodeAction*) -SymbolDocumentationPath = FileNameJoin[{$InstallationDirectory, "Documentation", "English", "System", "ReferencePages", "Symbols"}] +SymbolDocumentationPath = FileNameJoin[{$InstallationDirectory, "Documentation", $Language, "System", "ReferencePages", "Symbols"}] GetCodeActionsInRange[doc_TextDocument, range_LspRange] := With[ From 210999920f5c4941263b87560813c08e9cfd2380 Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Sat, 29 Aug 2020 05:00:53 -0700 Subject: [PATCH 05/32] Find reference notebooks in $Path instead of $InstallationDirectory * $Language is sometimes set by frontend init.m which doesn't reveal the true language available for documentations --- src/WolframLanguageServer/TextDocument.wl | 11 ++++------- src/WolframLanguageServer/Token.wl | 5 +---- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/WolframLanguageServer/TextDocument.wl b/src/WolframLanguageServer/TextDocument.wl index a3f99c3..de96989 100644 --- a/src/WolframLanguageServer/TextDocument.wl +++ b/src/WolframLanguageServer/TextDocument.wl @@ -1252,9 +1252,6 @@ FindTopLevelSymbols[node_, name_String] := ( (*CodeAction*) -SymbolDocumentationPath = FileNameJoin[{$InstallationDirectory, "Documentation", $Language, "System", "ReferencePages", "Symbols"}] - - GetCodeActionsInRange[doc_TextDocument, range_LspRange] := With[ { startPos = {range["start"]["line"] + 1, range["start"]["character"] + 1}, @@ -1272,8 +1269,9 @@ GetCodeActionsInRange[doc_TextDocument, range_LspRange] := With[ CompareNodePosition[#, startPos, -1] >= 0 && CompareNodePosition[#, endPos, 1] <= 0 )&) :> ( - FileNameJoin[{SymbolDocumentationPath, tokenString <> ".nb"}] - // If[FileExistsQ[#], + FindFile[FileNameJoin[{"ReferencePages", "Symbols", tokenString <> ".nb"}]] + // If[FailureQ[#], + Missing["NotFound"], LspCodeAction[<| "title" -> "Documentation: " <> tokenString, "kind" -> CodeActionKind["Empty"], @@ -1282,8 +1280,7 @@ GetCodeActionsInRange[doc_TextDocument, range_LspRange] := With[ "command" -> "openRef", "arguments" -> {#} |> - |>], - Missing["NotFound"] + |>] ]& ), Missing["NotFound"], diff --git a/src/WolframLanguageServer/Token.wl b/src/WolframLanguageServer/Token.wl index efab189..9504aa6 100644 --- a/src/WolframLanguageServer/Token.wl +++ b/src/WolframLanguageServer/Token.wl @@ -190,10 +190,7 @@ GetUri[token_String, tag_String] := ( tag // Replace[{ "usage" :> ( - StringJoin[ - "[*reference*](https://reference.wolfram.com/language/ref/", token, ".html) " - (* "[*document*](command:editor.action.addCommentLine)" *) - ] + StringJoin["[*reference*](https://reference.wolfram.com/language/ref/", token, ".html)"] ), _ :> ( StringJoin["[*reference*](https://reference.wolfram.com/language/ref/message/", token, "/", tag,".html)"] From b750b1eb95af0c657b737bb06a4f4de0a8d7d81a Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Sat, 29 Aug 2020 23:14:14 -0700 Subject: [PATCH 06/32] Rename SendResponse to SendMessage * Add message-related interfaces. * Factor out ToAssociation's. --- src/WolframLanguageServer/Server.wl | 148 ++++++++++----------- src/WolframLanguageServer/Specification.wl | 58 ++++++-- 2 files changed, 113 insertions(+), 93 deletions(-) diff --git a/src/WolframLanguageServer/Server.wl b/src/WolframLanguageServer/Server.wl index 01a51e5..2db43e2 100644 --- a/src/WolframLanguageServer/Server.wl +++ b/src/WolframLanguageServer/Server.wl @@ -649,7 +649,9 @@ constructRPCBytes[msg_Association] := ( "InternalError", "The request is not handled correctly." ]], - "RawJSON"] + "RawJSON", + "Compact" -> True + ] ] // { (* header *) Length @@ -707,13 +709,13 @@ handleMessage[msg_Association, state_WorkState] := With[ (* wrong message before initialization *) !state["initialized"] && !MemberQ[{"initialize", "initialized", "exit"}, method], If[!NotificationQ[msg], - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], "error" -> ServerError[ "ServerNotInitialized", "The server is not initialized." ] - |>] + |>]] (* otherwise, dropped the notification *) ]; {"Continue", state}, @@ -751,9 +753,9 @@ sendCachedResult[method_String, msg_, state_WorkState] := With[ cache = getCache[method, msg, state] }, - sendResponse[state["client"], - <|"id" -> msg["id"]|> - // Append[ + sendMessage[state["client"], + ResponseMessage[<|"id" -> msg["id"]|>] + // ReplaceKey[ If[!MissingQ[cache["result"]], "result" -> cache["result"], "error" -> cache["error"] @@ -784,8 +786,10 @@ scheduleDelayedRequest[method_String, msg_, state_WorkState] := ( (* response, notification and request will call this function *) -sendResponse[client_, res_Association] := ( - Prepend[res, <|"jsonrpc" -> "2.0"|>] +sendMessage[client_, res:(_ResponseMessage|_NotificationMessage)] := ( + res + // ReplaceKey["jsonrpc" -> "2.0"] + // ToAssociation // constructRPCBytes // WriteMessage[client] ) @@ -807,12 +811,12 @@ handleRequest["initialize", msg_, state_WorkState] := Module[ "clientCapabilities" -> msg["params"]["capabilities"] ]; - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], "result" -> <| "capabilities" -> ServerCapabilities |> - |>]; + |>]]; {"Continue", newState} ]; @@ -826,10 +830,10 @@ handleRequest["shutdown", msg_, state_] := Module[ { }, - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], "result" -> Null - |>]; + |>]]; {"Continue", state} ]; @@ -857,10 +861,10 @@ handleRequest["workspace/executeCommand", msg_, state_] := With[ ) }]; - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], "result" -> Null - |>]; + |>]]; {"Continue", state} @@ -872,25 +876,25 @@ handleRequest["workspace/executeCommand", msg_, state_] := With[ handleRequest["textDocument/publishDiagnostics", uri_String, state_WorkState] := ( - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "method" -> "textDocument/publishDiagnostics", "params" -> <| "uri" -> uri, - "diagnostics" -> ToAssociation[DiagnoseDoc[state["openedDocs"][uri]]] + "diagnostics" -> DiagnoseDoc[state["openedDocs"][uri]] |> - |>]; + |>]]; {"Continue", state} ) handleRequest["textDocument/clearDiagnostics", uri_String, state_WorkState] := ( - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "method" -> "textDocument/publishDiagnostics", "params" -> <| "uri" -> uri, "diagnostics" -> {} |> - |>]; + |>]]; {"Continue", state} ) @@ -910,10 +914,10 @@ handleRequest["textDocument/hover", msg_, state_] := With[ pos = LspPosition[msg["params"]["position"]] }, - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], - "result" -> ToAssociation[GetHoverAtPosition[doc, pos]] - |>]; + "result" -> GetHoverAtPosition[doc, pos] + |>]]; {"Continue", state} ] @@ -943,7 +947,6 @@ cacheResponse[method:"textDocument/signatureHelp", msg_, state_WorkState] := Wit "cachedTime" -> Now, "result" -> ( GetSignatureHelp[state["openedDocs"][uri], pos] - // ToAssociation ) |>] ] @@ -980,37 +983,35 @@ handleRequest["textDocument/completion", msg_, state_] := Module[ msg["params"]["context"]["triggerKind"] // Replace[{ CompletionTriggerKind["Invoked"] :> ( - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], "result" -> <| "isIncomplete" -> False, - "items" -> ToAssociation@GetTokenCompletionAtPostion[doc, pos] + "items" -> GetTokenCompletionAtPostion[doc, pos] |> - |>] + |>]] ), CompletionTriggerKind["TriggerCharacter"] :> ( - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], "result" -> <| "isIncomplete" -> True, "items" -> ( GetTriggerKeyCompletion[doc, pos] - // ToAssociation ) |> - |>] + |>]] ), CompletionTriggerKind["TriggerForIncompleteCompletions"] :> ( - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], "result" -> <| "isIncomplete" -> False, "items" -> ( GetIncompleteCompletionAtPosition[doc, pos] - // ToAssociation ) |> - |>]; + |>]]; ) }]; @@ -1037,13 +1038,13 @@ handleRequest["completionItem/resolve", msg_, state_] := With[ msg["params"]["data"]["type"] // Replace[{ "Alias" | "LongName" :> ( - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], "result" -> msg["params"] - |>] + |>]] ), "Token" :> ( - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], "result" -> <| msg["params"] @@ -1054,7 +1055,7 @@ handleRequest["completionItem/resolve", msg_, state_] := With[ |> ] |> - |>] + |>]] ) }]; @@ -1077,10 +1078,10 @@ handleRequest["textDocument/definition", msg_, state_] := With[ pos = LspPosition[msg["params"]["position"]] }, - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], - "result" -> ToAssociation@FindDefinitions[doc, pos] - |>] // AbsoluteTiming // First // LogDebug; + "result" -> FindDefinitions[doc, pos] + |>]] // AbsoluteTiming // First // LogDebug; {"Continue", state} ] @@ -1097,10 +1098,10 @@ handleRequest["textDocument/references", msg_, state_] := With[ includeDeclaration = msg["params"]["context"]["includeDeclaration"] }, - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], - "result" -> ToAssociation@FindReferences[doc, pos, "IncludeDeclaration" -> includeDeclaration] - |>] // AbsoluteTiming // First // LogDebug; + "result" -> FindReferences[doc, pos, "IncludeDeclaration" -> includeDeclaration] + |>]] // AbsoluteTiming // First // LogDebug; {"Continue", state} ] @@ -1117,13 +1118,10 @@ handleRequest[method:"textDocument/documentHighlight", msg_, state_WorkState] := pos = LspPosition[msg["params"]["position"]] }, - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "id" -> id, - "result" -> ( - FindDocumentHighlight[state["openedDocs"][uri], pos] - // ToAssociation - ) - |>]; + "result" -> FindDocumentHighlight[state["openedDocs"][uri], pos] + |>]]; {"Continue", state} ] @@ -1157,7 +1155,6 @@ cacheResponse[method:"textDocument/documentSymbol", msg_, state_WorkState] := Wi "result" -> ( state["openedDocs"][uri] // ToDocumentSymbol - // ToAssociation ) |>] ] @@ -1195,17 +1192,10 @@ handleRequest["textDocument/codeAction", msg_, state_] := With[ range = ConstructType[msg["params"]["range"], LspRange] }, - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], - "result" -> ToAssociation[ - (* Command[<| - "title" -> "Open Documentation", - "command" -> "openRef", - "arguments" -> {"C:/Program Files/Wolfram Research/Mathematica/12.1/Documentation/English/System/ReferencePages/Symbols/List.nb"} - |>] *) - GetCodeActionsInRange[doc, range] - ] - |>]; + "result" -> GetCodeActionsInRange[doc, range] + |>]]; {"Continue", state} ] @@ -1234,7 +1224,6 @@ cacheResponse[method:"textDocument/documentColor", msg_, state_WorkState] := Wit "result" -> ( state["openedDocs"][uri] // FindDocumentColor - // ToAssociation ) |>] ] @@ -1274,13 +1263,10 @@ handleRequest["textDocument/colorPresentation", msg_, state_] := With[ range = ConstructType[msg["params"]["range"], LspRange] }, - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], - "result" -> ( - GetColorPresentation[doc, color, range] - // ToAssociation - ) - |>]; + "result" -> GetColorPresentation[doc, color, range] + |>]]; { "Continue", @@ -1298,14 +1284,14 @@ handleRequest["textDocument/colorPresentation", msg_, state_] := With[ handleRequest[_, msg_, state_] := ( - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], "error" -> ServerError["MethodNotFound", msg // ErrorMessageTemplates["MethodNotFound"] // LogError ] - |>]; + |>]]; {"Continue", state} ) @@ -1366,13 +1352,13 @@ handleNotification["$/cancelRequest", msg_, state_] := With[ Part[state["scheduledTasks"], pos]["type"] // StringJoin[#, " request is cancelled."]& // LogDebug; - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "id" -> id, "error" -> ServerError[ "RequestCancelled", "The request is cancelled." ] - |>]; + |>]]; state // ReplaceKeyBy["scheduledTasks" -> (Delete[pos])] ), @@ -1551,13 +1537,13 @@ showMessage[message_String, msgType_String, state_WorkState] := ( // Replace[_?MissingQ :> MessageType["Error"]] // LogDebug // (type \[Function] - sendResponse[state["client"], <| + sendMessage[state["client"], NotificationMessage[<| "method" -> "window/showMessage", "params" -> <| "type" -> type, "message" -> message |> - |>] + |>]] ) ) @@ -1566,13 +1552,13 @@ logMessage[message_String, msgType_String, state_WorkState] := ( // Replace[_?MissingQ :> MessageType["Error"]] // LogDebug // (type \[Function] - sendResponse[state["client"], <| + sendMessage[state["client"], NotificationMessage[<| "method" -> "window/logMessage", "params" -> <| "type" -> type, "message" -> message |> - |>] + |>]] ) ) @@ -1581,7 +1567,7 @@ logMessage[message_String, msgType_String, state_WorkState] := ( (*Handle Error*) -ServerError[errorType_String, msg_String] := ( +ServerError[errorType_String, msg_String] := ResponseError[ <| "code" -> ( errorType @@ -1593,7 +1579,7 @@ ServerError[errorType_String, msg_String] := ( ), "message" -> msg |> -) +] ErrorMessageTemplates = <| @@ -1684,17 +1670,17 @@ doNextScheduledTask[state_WorkState] := ( (* if there will not be a same task in the future, do it now *) _?MissingQ :> If[!MissingQ[task["callback"]], (* If the function is time constrained, than the there should not be a lot of lags. *) - (* TimeConstrained[task["callback"][newState, task["params"]], 0.1, sendResponse[state["client"], <|"id" -> task["params"]["id"], "result" -> <||>|>]], *) + (* TimeConstrained[task["callback"][newState, task["params"]], 0.1, sendMessage[state["client"], ResponseMessage[<|"id" -> task["params"]["id"], "result" -> <||>|>]]], *) task["callback"][newState, task["params"]] // AbsoluteTiming // Apply[(LogInfo[{task["type"], #1}];#2)&], - sendResponse[newState["client"], <| + sendMessage[newState["client"], ResponseMessage[<| "id" -> task["id"], "error" -> ServerError[ "InternalError", "There is no callback function for this scheduled task." ] - |>]; + |>]]; {"Continue", newState} ], (* find a recent duplicate request *) @@ -1702,13 +1688,13 @@ doNextScheduledTask[state_WorkState] := ( (* execute fallback function if applicable *) task["duplicateFallback"][newState, task["params"]], (* otherwise, return ContentModified error *) - sendResponse[newState["client"], <| + sendMessage[newState["client"], ResponseMessage[<| "id" -> task["id"], "error" -> ServerError[ "RequestCancelled", "There is a more recent duplicate request." ] - |>]; + |>]]; {"Continue", newState} ] }] diff --git a/src/WolframLanguageServer/Specification.wl b/src/WolframLanguageServer/Specification.wl index a0e1d8a..a26e245 100644 --- a/src/WolframLanguageServer/Specification.wl +++ b/src/WolframLanguageServer/Specification.wl @@ -10,6 +10,13 @@ BeginPackage["WolframLanguageServer`Specification`"] ClearAll[Evaluate[Context[] <> "*"]]; +(* ::Section:: *) +(* Language Server Protocol*) + + +ResponseMessage::usage = "is type of RequestMessage interface in LSP." +ResponseError::usage = "is type of RequestError interface in LSP." +NotificationMessage::usage = "is type of Notification interface in LSP." LspPosition::usage = "is type of Position interface in LSP." LspRange::usage = "is type of Range interface in LSP." Location::usage = "is type of Location interface in LSP." @@ -33,6 +40,7 @@ ColorInformation::usage = "is the type of ColorInformation interface in LSP." LspColor::usage = "is the type of Color interface in LSP." ColorPresentation::usage = "is the type of ColorPresentation interface in LSP." + (* ::Section:: *) (*Type Aliases*) @@ -45,18 +53,18 @@ DocumentUri = String ErrorCodes = <| (* Defined by JSON RPC *) - "ParseError" -> -32700, - "InvalidRequest" -> -32600, - "MethodNotFound" -> -32601, - "InvalidParams" -> -32602, - "InternalError" -> -32603, - "serverErrorStart" -> -32099, - "serverErrorEnd" -> -32000, - "ServerNotInitialized" -> -32002, - "UnknownErrorCode" -> -32001, - (* Defined by the protocol *) - "RequestCancelled" -> -32800, - "ContentModified" -> -32801 + "ParseError" -> -32700, + "InvalidRequest" -> -32600, + "MethodNotFound" -> -32601, + "InvalidParams" -> -32602, + "InternalError" -> -32603, + "serverErrorStart" -> -32099, + "serverErrorEnd" -> -32000, + "ServerNotInitialized" -> -32002, + "UnknownErrorCode" -> -32001, + (* Defined by the protocol *) + "RequestCancelled" -> -32800, + "ContentModified" -> -32801 |> MarkupKind = <| @@ -178,6 +186,32 @@ Needs["DataType`"] (*Server Communication Related Type*) +(* ::Subsection:: *) +(*Basic Protocol*) + + +DeclareType[ResponseMessage, <| + "jsonrpc" -> _String, + "id" -> _Integer | _String, + "result" -> _, + "error" -> _ +|>] + + +DeclareType[ResponseError, <| + "code" -> _Integer, + "message" -> _String, + "data" -> _ +|>] + + +DeclareType[NotificationMessage, <| + "jsonrpc" -> _String, + "method" -> _String, + "params" -> _ +|>] + + (* ::Subsection:: *) (*Basic Structures*) From 0dd93ce73411850edc124b20432a6e54c7aa985b Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Sun, 30 Aug 2020 02:44:36 -0700 Subject: [PATCH 07/32] Fix: correctly handle scheduled requests after a file is closed * For diagnostics, just don't publish one. * For other cached requests, response Null results. --- src/WolframLanguageServer/Server.wl | 106 +++++++++++++++++----------- 1 file changed, 63 insertions(+), 43 deletions(-) diff --git a/src/WolframLanguageServer/Server.wl b/src/WolframLanguageServer/Server.wl index 2db43e2..d446a55 100644 --- a/src/WolframLanguageServer/Server.wl +++ b/src/WolframLanguageServer/Server.wl @@ -756,9 +756,13 @@ sendCachedResult[method_String, msg_, state_WorkState] := With[ sendMessage[state["client"], ResponseMessage[<|"id" -> msg["id"]|>] // ReplaceKey[ - If[!MissingQ[cache["result"]], - "result" -> cache["result"], - "error" -> cache["error"] + If[MissingQ[cache], + (* File closed, sends Null. *) + "result" -> Null, + If[!MissingQ[cache["result"]], + "result" -> cache["result"], + "error" -> cache["error"] + ] ] ] ]; @@ -942,13 +946,16 @@ cacheResponse[method:"textDocument/signatureHelp", msg_, state_WorkState] := Wit }, state - // ReplaceKey[ - {"caches", method, uri} -> RequestCache[<| - "cachedTime" -> Now, - "result" -> ( - GetSignatureHelp[state["openedDocs"][uri], pos] - ) - |>] + // If[MissingQ[state["openedDocs"][uri]], + Identity, + ReplaceKey[ + {"caches", method, uri} -> RequestCache[<| + "cachedTime" -> Now, + "result" -> ( + GetSignatureHelp[state["openedDocs"][uri], pos] + ) + |>] + ] ] ] @@ -1149,14 +1156,17 @@ cacheResponse[method:"textDocument/documentSymbol", msg_, state_WorkState] := Wi }, state - // ReplaceKey[ - {"caches", method, uri} -> RequestCache[<| - "cachedTime" -> Now, - "result" -> ( - state["openedDocs"][uri] - // ToDocumentSymbol - ) - |>] + // If[MissingQ[state["openedDocs"][uri]], + Identity, + ReplaceKey[ + {"caches", method, uri} -> RequestCache[<| + "cachedTime" -> Now, + "result" -> ( + state["openedDocs"][uri] + // ToDocumentSymbol + ) + |>] + ] ] ] @@ -1218,14 +1228,17 @@ cacheResponse[method:"textDocument/documentColor", msg_, state_WorkState] := Wit }, state - // ReplaceKey[ - {"caches", method, uri} -> RequestCache[<| - "cachedTime" -> Now, - "result" -> ( - state["openedDocs"][uri] - // FindDocumentColor - ) - |>] + // If[MissingQ[state["openedDocs"][uri]], + Identity, + ReplaceKey[ + {"caches", method, uri} -> RequestCache[<| + "cachedTime" -> Now, + "result" -> ( + state["openedDocs"][uri] + // FindDocumentColor + ) + |>] + ] ] ] @@ -1416,7 +1429,8 @@ handleNotification["textDocument/didClose", msg_, state_] := With[ // ReplaceKeyBy["caches" -> (Fold[ReplaceKeyBy, #, { "textDocument/documentSymbol" -> KeyDrop[uri], "textDocument/documentColor" -> KeyDrop[uri], - "textDocument/codeLens" -> KeyDrop[uri] + "textDocument/codeLens" -> KeyDrop[uri], + "textDocument/publishDiagnostics" -> KeyDrop[uri] }]&)] // handleRequest["textDocument/clearDiagnostics", uri, #]& ] @@ -1634,22 +1648,28 @@ doNextScheduledTask[state_WorkState] := ( task["type"] // Replace[{ method:"textDocument/publishDiagnostics" :> ( - newState - // If[DatePlus[ - newState["openedDocs"][task["params"]]["lastUpdate"], - {5, "Second"}] > Now, - (* Reschedule the task *) - scheduleDelayedRequest[method, task["params"], #]&, - ReplaceKey[ - { - "caches", - "textDocument/publishDiagnostics", - task["params"], - "scheduledQ" - } -> False - ] - /* (task["callback"][#, task["params"]]&) - ] + newState["openedDocs"][task["params"]] + // Replace[{ + (* File closed, does nothing *) + _?MissingQ -> {"Continue", newState}, + _?((DatePlus[#["lastUpdate"], {5, "Second"}] > Now)&) :> ( + (* Reschedule the task *) + newState + // scheduleDelayedRequest[method, task["params"], #]& + ), + _ :> ( + newState + // ReplaceKey[ + { + "caches", + "textDocument/publishDiagnostics", + task["params"], + "scheduledQ" + } -> False + ] + // task["callback"][#, task["params"]]& + ) + }] ), "InitialCheck" :> ( newState From 2bebb52d1523265cc915740a947efa55b2230ae8 Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Sun, 30 Aug 2020 02:01:27 -0700 Subject: [PATCH 08/32] Fix: lower-case symbols falsely trigger the codeActions Use AbsoluteFileName for Windows to compare --- src/WolframLanguageServer/TextDocument.wl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/WolframLanguageServer/TextDocument.wl b/src/WolframLanguageServer/TextDocument.wl index de96989..c159ba3 100644 --- a/src/WolframLanguageServer/TextDocument.wl +++ b/src/WolframLanguageServer/TextDocument.wl @@ -1270,8 +1270,9 @@ GetCodeActionsInRange[doc_TextDocument, range_LspRange] := With[ CompareNodePosition[#, endPos, 1] <= 0 )&) :> ( FindFile[FileNameJoin[{"ReferencePages", "Symbols", tokenString <> ".nb"}]] - // If[FailureQ[#], - Missing["NotFound"], + // If[!FailureQ[#] && + (* FindFile is case-insensitive on Windows. Needs AbsoluteFileName to confirm. *) + ($OperatingSystem == "Windows" \[Implies] AbsoluteFileName[#] == #), LspCodeAction[<| "title" -> "Documentation: " <> tokenString, "kind" -> CodeActionKind["Empty"], @@ -1280,7 +1281,8 @@ GetCodeActionsInRange[doc_TextDocument, range_LspRange] := With[ "command" -> "openRef", "arguments" -> {#} |> - |>] + |>], + Missing["NotFound"] ]& ), Missing["NotFound"], From bacf6fc1050e2f8efe933847fefdd1d291ea960a Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Sun, 30 Aug 2020 03:42:58 -0700 Subject: [PATCH 09/32] Update: use spdx identifiers in file headers * Use the creation year as the copyright year * Update file descriptions in the comments --- LICENSE | 2 +- init.wls | 9 ++++----- src/PatternTemplate.wl | 5 ++++- src/WolframLanguageServer/Archives.wl | 8 ++++---- src/WolframLanguageServer/AstPatterns.wl | 8 +++++--- src/WolframLanguageServer/ColorTable.wl | 8 +++++--- src/WolframLanguageServer/Logger.wl | 7 ++++--- src/WolframLanguageServer/Server.wl | 7 ++++--- src/WolframLanguageServer/Specification.wl | 7 ++++--- src/WolframLanguageServer/TableGenerator.wl | 7 +++++++ src/WolframLanguageServer/TextDocument.wl | 7 ++++--- src/WolframLanguageServer/Token.wl | 7 ++++--- test/PatternTemplateTest.wl | 7 +++++++ test/RunTest.wl | 7 +++++++ test/WolframLanguageServer/TextDocumentTest.wl | 7 +++++++ test/WolframLanguageServer/TokenTest.wl | 7 +++++++ 16 files changed, 78 insertions(+), 32 deletions(-) diff --git a/LICENSE b/LICENSE index 51c71cd..fdd4ab3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 lsp-wl +Copyright (c) 2018 lsp-wl Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/init.wls b/init.wls index 1e383f1..4ac82f3 100644 --- a/init.wls +++ b/init.wls @@ -1,19 +1,18 @@ #!/usr/bin/env wolframscript (* ::Package:: *) -(* Wolfram Language Server *) -(* Author: kenkangxgwe , - huxianglong -*) +(* Copyright 2018 lsp-wl Authors *) +(* SPDX-License-Identifier: MIT *) -(* init.wl +(* init.wls This is a script file to initialize the Wolfram Language Server. Please see the help info below. *) + (* For windows, this should be set for Print to correctly format outputs. *) SetOptions[$Output, FormatType -> OutputForm] diff --git a/src/PatternTemplate.wl b/src/PatternTemplate.wl index 8847dc0..968cd4b 100644 --- a/src/PatternTemplate.wl +++ b/src/PatternTemplate.wl @@ -1,7 +1,10 @@ (* ::Package:: *) +(* Copyright 2020 lsp-wl Authors *) +(* SPDX-License-Identifier: MIT *) + + (* Pattern Template *) -(* Author: kenkangxgwe *) BeginPackage["PatternTemplate`"] diff --git a/src/WolframLanguageServer/Archives.wl b/src/WolframLanguageServer/Archives.wl index bfa6595..8ded83a 100644 --- a/src/WolframLanguageServer/Archives.wl +++ b/src/WolframLanguageServer/Archives.wl @@ -1,10 +1,10 @@ (* ::Package:: *) -(*Archives*) + +(* Copyright 2019 lsp-wl Authors *) +(* SPDX-License-Identifier: MIT *) + (* Wolfram Language Server Archived Functions *) -(* Author: kenkangxgwe , - huxianglong -*) BeginPackage["WolframLanguageServer`Archives`"] diff --git a/src/WolframLanguageServer/AstPatterns.wl b/src/WolframLanguageServer/AstPatterns.wl index f92d408..5074df9 100644 --- a/src/WolframLanguageServer/AstPatterns.wl +++ b/src/WolframLanguageServer/AstPatterns.wl @@ -1,9 +1,11 @@ (* ::Package:: *) +(* Copyright 2019 lsp-wl Authors *) +(* SPDX-License-Identifier: MIT *) + + (* Wolfram Language Server Ast Patterns *) -(* Author: kenkangxgwe , - huxianglong -*) + BeginPackage["WolframLanguageServer`AstPatterns`"] ClearAll[Evaluate[Context[] <> "*"]] diff --git a/src/WolframLanguageServer/ColorTable.wl b/src/WolframLanguageServer/ColorTable.wl index ecce2a1..53efb2b 100644 --- a/src/WolframLanguageServer/ColorTable.wl +++ b/src/WolframLanguageServer/ColorTable.wl @@ -1,9 +1,11 @@ (* ::Package:: *) +(* Copyright 2019 lsp-wl Authors *) +(* SPDX-License-Identifier: MIT *) + + (* Wolfram Language Server Color Table *) -(* Author: kenkangxgwe , - huxianglong -*) + BeginPackage["WolframLanguageServer`ColorTable`"] ClearAll[Evaluate[Context[] <> "*"]] diff --git a/src/WolframLanguageServer/Logger.wl b/src/WolframLanguageServer/Logger.wl index e3fe62e..97b738c 100644 --- a/src/WolframLanguageServer/Logger.wl +++ b/src/WolframLanguageServer/Logger.wl @@ -1,9 +1,10 @@ (* ::Package:: *) +(* Copyright 2018 lsp-wl Authors *) +(* SPDX-License-Identifier: MIT *) + + (* Wolfram Language Server Logger *) -(* Author: kenkangxgwe , - huxianglong -*) BeginPackage["WolframLanguageServer`Logger`"]; diff --git a/src/WolframLanguageServer/Server.wl b/src/WolframLanguageServer/Server.wl index d446a55..279acc0 100644 --- a/src/WolframLanguageServer/Server.wl +++ b/src/WolframLanguageServer/Server.wl @@ -1,9 +1,10 @@ (* ::Package:: *) +(* Copyright 2018 lsp-wl Authors *) +(* SPDX-License-Identifier: MIT *) + + (* Wolfram Language Server *) -(* Author: kenkangxgwe , - huxianglong -*) BeginPackage["WolframLanguageServer`Server`"]; diff --git a/src/WolframLanguageServer/Specification.wl b/src/WolframLanguageServer/Specification.wl index a26e245..17f9255 100644 --- a/src/WolframLanguageServer/Specification.wl +++ b/src/WolframLanguageServer/Specification.wl @@ -1,9 +1,10 @@ (* ::Package:: *) +(* Copyright 2018 lsp-wl Authors *) +(* SPDX-License-Identifier: MIT *) + + (* Wolfram Language Server Specification *) -(* Author: kenkangxgwe , - huxianglong -*) BeginPackage["WolframLanguageServer`Specification`"] diff --git a/src/WolframLanguageServer/TableGenerator.wl b/src/WolframLanguageServer/TableGenerator.wl index 01a087c..85d85ca 100644 --- a/src/WolframLanguageServer/TableGenerator.wl +++ b/src/WolframLanguageServer/TableGenerator.wl @@ -1,5 +1,12 @@ (* ::Package:: *) +(* Copyright 2019 lsp-wl Authors *) +(* SPDX-License-Identifier: MIT *) + + +(* Wolfram Language Server Table Generator *) + + BeginPackage["WolframLanguageServer`TableGenerator`"] ClearAll[Evaluate[Context[] <> "*"]] diff --git a/src/WolframLanguageServer/TextDocument.wl b/src/WolframLanguageServer/TextDocument.wl index c159ba3..67883ad 100644 --- a/src/WolframLanguageServer/TextDocument.wl +++ b/src/WolframLanguageServer/TextDocument.wl @@ -1,9 +1,10 @@ (* ::Package:: *) +(* Copyright 2019 lsp-wl Authors *) +(* SPDX-License-Identifier: MIT *) + + (* Wolfram Language Server TextDocument *) -(* Author: kenkangxgwe , - huxianglong -*) BeginPackage["WolframLanguageServer`TextDocument`"] diff --git a/src/WolframLanguageServer/Token.wl b/src/WolframLanguageServer/Token.wl index 9504aa6..47760e6 100644 --- a/src/WolframLanguageServer/Token.wl +++ b/src/WolframLanguageServer/Token.wl @@ -1,9 +1,10 @@ (* ::Package:: *) +(* Copyright 2019 lsp-wl Authors *) +(* SPDX-License-Identifier: MIT *) + + (* Wolfram Language Server Token *) -(* Author: kenkangxgwe , - huxianglong -*) BeginPackage["WolframLanguageServer`Token`"] diff --git a/test/PatternTemplateTest.wl b/test/PatternTemplateTest.wl index 9518659..4d27b4c 100644 --- a/test/PatternTemplateTest.wl +++ b/test/PatternTemplateTest.wl @@ -1,5 +1,12 @@ (* ::Package:: *) +(* Copyright 2019 lsp-wl Authors *) +(* SPDX-License-Identifier: MIT *) + + +(* Wolfram Language Server Pattern Template Test *) + + BeginPackage["PatternTemplateTest`"] ClearAll[Evaluate[Context[] <> "*"]] diff --git a/test/RunTest.wl b/test/RunTest.wl index 1de0b08..6ba2fca 100644 --- a/test/RunTest.wl +++ b/test/RunTest.wl @@ -1,5 +1,12 @@ (* ::Package:: *) +(* Copyright 2019 lsp-wl Authors *) +(* SPDX-License-Identifier: MIT *) + + +(* Wolfram Language Server Test Runner *) + + BeginPackage["RunTest`"] ClearAll[Evaluate[Context[] <> "*"]] diff --git a/test/WolframLanguageServer/TextDocumentTest.wl b/test/WolframLanguageServer/TextDocumentTest.wl index a6fc74c..464a267 100644 --- a/test/WolframLanguageServer/TextDocumentTest.wl +++ b/test/WolframLanguageServer/TextDocumentTest.wl @@ -1,5 +1,12 @@ (* ::Package:: *) +(* Copyright 2019 lsp-wl Authors *) +(* SPDX-License-Identifier: MIT *) + + +(* Wolfram Language Server TextDocument Test *) + + BeginPackage["WolframLanguageServer`TextDocumentTest`"] ClearAll[Evaluate[Context[] <> "*"]] diff --git a/test/WolframLanguageServer/TokenTest.wl b/test/WolframLanguageServer/TokenTest.wl index 6c4f350..14413da 100644 --- a/test/WolframLanguageServer/TokenTest.wl +++ b/test/WolframLanguageServer/TokenTest.wl @@ -1,5 +1,12 @@ (* ::Package:: *) +(* Copyright 2019 lsp-wl Authors *) +(* SPDX-License-Identifier: MIT *) + + +(* Wolfram Language Server Token Test *) + + BeginPackage["WolframLanguageServer`TokenTest`"] ClearAll[Evaluate[Context[] <> "*"]] From ca4b39dadecfdc69948f1dbb400087af58cdf710 Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Sun, 6 Sep 2020 02:20:20 -0700 Subject: [PATCH 10/32] TypeCheck will emit Message instead of implicitly returning Missing[...] --- external/Matypetica/src/DataType.wl | 32 +++++++++++++++--------- external/Matypetica/test/DataTypeTest.wl | 7 +++--- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/external/Matypetica/src/DataType.wl b/external/Matypetica/src/DataType.wl index d864ded..f4b1763 100644 --- a/external/Matypetica/src/DataType.wl +++ b/external/Matypetica/src/DataType.wl @@ -25,8 +25,7 @@ ReplaceKeyBy[replaceRule_Rule] is an operator that can be applied to an object." DeleteKey::usage = "DeleteKey[object, key] deletes the key-value pair from object[key]. DeleteKey[object, {key1, key2}] deletes the key-value pair at object[key1][key2]. DeleteKey[keys] is an operator that can be applied to an object." -TypeCheckOn::usage = "TypeCheckOn[] turns on type checking." -TypeCheckOff::usage = "TypeCheckOff[] turns off type checking." +TypeCheck::usage = "TypeCheck[toggle_?BooleanQ] turns on/off the type checking." Begin["`Private`"] @@ -42,10 +41,22 @@ DeclareType[typename_Symbol, typekey:<|(_String -> _)...|>] := Module[ }, (* Getter *) - typename[typedict_Association][key_String] := TypeCheck[typedict[key], typekey[key]]; + typename[typedict_Association][key_String] := ( + If[$typeCheckQ, + Which[ + MissingQ[typedict[key]], + Null, + !KeyMemberQ[typekey, key], + Message[TypeCheck::miskey, typename, key], + !MatchQ[typedict[key], typekey[key]], + Message[TypeCheck::mispat, typename, key, typedict[key]] + ] + ]; + typedict[key] + ); typename[typedict_Association][key_String, default_] := With[ {value = typename[typedict][key]}, - If[MissingQ[value], TypeCheck[default, typekey[key]], value] + If[MissingQ[value], typename[<|key -> default|>][key], value] ]; (* Deserializer*) @@ -187,14 +198,11 @@ ConstructType[parameters_Association, pattern:Association[(Verbatim[Repeated]|Ve (*TypeCheck*) -TypeCheckOff[] := (ClearAll[TypeCheck]; TypeCheck[v_, ___] := v) -TypeCheckOn[] := ( - ClearAll[TypeCheck]; - TypeCheck[v_?MissingQ, _] := v; - TypeCheck[_, p_?MissingQ] := p; - TypeCheck[val_, pat_] := If[MatchQ[val, pat], val, Missing["PatternMismatch", {val, pat}]]; -) -TypeCheckOn[] +TypeCheck::miskey = "`1` doesn't contains key `2`." +TypeCheck::mispat = "`3` doesn't match the pattern of \"`2`\" in `1`." + +TypeCheck[toggle_?BooleanQ] := ($typeCheckQ = toggle) +TypeCheck[True] (* ::Section:: *) diff --git a/external/Matypetica/test/DataTypeTest.wl b/external/Matypetica/test/DataTypeTest.wl index 2eb206f..d06af06 100644 --- a/external/Matypetica/test/DataTypeTest.wl +++ b/external/Matypetica/test/DataTypeTest.wl @@ -58,14 +58,15 @@ VerificationTest[ VerificationTest[ stu1 = Student[<|"id" -> 1, "name" -> "John Doe", "sex" -> "Man"|>]; stu1["sex"], - Missing["PatternMismatch", {"Man", "Male"|"Female"}], + "Man", + {TypeCheck::mispat}, TestID -> "Getter Type Check" ], VerificationTest[ - TypeCheckOff[]; + TypeCheck[False]; stu1 = Student[<|"id" -> 1, "name" -> "John Doe", "sex" -> "Man"|>]; - With[{res = stu1["sex"]}, TypeCheckOn[]; res], + With[{res = stu1["sex"]}, TypeCheck[True]; res], "Man", TestID -> "Getter Type Check Off" ], From 5f2cc42d5e830c4a98861e2469963a290dd97794 Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Sun, 6 Sep 2020 02:42:23 -0700 Subject: [PATCH 11/32] Fix: codeRange should not be empty when merge with range * This fixes the type check failure * Also add a test --- src/WolframLanguageServer/TextDocument.wl | 10 +-- .../WolframLanguageServer/TextDocumentTest.wl | 71 +++++++++++++++++++ 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/src/WolframLanguageServer/TextDocument.wl b/src/WolframLanguageServer/TextDocument.wl index 67883ad..458d83e 100644 --- a/src/WolframLanguageServer/TextDocument.wl +++ b/src/WolframLanguageServer/TextDocument.wl @@ -268,19 +268,19 @@ InsertCell[rootCell_CellNode, nextCell_CellNode] := ( rootCell // ReplaceKeyBy[{"children", -1} -> (InsertCell[#, nextCell]&)], rootCell - // If[nextCell["level"] == Infinity, + // If[Length[rootCell["children"]] > 0, + ReplaceKeyBy[{"children", -1} -> TerminateCell], + If[nextCell["level"] == Infinity, (* Joins the codeRange with root *) ReplaceKeyBy["codeRange" -> (Join[#, nextCell["codeRange"]]&)], Identity ] - // If[Length[rootCell["children"]] > 0, - ReplaceKeyBy[{"children", -1} -> TerminateCell], - Identity ] (* appends the new cell in the children list *) // ReplaceKeyBy["children" -> Append[ nextCell - // If[nextCell["level"] == Infinity, + // If[nextCell["level"] == Infinity && + Length[nextCell["codeRange"]] > 0, (* removes codeRange *) ReplaceKey[{"range", -1} -> ( First[First[nextCell["codeRange"]]] - 1 diff --git a/test/WolframLanguageServer/TextDocumentTest.wl b/test/WolframLanguageServer/TextDocumentTest.wl index 464a267..1e81c22 100644 --- a/test/WolframLanguageServer/TextDocumentTest.wl +++ b/test/WolframLanguageServer/TextDocumentTest.wl @@ -111,6 +111,77 @@ VerificationTest[ TestID -> "ChangeTextDocument2" ], +VerificationTest[ + ToDocumentSymbol[TextDocument[<| + "text" -> { + "(* " ~~ "::Section::" ~~ " *)", + "(*section name*)", + "", + "", + "(* " ~~ "::nostyle::" ~~ " *)", + "(*section name*)", + "", + "" + } + |>]], + { + DocumentSymbol[<| + "name" -> "section name", + "detail" -> "Section", + "kind" -> 15, + "range" -> LspRange[<| + "start" -> LspPosition[<| + "line" -> 0, + "character" -> 0 + |>], + "end" -> LspPosition[<| + "line" -> 7, + "character" -> 0 + |>] + |>], + "selectionRange" -> LspRange[<| + "start" -> LspPosition[<| + "line" -> 1, + "character" -> 2 + |>], + "end" -> LspPosition[<| + "line" -> 1, + "character" -> 14 + |>] + |>], + "children" -> { + DocumentSymbol[<| + "name" -> "section name", + "detail" -> "nostyle", + "kind" -> 15, + "range" -> LspRange[<| + "start" -> LspPosition[<| + "line" -> 4, + "character" -> 0 + |>], + "end" -> LspPosition[<| + "line" -> 7, + "character" -> 0 + |>] + |>], + "selectionRange" -> LspRange[<| + "start" -> LspPosition[<| + "line" -> 5, + "character" -> 2 + |>], + "end" -> LspPosition[<| + "line" -> 5, + "character" -> 14 + |>] + |>], + "children"->{} + |>] + } + |>] + }, + TestID -> "ToDocumentSymbolEmptySymbol1" +], + VerificationTest[ FindAllCodeRanges[TextDocument[<| "text" -> { From 58945a82fbeb3b65c85595d5a6c7d3a44418b088 Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Sun, 6 Sep 2020 02:44:55 -0700 Subject: [PATCH 12/32] Add FailureQ test to Options when generating usages * Applying `Options` on `$FrontEndSession` and `$DefaultFrontEnd` will return $Failed. * Also add a test only if there's no FrontEnd presents. --- src/WolframLanguageServer/Token.wl | 3 ++- test/WolframLanguageServer/TokenTest.wl | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/WolframLanguageServer/Token.wl b/src/WolframLanguageServer/Token.wl index 47760e6..3a8b6b0 100644 --- a/src/WolframLanguageServer/Token.wl +++ b/src/WolframLanguageServer/Token.wl @@ -215,7 +215,8 @@ GenOptions[token_String, o:OptionsPattern[]] := ( token // StringTemplate["Options[``]"] // ToExpression - // Replace[_Options -> {}] + // Quiet + // Replace[_Options|_?FailureQ -> {}] // Map[ToString[#, InputForm]&] // Replace[{options__} :> ( If[OptionValue["Format"] == MarkupKind["Markdown"], diff --git a/test/WolframLanguageServer/TokenTest.wl b/test/WolframLanguageServer/TokenTest.wl index 14413da..7f382b6 100644 --- a/test/WolframLanguageServer/TokenTest.wl +++ b/test/WolframLanguageServer/TokenTest.wl @@ -89,6 +89,21 @@ If[$VersionNumber >= 12.0, ] ], +If[$FrontEnd === Null, +VerificationTest[ + TokenDocumentation["$FrontEndSession", "usage"], + StringJoin[ + "**$FrontEndSession** [*reference*](https://reference.wolfram.com/language/ref/$FrontEndSession.html) (Protected, ReadProtected)\n\n\n", + "```mathematica\n", + "$FrontEndSession \n", + "```\n\n", + "is a global symbol that represents the current session of the front end from which the kernel is being run.\n\n" + ], + TestID -> "KnownSymbolUsage 4" +], +Nothing +], + VerificationTest[ TokenDocumentation["Syntax", "stresc"], StringJoin[ From 4ca5e0184662e6eaf3814b1630ec0a06d22c5b18 Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Sun, 6 Sep 2020 02:51:32 -0700 Subject: [PATCH 13/32] Refine init script, Logger and Server * Only reset $Output under Windows command line. * Add `--no-start` command line argument to import but not run the server. * Extract Snippet so it can be quickly commented out. * Send compact response to client. --- init.wls | 19 ++++++++++++------- src/WolframLanguageServer/Logger.wl | 8 ++++++-- src/WolframLanguageServer/Server.wl | 2 +- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/init.wls b/init.wls index 4ac82f3..918d468 100644 --- a/init.wls +++ b/init.wls @@ -14,7 +14,9 @@ Please see the help info below. (* For windows, this should be set for Print to correctly format outputs. *) -SetOptions[$Output, FormatType -> OutputForm] +If[$OperatingSystem == "Windows" && $FrontEnd === Null, + SetOptions[$Output, FormatType -> OutputForm] +] (* ::Subsection:: *) (*RootDirectory*) @@ -94,6 +96,7 @@ Options: --test, -t Run all tests --log=loglevel, -l loglevel Specifiy logging level as debug, info, warn or error (default: info) + --no-start Do not run the language server. (Useful when running in notebook) --socket=port Connect to a socket server on port (default: 6536) --tcp-server=port Start a socket server on port (default: 6536) --pipe=pipeName Connect via a named pipe (Windows only) @@ -210,12 +213,14 @@ Module[ *) << WolframLanguageServer`Server`; WolframLanguageServer`CheckReturnTypeQ = True; - LogDebug @ WolframLanguageServer`Server`WLServerStart[ - "Stream" -> stream, - "ClientPid" -> clientPid, - "Port" -> port, - "Pipe" -> pipe, - "WorkingDir" -> WolframLanguageServer`RootDirectory + If[!MemberQ[WolframLanguageServer`CommandLine, "--no-start"], + LogDebug @ WolframLanguageServer`Server`WLServerStart[ + "Stream" -> stream, + "ClientPid" -> clientPid, + "Port" -> port, + "Pipe" -> pipe, + "WorkingDir" -> WolframLanguageServer`RootDirectory + ] ]; Quit[0] ]; diff --git a/src/WolframLanguageServer/Logger.wl b/src/WolframLanguageServer/Logger.wl index 97b738c..d0839ee 100644 --- a/src/WolframLanguageServer/Logger.wl +++ b/src/WolframLanguageServer/Logger.wl @@ -32,8 +32,12 @@ LoggerStart[level_, streams:{_OutputStream..}] := Module[ (Function[{lvl}, Function[{msg}, Function[{stream}, WriteString[stream, - Snippet["[" <> StringPadRight[ToUpperCase[lvl], 5] <> " " - <> DateString["ISODateTime"] <> "] " <> ToString[msg], 20] <> "\n" + StringJoin[ + StringJoin[ + "[", StringPadRight[ToUpperCase[lvl], 5], " ", DateString["ISODateTime"], "] ", ToString[msg] + ] + // Snippet[#, 20]&, + "\n"] ]] /@ streams; msg ] (*Echo[#, lvl, ##2]&*) diff --git a/src/WolframLanguageServer/Server.wl b/src/WolframLanguageServer/Server.wl index 279acc0..376d596 100644 --- a/src/WolframLanguageServer/Server.wl +++ b/src/WolframLanguageServer/Server.wl @@ -637,7 +637,7 @@ RPCPatterns = <| constructRPCBytes[msg_Association] := ( Check[ - ExportByteArray[msg, "RawJSON"], + ExportByteArray[msg, "RawJSON", "Compact" -> True], (* if the result is not able to convert to JSON, returns an error respond From 71deb7c027bf37d8dd1a0b87119abf3d536185bb Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Sun, 4 Oct 2020 22:51:36 -0700 Subject: [PATCH 14/32] Fix: Handling uncommon DocumentSymbol * Correctly handles codeRange when multiple unknown styles present. * Correctly handles compound style (e.g. Section::Closed). * Add tests. --- src/WolframLanguageServer/TextDocument.wl | 152 ++++++++++-------- .../WolframLanguageServer/TextDocumentTest.wl | 128 ++++++++++++++- 2 files changed, 203 insertions(+), 77 deletions(-) diff --git a/src/WolframLanguageServer/TextDocument.wl b/src/WolframLanguageServer/TextDocument.wl index 458d83e..7ef05fb 100644 --- a/src/WolframLanguageServer/TextDocument.wl +++ b/src/WolframLanguageServer/TextDocument.wl @@ -25,6 +25,7 @@ FindReferences::usage = "FindReferences[doc_TextDocument, pos_LspPosition, o:Opt FindDocumentHighlight::usage = "FindDocumentHighlight[doc_TextDocument, pos_LspPosition] gives a list of DocumentHighlight." FindAllCodeRanges::usage = "FindAllCodeRanges[doc_TextDocument] returns a list of LspRange which locate all the code ranges (cells) in the given doc." GetCodeActionsInRange::usage = "GetCodeActionsInRange[doc_TextDocument, range_LspRange] returns a list of CodeAction related to specified range." +GetDocumentText::usage = "GetDocumentText[doc_TextDocument, range_LspRange] returns the text of the doc at given range." FindDocumentColor::usage = "FindDocumentColor[doc_TextDocument] gives a list of colors in the text document." GetColorPresentation::usage = "GetColorPresentation[doc_TextDocument, color_LspColor, range_LspRange] gives the RGBColor presentation of the color." @@ -194,7 +195,9 @@ constructCellNode[doc_TextDocument, styleLine_Integer, endLine_Integer] := Block style = If[styleLine == 0, AdditionalStyle["File"], Part[doc["text"], styleLine] - // StringCases["(* "~~"::"~~Shortest[style___]~~"::"~~" *)" :> style] + // StringCases["(* " ~~ "::" ~~ Shortest[style___] ~~ "::" ~~ " *)" :> style] + // First + // StringSplit[#, "::"]& // First // Replace["" -> "[empty]"] ]; @@ -270,12 +273,13 @@ InsertCell[rootCell_CellNode, nextCell_CellNode] := ( rootCell // If[Length[rootCell["children"]] > 0, ReplaceKeyBy[{"children", -1} -> TerminateCell], - If[nextCell["level"] == Infinity, + Identity + ] + // If[nextCell["level"] == Infinity, (* Joins the codeRange with root *) ReplaceKeyBy["codeRange" -> (Join[#, nextCell["codeRange"]]&)], Identity ] - ] (* appends the new cell in the children list *) // ReplaceKeyBy["children" -> Append[ nextCell @@ -304,15 +308,11 @@ TerminateCell[rootcell_CellNode] := ( Max[ Last[newRootCell["range"]], newRootCell["children"] - // Replace[{ - {___, lastChild_} :> Last[lastChild["range"]], - _ -> -Infinity - }], + // Last[#, <|"range" -> -Infinity|>]& + // Key["range"], newRootCell["codeRange"] - // Replace[{ - {___, {_, last_}} :> last, - _ -> -Infinity - }] + // Last[#, {-Infinity}]& + // Last ] )] )) @@ -376,7 +376,6 @@ GetCodeRangeAtPosition[doc_TextDocument, pos_LspPosition] := With[ FindAllCodeRanges[doc_TextDocument] := ( - Cases[ divideCells[doc], node_CellNode :> node["codeRange"], @@ -386,6 +385,19 @@ FindAllCodeRanges[doc_TextDocument] := ( // Map[ToLspRange[doc, #]&] ) +GetDocumentText[doc_TextDocument, range_LspRange] := ( + doc["text"] + // Take[#, { + range["start"]["line"] + 1, + range["end"]["line"] + 1 + }]& + // ReplacePart[#, 1 -> + StringDrop[First[#], range["start"]["character"]]]& + // ReplacePart[#, -1 -> + StringTake[Last[#], range["end"]["character"]]]& + // StringRiffle[#, "\n"]& +) + (* ::Section:: *) (*AST utils*) @@ -451,54 +463,37 @@ ToDocumentSymbol[doc_TextDocument] := ( ) -ToDocumentSymbolImpl[doc_TextDocument, node_] := ( - node - // Replace[{ - _CellNode?(Key["style"] /* AnonymousStyleQ /* Not) :> ( - DocumentSymbol[<| - "name" -> node["name"], - "detail" -> node["style"], - "kind" -> If[node["style"] == "Package", - SymbolKind["Package"], - SymbolKind["String"] - ], - "range" -> ToLspRange[doc, node["range"]], - "selectionRange" -> node["selectionRange"], - "children" -> ( - Join[ - If[!MissingQ[node["codeRange"]], - node["codeRange"] - // Map[CellToAST[doc ,#]&] - // Flatten - // Map[ToDocumentSymbolImpl], - {} - ], - If[!MissingQ[node["children"]], - node["children"] - // Map[ToDocumentSymbolImpl[doc, #]&], - {} - ] - ] // Flatten - ) - |>] - ), - _CellNode :> ( - Join[ - If[!MissingQ[node["codeRange"]], - node["codeRange"] - // Map[CellToAST[doc, #]&] - // Flatten - // Map[ToDocumentSymbolImpl], - {} - ], - If[!MissingQ[node["children"]], - node["children"] - // Map[ToDocumentSymbolImpl[doc, #]&], - {} - ] - ] // Flatten - ) - }] +ToDocumentSymbolImpl[doc_TextDocument, node_CellNode] := ( + Join[ + If[!MissingQ[node["codeRange"]], + node["codeRange"] + // Map[CellToAST[doc, #]&] + // Flatten + // Map[ToDocumentSymbolImpl], + {} + ], + If[!MissingQ[node["children"]], + node["children"] + // Map[ToDocumentSymbolImpl[doc, #]&], + {} + ] + ] + // Flatten + // If[!AnonymousStyleQ[node["style"]], + DocumentSymbol[<| + "name" -> node["name"], + "detail" -> node["style"], + "kind" -> If[node["style"] == "Package", + (* This shouldn't be reachable if "Package" is an anonymous style. *) + SymbolKind["Package"], + SymbolKind["String"] + ], + "range" -> ToLspRange[doc, node["range"]], + "selectionRange" -> node["selectionRange"], + "children" -> # + |>]&, + Identity + ] ) ToDocumentSymbolImpl[node_] := ( @@ -621,6 +616,7 @@ ToDocumentSymbolImpl[node_] := ( ) +(* Convert the line range of the given document to LSP Range. *) ToLspRange[doc_TextDocument, {startLine_Integer, endLine_Integer}] := LspRange[<| "start" -> LspPosition[<| "line" -> startLine - 1, @@ -641,8 +637,9 @@ ToLspRange[doc_TextDocument, {startLine_Integer, endLine_Integer}] := LspRange[< |>] -GetSymbolList[node_] := ( - node +(* Get all the symbols in the specified nested list AST node. *) +GetSymbolList[nestedList_] := ( + nestedList // Replace[{ AstPattern["Function"][functionName:"List", arguments_] :> ( arguments @@ -1253,6 +1250,22 @@ FindTopLevelSymbols[node_, name_String] := ( (*CodeAction*) +$referencePageCache = <||> + +hasReferencePage[symbol_String] := ( + If[$referencePageCache // KeyMemberQ[symbol], + $referencePageCache[symbol], + $referencePageCache[symbol] = + FindFile[FileNameJoin[{"ReferencePages", "Symbols", symbol <> ".nb"}]] + // If[!FailureQ[#] && + (* FindFile is case-insensitive on Windows. Needs AbsoluteFileName to confirm. *) + (!$OperatingSystem == "Windows" || AbsoluteFileName[#] == #), + #, + Missing["NotFound"] + ]& + ] +) + GetCodeActionsInRange[doc_TextDocument, range_LspRange] := With[ { startPos = {range["start"]["line"] + 1, range["start"]["character"] + 1}, @@ -1264,27 +1277,24 @@ GetCodeActionsInRange[doc_TextDocument, range_LspRange] := With[ CellToAST[doc, lineRange] // (ast \[Function] ( FirstCase[ - ast, + ast, AstPattern["Token"][tokenString_]?(( (* The token node overlaps the range *) CompareNodePosition[#, startPos, -1] >= 0 && CompareNodePosition[#, endPos, 1] <= 0 )&) :> ( - FindFile[FileNameJoin[{"ReferencePages", "Symbols", tokenString <> ".nb"}]] - // If[!FailureQ[#] && - (* FindFile is case-insensitive on Windows. Needs AbsoluteFileName to confirm. *) - ($OperatingSystem == "Windows" \[Implies] AbsoluteFileName[#] == #), + hasReferencePage[tokenString] + // Replace[referencePath_?(MissingQ /* Not) :> ( LspCodeAction[<| "title" -> "Documentation: " <> tokenString, "kind" -> CodeActionKind["Empty"], "command" -> <| "title" -> "Documentation: " <> tokenString, "command" -> "openRef", - "arguments" -> {#} + "arguments" -> {referencePath} |> - |>], - Missing["NotFound"] - ]& + |>] + )] ), Missing["NotFound"], AstLevelspec["DataWithSource"], diff --git a/test/WolframLanguageServer/TextDocumentTest.wl b/test/WolframLanguageServer/TextDocumentTest.wl index 1e81c22..e2ee7f9 100644 --- a/test/WolframLanguageServer/TextDocumentTest.wl +++ b/test/WolframLanguageServer/TextDocumentTest.wl @@ -182,6 +182,77 @@ VerificationTest[ TestID -> "ToDocumentSymbolEmptySymbol1" ], +VerificationTest[ + ToDocumentSymbol[TextDocument[<| + "text" -> { + "(* " ~~ "::Section::" ~~ " *)", + "(*section name*)", + "", + "", + "(* " ~~ "::Subsection::Closed::" ~~ " *)", + "(*section name*)", + "", + "" + } + |>]], + { + DocumentSymbol[<| + "name" -> "section name", + "detail" -> "Section", + "kind" -> 15, + "range" -> LspRange[<| + "start" -> LspPosition[<| + "line" -> 0, + "character" -> 0 + |>], + "end" -> LspPosition[<| + "line" -> 7, + "character" -> 0 + |>] + |>], + "selectionRange" -> LspRange[<| + "start" -> LspPosition[<| + "line" -> 1, + "character" -> 2 + |>], + "end" -> LspPosition[<| + "line" -> 1, + "character" -> 14 + |>] + |>], + "children" -> { + DocumentSymbol[<| + "name" -> "section name", + "detail" -> "Subsection", + "kind" -> 15, + "range" -> LspRange[<| + "start" -> LspPosition[<| + "line" -> 4, + "character" -> 0 + |>], + "end" -> LspPosition[<| + "line" -> 7, + "character" -> 0 + |>] + |>], + "selectionRange" -> LspRange[<| + "start" -> LspPosition[<| + "line" -> 5, + "character" -> 2 + |>], + "end" -> LspPosition[<| + "line" -> 5, + "character" -> 14 + |>] + |>], + "children"->{} + |>] + } + |>] + }, + TestID -> "ToDocumentSymbolCompoundStyle1" +], + VerificationTest[ FindAllCodeRanges[TextDocument[<| "text" -> { @@ -199,7 +270,7 @@ VerificationTest[ "character" -> 30 |>] |>]}, - TestID -> "FindAllCodeRangePackage1" + TestID -> "FindAllCodeRangesPackage1" ], VerificationTest[ @@ -220,7 +291,7 @@ VerificationTest[ "character" -> 30 |>] |>]}, - TestID -> "FindAllCodeRangePackage2" + TestID -> "FindAllCodeRangesPackage2" ], VerificationTest[ @@ -250,7 +321,7 @@ VerificationTest[ |>] |>] }, - TestID -> "FindAllCodeRangeSection1" + TestID -> "FindAllCodeRangesSection1" ], VerificationTest[ @@ -278,7 +349,7 @@ VerificationTest[ |>] |>] }, - TestID -> "FindAllCodeRangeSection2" + TestID -> "FindAllCodeRangesSection2" ], VerificationTest[ @@ -304,7 +375,7 @@ VerificationTest[ |>] |>] }, - TestID -> "FindAllCodeRangeSection3" + TestID -> "FindAllCodeRangesSection3" ], VerificationTest[ @@ -333,7 +404,52 @@ VerificationTest[ |>] |>] }, - TestID -> "FindAllCodeRangeTwoSection1" + TestID -> "FindAllCodeRangesTwoSection1" +], + +VerificationTest[ + FindAllCodeRanges[TextDocument[<| + "text" -> { + "(* " ~~ "::UnknownStyle::" ~~ " *)", + "(*style title*)", + "", + "", + "(* code range with one line *)", + "(* " ~~ "::UnknownStyle::" ~~ " *)", + "(*style title*)", + "", + "", + "(* code range with two lines *)", + "(* code range with two lines *)", + "(* " ~~ "::UnknownStyle::" ~~ " *)", + "(*style title*)", + "", + "" + } + |>]], + { + LspRange[<| + "start" -> LspPosition[<| + "line" -> 4, + "character" -> 0 + |>], + "end" -> LspPosition[<| + "line" -> 5, + "character" -> 0 + |>] + |>], + LspRange[<| + "start" -> LspPosition[<| + "line" -> 9, + "character" -> 0 + |>], + "end" -> LspPosition[<| + "line" -> 11, + "character" -> 0 + |>] + |>] + }, + TestID -> "FindAllCodeRangesMultipleUnknownStyles1" ], VerificationTest[ From 2e2e426210528efabf90046b7b763113f4f52bb6 Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Mon, 5 Oct 2020 01:28:47 -0700 Subject: [PATCH 15/32] Update initial checks * Different definitions based on paclets. * Change dependencies version to wildcard format * Remove unnecessary logging and timing * Rename WolframLanguageServer`Version to WolframLanguageServer`$Version --- init.wls | 5 +- src/WolframLanguageServer/Server.wl | 231 ++++++++++++++-------------- 2 files changed, 122 insertions(+), 114 deletions(-) diff --git a/init.wls b/init.wls index 918d468..fc0c0f2 100644 --- a/init.wls +++ b/init.wls @@ -18,6 +18,7 @@ If[$OperatingSystem == "Windows" && $FrontEnd === Null, SetOptions[$Output, FormatType -> OutputForm] ] + (* ::Subsection:: *) (*RootDirectory*) @@ -117,10 +118,10 @@ Options: (*Version*) -WolframLanguageServer`Version = "0.2.2" +WolframLanguageServer`$Version = "0.2.2" If[MemberQ[WolframLanguageServer`CommandLine, "-v" | "--version"], Print[" -Wolfram Language Server " <> WolframLanguageServer`Version <> " running on +Wolfram Language Server " <> WolframLanguageServer`$Version <> " running on Wolfram Language " <> $Version <> "\n"]; Quit[]; ]; diff --git a/src/WolframLanguageServer/Server.wl b/src/WolframLanguageServer/Server.wl index 376d596..27db824 100644 --- a/src/WolframLanguageServer/Server.wl +++ b/src/WolframLanguageServer/Server.wl @@ -1089,7 +1089,7 @@ handleRequest["textDocument/definition", msg_, state_] := With[ sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], "result" -> FindDefinitions[doc, pos] - |>]] // AbsoluteTiming // First // LogDebug; + |>]]; {"Continue", state} ] @@ -1109,7 +1109,7 @@ handleRequest["textDocument/references", msg_, state_] := With[ sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], "result" -> FindReferences[doc, pos, "IncludeDeclaration" -> includeDeclaration] - |>]] // AbsoluteTiming // First // LogDebug; + |>]]; {"Continue", state} ] @@ -1548,33 +1548,29 @@ MessageType = <| |> showMessage[message_String, msgType_String, state_WorkState] := ( - MessageType[msgType] - // Replace[_?MissingQ :> MessageType["Error"]] - // LogDebug - // (type \[Function] - sendMessage[state["client"], NotificationMessage[<| - "method" -> "window/showMessage", - "params" -> <| - "type" -> type, - "message" -> message - |> - |>]] - ) + sendMessage[state["client"], NotificationMessage[<| + "method" -> "window/showMessage", + "params" -> <| + "type" -> ( + MessageType[msgType] + // Replace[_?MissingQ :> MessageType["Error"]] + ), + "message" -> message + |> + |>]] ) logMessage[message_String, msgType_String, state_WorkState] := ( - MessageType[msgType] - // Replace[_?MissingQ :> MessageType["Error"]] - // LogDebug - // (type \[Function] - sendMessage[state["client"], NotificationMessage[<| - "method" -> "window/logMessage", - "params" -> <| - "type" -> type, - "message" -> message - |> - |>]] - ) + sendMessage[state["client"], NotificationMessage[<| + "method" -> "window/logMessage", + "params" -> <| + "type" -> ( + MessageType[msgType] + // Replace[_?MissingQ :> MessageType["Error"]] + ), + "message" -> message + |> + |>]] ) @@ -1770,114 +1766,125 @@ defaultConfig = <| (* ::Subsection:: *) -(*check upgrades*) +(*initial checks*) initialCheck[state_WorkState] := ( checkDependencies[state]; - If[ - DateDifference[ - DateObject[state["config"]["configFileConfig"]["lastCheckForUpgrade"]], - Today - ] < ServerConfig["updateCheckInterval"], - logMessage[ - "Upgrade not checked, only a few days after the last check.", - "Log", - state - ], - (* check for upgrade if not checked for more than checkInterval days *) - checkGitRepo[state]; - (* ReplaceKey[state["config"], "lastCheckForUpgrade" -> DateString[Today]] - // saveConfig *) - ]; + checkUpdates[state]; {"Continue", state} ) -checkGitRepo[state_WorkState] := ( - Check[Needs["GitLink`"], - showMessage[ - "The GitLink is not installed to the current Wolfram kernel, please check upgrades via git manually.", - "Info", - state - ]; - Return[] - ] // Quiet; +(* ::Subsubsection:: *) +(*checkDependencies*) + - If[!GitLink`GitRepoQ[WolframLanguageServer`RootDirectory], +If[FindFile["PacletManager`"] // FailureQ, + checkDependencies[state_Workstate] := ( showMessage[ - "Wolfram Language Server is not in a git repository, cannot detect upgrades.", + "The PacletManager is not installed to the current Wolfram kernel, please check dependencies manually.", "Info", state - ]; - Return[] - ]; + ] + ), + Needs["PacletManager`"]; + checkDependencies[state_WorkState] := With[ + { + dependencies = { + {"CodeParser", "1.*"}, + {"CodeInspector", "1.*"} + } + }, - With[{repo = GitLink`GitOpen[WolframLanguageServer`RootDirectory]}, - If[GitLink`GitProperties[repo, "HeadBranch"] != "master", + dependencies + // Select[PacletFind /* MatchQ[{}]] + // Replace[ + missingDeps:Except[{}] :> ( + StringRiffle[missingDeps, ", ", "-"] + // StringTemplate[StringJoin[ + "These dependencies with correct versions need to be installed or upgraded: ``, ", + "otherwise the server may malfunction. ", + "Please see the [Installation](https://github.com/kenkangxgwe/lsp-wl/blob/master/README.md#installation) section for details." + ]] + // showMessage[#, "Warning", state]& + ) + ] + ] +] + + +(* ::Subsubsection:: *) +(*checkUpdates*) + + +Check[ + Needs["GitLink`"]; + checkUpdates[state_WorkState] := ( + (* check for upgrade if not checked for more than checkInterval days *) + If[ + DateDifference[ + DateObject[state["config"]["configFileConfig"]["lastCheckForUpgrade"]], + Today + ] < ServerConfig["updateCheckInterval"], logMessage[ - "Upgrade not checked, the current branch is not 'master'.", + "Upgrade not checked, only a few days after the last check.", "Log", state - ], - GitLink`GitAheadBehind[repo, "master", GitLink`GitUpstreamBranch[repo, "master"]] - // Replace[ - {_, _?Positive} :> ( - showMessage[ - "A new version detected, please close the server and use 'git pull' to upgrade.", - "Info", - state - ] - ) - ] - ] - ]; -) - -If[$VersionNumber >= 12.1, - pacletInstalledQ[{name_String, version_String}] := ( - PacletObject[name -> version] - // FailureQ // Not - ), - pacletInstalledQ[{name_String, version_String}] := ( - PacletManager`PacletInformation[{name, version}] - // MatchQ[{}] // Not - ) -] + ]; + Return[] + (* ReplaceKey[state["config"], "lastCheckForUpgrade" -> DateString[Today]] + // saveConfig *) + ]; -checkDependencies[state_WorkState] := With[ - { - dependencies = { - {"CodeParser", "1.0"}, - {"CodeInspector", "1.0"} - } - }, + If[!GitLink`GitRepoQ[WolframLanguageServer`RootDirectory], + showMessage[ + "Wolfram Language Server is not in a git repository, cannot detect upgrades.", + "Log", + state + ]; + Return[] + ]; - Check[Needs["PacletManager`"], - showMessage[ - "The PacletManager is not installed to the current Wolfram kernel, please check dependencies manually.", + With[{repo = GitLink`GitOpen[WolframLanguageServer`RootDirectory]}, + If[GitLink`GitProperties[repo, "HeadBranch"] != "master", + showMessage[ + "Upgrade not checked, the current branch is not 'master'.", + "Log", + state + ], + GitLink`GitAheadBehind[repo, "master", GitLink`GitUpstreamBranch[repo, "master"]] + // Replace[ + {_, _?Positive} :> ( + showMessage[ + "A new version detected, please close the server and use 'git pull' to upgrade.", + "Info", + state + ] + ) + ] + ] + ]; + ), + checkUpdates[state_WorkState] := ( + (* + GitLink is not a native paclet for Mathematica / Wolfram Engine. + Don't show the message by default. + *) + (* showMessage[ + "The GitLink is not installed to the current Wolfram kernel, please check upgrades via git manually.", "Info", state - ]; - Return[] - ]; + ]; *) + Null + ) +] // Quiet; - dependencies - // Select[pacletInstalledQ /* Not] - // Replace[ - missingDeps:Except[{}] :> ( - StringRiffle[missingDeps, ", ", "-"] - // StringTemplate[StringJoin[ - "These dependencies with correct versions need to be installed or upgraded: ``, ", - "otherwise the server may malfunction. ", - "Please see the [Installation](https://github.com/kenkangxgwe/lsp-wl/blob/master/README.md#installation) section for details." - ]] - // showMessage[#, "Warning", state]& - ) - ] -] + +(* ::Subsection:: *) +(*Constant Functions*) -WLServerVersion[] := WolframLanguageServer`Version; +WLServerVersion[] := WolframLanguageServer`$Version; WLServerDebug[] := Print["This is a debug function."]; From 08871895333e8ded83b21abc8604de85cb51d4bb Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Mon, 5 Oct 2020 01:30:25 -0700 Subject: [PATCH 16/32] Add capatibility for CodeParser/CodeInspector 1.1 * Change "TabWidth" option to 1. * Lower the severity of "Error" and "Warning" * Replace "**" in Lint to quote. --- README.md | 4 ++-- src/WolframLanguageServer/TextDocument.wl | 26 +++++++++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index ab4efc8..f6fad1c 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,8 @@ would certainly work too. git clone https://github.com/kenkangxgwe/lsp-wl.git ``` -2. Install the dependent paclets with the correct versions (currently 1.0) from -the Wolfram kernel / Mathematica. +2. Install the dependent paclets with the correct versions (currently 1.0 or +later) from the Wolfram kernel / Mathematica. (_This will cost some time for the first time_) : ``` mathematica PacletInstall["CodeParser"] diff --git a/src/WolframLanguageServer/TextDocument.wl b/src/WolframLanguageServer/TextDocument.wl index 7ef05fb..51d1838 100644 --- a/src/WolframLanguageServer/TextDocument.wl +++ b/src/WolframLanguageServer/TextDocument.wl @@ -351,7 +351,7 @@ CellToAST[doc_TextDocument, {startLine_, endLine_}] := ( {StringRepeat::intp (* before 12.0 *)} ] // Quiet, #]& - // CodeParser`CodeParse + // CodeParser`CodeParse[#, "TabWidth" -> 1]& // Part[#, 2]& ) @@ -828,9 +828,9 @@ DiagnoseDoc[doc_TextDocument] := ( // Replace[{_String?(StringStartsQ["#!"]), restLines___} :> ({"", restLines})] // StringRiffle[#, "\n"]& // Replace[err:Except[_String] :> (LogError[doc]; "")] - // CodeInspector`CodeInspect + // CodeInspector`CodeInspect[#, "TabWidth" -> 1]& // Replace[_?FailureQ -> {}] - // ReplaceAll[CodeInspector`InspectionObject[tag_, description_, severity_, data_] :> Diagnostic[<| + // Cases[CodeInspector`InspectionObject[tag:Except["BadSymbol"], description_, severity_, data_] :> Diagnostic[<| "range" -> ( data // Key[CodeParser`Source] @@ -844,17 +844,17 @@ DiagnoseDoc[doc_TextDocument] := ( severity // Replace[{ "Fatal" -> "Error", + "Error" -> "Warning", + "Warning" -> "Information", "Formatting"|"Remark" -> "Hint" }] - // Replace[{ - "Warning" :> ( - tag - // Replace[{ - "ExperimentalSymbol" -> "Hint", - _ -> "Warning" - }] - ) - }] + // (newSeverity \[Function] ( + tag + // Replace[{ + "ExperimentalSymbol" -> "Hint", + _ -> newSeverity + }] + )) // DiagnosticSeverity ), "source" -> "Wolfram", @@ -865,7 +865,7 @@ DiagnoseDoc[doc_TextDocument] := ( (* // ReplaceAll[{CodeInspector`Format`LintMarkup[content_, ___] :> ( ToString[content] )}] *) - // StringReplace["``" -> "\""] + // StringReplace["``"|"**" -> "\""] ] ) |>]] From 1808f920b28f08e42002cb28d81f0b585ee6ce48 Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Wed, 9 Sep 2020 20:18:42 -0700 Subject: [PATCH 17/32] Refine: documentation format * Use whitespaces instead of &-escaped characters * Don't show Options when empty --- src/WolframLanguageServer/Token.wl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/WolframLanguageServer/Token.wl b/src/WolframLanguageServer/Token.wl index 3a8b6b0..cdf9be3 100644 --- a/src/WolframLanguageServer/Token.wl +++ b/src/WolframLanguageServer/Token.wl @@ -165,7 +165,7 @@ GenHeader[token_String, tag_String, o: OptionsPattern[]] := ( } // Through // Apply[ If[OptionValue["Format"] == MarkupKind["Markdown"], - StringTemplate["**`1`** `2` (`3`)\n"], + StringTemplate["**`1`** `2` `3`\n"], StringTemplate["`1`\t(`3`)\n"] ] ] @@ -205,7 +205,8 @@ Options[GenAttributes] = { GenAttributes[token_String, o:OptionsPattern[]] := ( Attributes[token] // Replace[_Attributes -> {}] - // StringRiffle[#, ", "]& + // StringRiffle[#, {"(", ", ", ")"}]& + // Replace["()" -> ""] ) Options[GenOptions] = { From 7f65679c17cc8baf6ceb2c76f2fd950558c98a2e Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Mon, 5 Oct 2020 02:09:33 -0700 Subject: [PATCH 18/32] Modify tests to fit 1808f920b28f08e42002cb28d81f0b585ee6ce48 --- test/WolframLanguageServer/TokenTest.wl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/WolframLanguageServer/TokenTest.wl b/test/WolframLanguageServer/TokenTest.wl index 7f382b6..9d3920a 100644 --- a/test/WolframLanguageServer/TokenTest.wl +++ b/test/WolframLanguageServer/TokenTest.wl @@ -28,7 +28,7 @@ Needs["WolframLanguageServer`TextDocument`"] VerificationTest[ TokenDocumentation["BeginPackage", "usage"], StringJoin[ - "**BeginPackage** [*reference*](https://reference.wolfram.com/language/ref/BeginPackage.html) (Protected)\n\n\n", + "**BeginPackage** [*reference*](https://reference.wolfram.com/language/ref/BeginPackage.html) (Protected)\n\n\n", "```mathematica\n", "BeginPackage[\"context`\"]\n", "```\n\n", @@ -44,7 +44,7 @@ VerificationTest[ VerificationTest[ TokenDocumentation["Replace", "usage"], StringJoin[ - "**Replace** [*reference*](https://reference.wolfram.com/language/ref/Replace.html) (Protected)\n\n\n", + "**Replace** [*reference*](https://reference.wolfram.com/language/ref/Replace.html) (Protected)\n\n\n", "```mathematica\n", "Replace[expr,rules]\n", "```\n\n", @@ -69,7 +69,7 @@ If[$VersionNumber >= 12.0, VerificationTest[ TokenDocumentation["SlotSequence", "usage"], StringJoin[ - "**SlotSequence** [*reference*](https://reference.wolfram.com/language/ref/SlotSequence.html) (NHoldAll, Protected)\n\n\n", + "**SlotSequence** [*reference*](https://reference.wolfram.com/language/ref/SlotSequence.html) (NHoldAll, Protected)\n\n\n", "```mathematica\n", "## \n", "```\n\n", @@ -93,7 +93,7 @@ If[$FrontEnd === Null, VerificationTest[ TokenDocumentation["$FrontEndSession", "usage"], StringJoin[ - "**$FrontEndSession** [*reference*](https://reference.wolfram.com/language/ref/$FrontEndSession.html) (Protected, ReadProtected)\n\n\n", + "**$FrontEndSession** [*reference*](https://reference.wolfram.com/language/ref/$FrontEndSession.html) (Protected, ReadProtected)\n\n\n", "```mathematica\n", "$FrontEndSession \n", "```\n\n", From 621841f0b80aa283836a504ca8beec35de83e3f9 Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Mon, 5 Oct 2020 02:11:33 -0700 Subject: [PATCH 19/32] :memo: Add CI badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f6fad1c..eea1de0 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ [![Develop with: Wolfram Language](https://img.shields.io/badge/Develop%20with-Wolfram%20Language-%23d81013.svg)](http://www.wolfram.com/language/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![CI](https://github.com/kenkangxgwe/lsp-wl/workflows/CI/badge.svg?branch=develop)](https://github.com/kenkangxgwe/lsp-wl/actions?query=workflow%3ACI+branch%3Adevelop) ![WolframLanguageServerLogo](images/wolfram-language-server-logo-clipped.png) -> by [kenkangxgwe](https://github.com/kenkangxgwe) and [hxianglong](https://github.com/huxianglong) **Table of Contents** From dcc9e76366acc526c779c27b7683ac35bf166dcf Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Thu, 14 Jan 2021 23:33:32 -0800 Subject: [PATCH 20/32] Add Cache to TextDocument - This accelerates the divideCells and GetXxxAtPosition functions - Might have side-effects on arbitrary input. --- src/WolframLanguageServer/Server.wl | 80 ++- src/WolframLanguageServer/TextDocument.wl | 746 +++++++++++++--------- src/WolframLanguageServer/Token.wl | 2 +- 3 files changed, 476 insertions(+), 352 deletions(-) diff --git a/src/WolframLanguageServer/Server.wl b/src/WolframLanguageServer/Server.wl index 27db824..a9daa4d 100644 --- a/src/WolframLanguageServer/Server.wl +++ b/src/WolframLanguageServer/Server.wl @@ -103,7 +103,7 @@ ServerCapabilities = <| ServerConfig = <| "updateCheckInterval" -> Quantity[7, "Days"], - (* cached results *) + (* cached results *) "cachedRequests" -> { "textDocument/signatureHelp", "textDocument/documentSymbol", @@ -637,7 +637,7 @@ RPCPatterns = <| constructRPCBytes[msg_Association] := ( Check[ - ExportByteArray[msg, "RawJSON", "Compact" -> True], + ExportByteArray[msg, "RawJSON", "Compact" -> True], (* if the result is not able to convert to JSON, returns an error respond @@ -915,15 +915,17 @@ getScheduleTaskParameter[method:"textDocument/publishDiagnostics", uri_String, s handleRequest["textDocument/hover", msg_, state_] := With[ { - doc = state["openedDocs"][msg["params"]["textDocument"]["uri"]], + uri = msg["params"]["textDocument"]["uri"], pos = LspPosition[msg["params"]["position"]] }, sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], - "result" -> GetHoverAtPosition[doc, pos] + "result" -> GetHoverAtPosition[ + state["openedDocs"][uri], + pos + ] |>]]; - {"Continue", state} ] @@ -952,9 +954,10 @@ cacheResponse[method:"textDocument/signatureHelp", msg_, state_WorkState] := Wit ReplaceKey[ {"caches", method, uri} -> RequestCache[<| "cachedTime" -> Now, - "result" -> ( - GetSignatureHelp[state["openedDocs"][uri], pos] - ) + "result" -> GetSignatureHelp[ + state["openedDocs"][uri], + pos + ] |>] ] ] @@ -984,7 +987,7 @@ getCache[method:"textDocument/signatureHelp", msg_, state_WorkState] := ( handleRequest["textDocument/completion", msg_, state_] := Module[ { - doc = state["openedDocs"][msg["params"]["textDocument"]["uri"]], + uri = msg["params"]["textDocument"]["uri"], pos = LspPosition[msg["params"]["position"]] }, @@ -995,7 +998,10 @@ handleRequest["textDocument/completion", msg_, state_] := Module[ "id" -> msg["id"], "result" -> <| "isIncomplete" -> False, - "items" -> GetTokenCompletionAtPostion[doc, pos] + "items" -> GetTokenCompletionAtPostion[ + state["openedDocs"][uri], + pos + ] |> |>]] ), @@ -1004,9 +1010,10 @@ handleRequest["textDocument/completion", msg_, state_] := Module[ "id" -> msg["id"], "result" -> <| "isIncomplete" -> True, - "items" -> ( - GetTriggerKeyCompletion[doc, pos] - ) + "items" -> GetTriggerKeyCompletion[ + state["openedDocs"][uri], + pos + ] |> |>]] ), @@ -1015,9 +1022,10 @@ handleRequest["textDocument/completion", msg_, state_] := Module[ "id" -> msg["id"], "result" -> <| "isIncomplete" -> False, - "items" -> ( - GetIncompleteCompletionAtPosition[doc, pos] - ) + "items" -> GetIncompleteCompletionAtPosition[ + state["openedDocs"][uri], + pos + ] |> |>]]; ) @@ -1082,13 +1090,16 @@ handleRequest["completionItem/resolve", msg_, state_] := With[ handleRequest["textDocument/definition", msg_, state_] := With[ { - doc = state["openedDocs"][msg["params"]["textDocument"]["uri"]], + uri = msg["params"]["textDocument"]["uri"], pos = LspPosition[msg["params"]["position"]] }, sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], - "result" -> FindDefinitions[doc, pos] + "result" -> FindDefinitions[ + state["openedDocs"][uri], + pos + ] |>]]; {"Continue", state} @@ -1101,14 +1112,18 @@ handleRequest["textDocument/definition", msg_, state_] := With[ handleRequest["textDocument/references", msg_, state_] := With[ { - doc = state["openedDocs"][msg["params"]["textDocument"]["uri"]], + uri = msg["params"]["textDocument"]["uri"], pos = LspPosition[msg["params"]["position"]], includeDeclaration = msg["params"]["context"]["includeDeclaration"] }, sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], - "result" -> FindReferences[doc, pos, "IncludeDeclaration" -> includeDeclaration] + "result" -> FindReferences[ + state["openedDocs"][uri], + pos, + "IncludeDeclaration" -> includeDeclaration + ] |>]]; {"Continue", state} @@ -1121,14 +1136,16 @@ handleRequest["textDocument/references", msg_, state_] := With[ handleRequest[method:"textDocument/documentHighlight", msg_, state_WorkState] := With[ { - id = msg["id"], uri = msg["params"]["textDocument"]["uri"], pos = LspPosition[msg["params"]["position"]] }, sendMessage[state["client"], ResponseMessage[<| - "id" -> id, - "result" -> FindDocumentHighlight[state["openedDocs"][uri], pos] + "id" -> msg["id"], + "result" -> FindDocumentHighlight[ + state["openedDocs"][uri], + pos + ] |>]]; {"Continue", state} @@ -1162,10 +1179,7 @@ cacheResponse[method:"textDocument/documentSymbol", msg_, state_WorkState] := Wi ReplaceKey[ {"caches", method, uri} -> RequestCache[<| "cachedTime" -> Now, - "result" -> ( - state["openedDocs"][uri] - // ToDocumentSymbol - ) + "result" -> ToDocumentSymbol[state["openedDocs"][uri]] |>] ] ] @@ -1199,13 +1213,16 @@ getCache[method:"textDocument/documentSymbol", msg_, state_WorkState] := ( handleRequest["textDocument/codeAction", msg_, state_] := With[ { - doc = state["openedDocs"][msg["params"]["textDocument"]["uri"]], + uri = msg["params"]["textDocument"]["uri"], range = ConstructType[msg["params"]["range"], LspRange] }, sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], - "result" -> GetCodeActionsInRange[doc, range] + "result" -> GetCodeActionsInRange[ + state["openedDocs"][uri], + range + ] |>]]; {"Continue", state} @@ -1234,10 +1251,7 @@ cacheResponse[method:"textDocument/documentColor", msg_, state_WorkState] := Wit ReplaceKey[ {"caches", method, uri} -> RequestCache[<| "cachedTime" -> Now, - "result" -> ( - state["openedDocs"][uri] - // FindDocumentColor - ) + "result" -> FindDocumentColor[state["openedDocs"][uri]] |>] ] ] diff --git a/src/WolframLanguageServer/TextDocument.wl b/src/WolframLanguageServer/TextDocument.wl index 51d1838..a5ac1e1 100644 --- a/src/WolframLanguageServer/TextDocument.wl +++ b/src/WolframLanguageServer/TextDocument.wl @@ -41,6 +41,25 @@ Needs["WolframLanguageServer`AstPatterns`"] Needs["WolframLanguageServer`ColorTable`"] +(* ::Section:: *) +(*Cache*) + +(* + Idealy, cache is a side-effect that doesn't violate purity and referential + transparency. Thus, monad (or something equivalent) is not needed. + However, as for now, the cache is indexed by the URIs, so an arbitrary + input may results in different output due to the last input. +*) + + +(* $Cell stores the results from divideCells for each uri *) +$Cell = <||> + + +(* $CodeRange stores the key-value pairs of codeRange and its AST for each uri *) +$CodeRange = <||> + + (* ::Section:: *) (*CodeParser Shims*) @@ -73,10 +92,10 @@ DeclareType[TextDocument, <| |>] TextDocument /: ToString[textDocument_TextDocument] := StringJoin["TextDocument[<|", - "\"uri\" -> ", textDocument["uri"], ", ", + "\"uri\" -> ", ToString[textDocument["uri"]], ", ", "\"text\" -> ", textDocument["text"] // Shallow // ToString, ", ", "\"version\" -> ", ToString[textDocument["version"]], ", ", - "\"cell\" -> ", ToString[textDocument["cell"]], + "\"lastUpdate\" -> ", ToString[textDocument["lastUpdate"]], ", ", "|>]"] TextDocument /: Format[textDocument_TextDocument] := ToString[textDocument] @@ -107,6 +126,9 @@ ChangeTextDocument[doc_TextDocument, contextChange_TextDocumentContentChangeEven newtext = StringSplit[contextChange["text"], EOL, All] }, + + KeyDropFrom[$Cell, doc["uri"]]; + KeyDropFrom[$CodeRange, doc["uri"]]; ReplaceKey[doc, "text" -> ( contextChange["range"] // Replace[{ @@ -151,7 +173,7 @@ ChangeTextDocument[doc_TextDocument, contextChange_TextDocumentContentChangeEven (* ::Section:: *) -(*CodeCells*) +(*Helper Function*) (* ::Subsection:: *) @@ -168,28 +190,56 @@ DeclareType[CellNode, <| "children" -> {___CellNode} |>] -divideCells[doc_TextDocument] := ( - Position[ - doc["text"], - (* matches style line *) - _?(StringContainsQ["(* " ~~ "::" ~~ Shortest[style___] ~~ "::" ~~ " *)"]), - {1}, Heads -> False + +Options[divideCells] = { + "CodeRange" -> False +} + +divideCells[doc_TextDocument, o:OptionsPattern[]] := ( + If[$Cell[doc["uri"]] // MissingQ, + LogDebug["NewDivide!"]; + Position[ + doc["text"], + (* matches style line *) + _?(StringContainsQ["(* " ~~ "::" ~~ Shortest[style___] ~~ "::" ~~ " *)"]), + {1}, Heads -> False + ] + // Flatten + // Append[Length[doc["text"]] + 1] + // Prepend[0] + // BlockMap[Apply[constructCellNode[doc, #1, #2]&], #, 2, 1]& + // Fold[InsertCell] + // TerminateCell + // Reap + // MapAt[ + Replace[{codeRange_List} :> codeRange] + /* Catenate + /* (Thread[# -> Missing["NotParsed"]]&) + /* Association, + 2 + ] + // Apply[{cell, codeRange} \[Function] ( + If[doc["uri"] // MissingQ // Not, + (* cache if the URI is not missing *) + AssociateTo[$CodeRange, doc["uri"] -> codeRange]; + AssociateTo[$Cell, doc["uri"] -> cell] + ]; + If[OptionValue["CodeRange"], + {cell, codeRange}, + cell + ] + )], + If[OptionValue["CodeRange"], + {$Cell[doc["uri"]] , $CodeRange[doc["uri"]]}, + $Cell[doc["uri"]] + ] ] - // Flatten - // Append[Length[doc["text"]] + 1] - // Prepend[0] - // BlockMap[Apply[constructCellNode[doc, #1, #2]&], #, 2, 1]& - // Fold[InsertCell] - // TerminateCell - // Replace[err:Except[_CellNode] :> ( - LogError["The result of devideCells is not a CellNode " <> ToString[err]] - )] ) constructCellNode[doc_TextDocument, styleLine_Integer, endLine_Integer] := Block[ { - style, title = Missing["Untitled"], codeStart + style, title, codeStart }, style = If[styleLine == 0, @@ -202,19 +252,24 @@ constructCellNode[doc_TextDocument, styleLine_Integer, endLine_Integer] := Block // Replace["" -> "[empty]"] ]; - If[!AnonymousStyleQ[style] && + {title, codeStart} = If[!AnonymousStyleQ[style] && (styleLine + 1 != endLine), - (Part[doc["text"], styleLine + 1] - // StringCases[ - StartOfString ~~ (Whitespace | "") ~~ - "(*" ~~ Longest[t___] ~~ "*)" ~~ - (Whitespace | "") ~~ EndOfString :> t - ] - // Replace[ - {t_, ___} :> (title = t) - ]); - codeStart = findCodeLine[doc, styleLine + 2], - codeStart = findCodeLine[doc, styleLine + 1] + { + (Part[doc["text"], styleLine + 1] + // StringCases[ + StartOfString ~~ (Whitespace | "") ~~ + "(*" ~~ Longest[t___] ~~ "*)" ~~ + (Whitespace | "") ~~ EndOfString :> t + ] + // Replace[ + {t_, ___} :> t + ]), + findCodeLine[doc, styleLine + 2] + }, + { + Missing["Untitled"], + findCodeLine[doc, styleLine + 1] + } ]; CellNode[<| @@ -249,10 +304,12 @@ constructCellNode[doc_TextDocument, styleLine_Integer, endLine_Integer] := Block |>] |>] ], - "codeRange" -> If[codeStart < endLine, {{codeStart, endLine - 1}}, {}], + "codeRange" -> If[codeStart < endLine, + {{codeStart, endLine - 1}} // Sow, + {} + ], "children" -> {} |>] - ] findCodeLine[doc_TextDocument, currentLine_Integer] := ( @@ -336,10 +393,68 @@ HeadingLevel = <| ScriptFileQ[uri_String] := URLParse[uri, "Path"] // Last // FileExtension // EqualTo["wls"] -CellToAST[doc_TextDocument, {startLine_, endLine_}] := ( +(* ::Subsection:: *) +(*Code Range*) + + +getCodeRanges[doc_TextDocument] := ( + divideCells[doc, "CodeRange" -> True] + // Last +) + + +rangeToAst[doc_TextDocument, All] := ( + doc + // { + Identity, + getCodeRanges + /* Keys + } + // Through + // Apply[rangeToAst] + // Catenate +) + + +rangeToAst[doc_TextDocument, range:{_Integer, _Integer}] := rangeToAst[doc, {range}] +rangeToAst[doc_TextDocument, ranges:{{_Integer, _Integer}...}] := With[ + { + uri = doc["uri"] + }, + + ranges + // If[doc["uri"] // MissingQ, + Identity, + (* If cached, get missing ranges only *) + Extract[ + doc + // getCodeRanges + // Lookup[ranges] + // Position[#, _?MissingQ, {1}]& + ] + ] + // Rule[ + Identity, + Map[rangeToCode[doc, #]&] + /* (CodeParser`CodeParse[#, "TabWidth" -> 1]&) + /* (Part[#, All, 2]&) + ] + // Through + // Thread + // If[doc["uri"] // MissingQ, + Values, + Replace[{} -> <||>] + /* (AssociateTo[$CodeRange[uri], #]&) + /* Lookup[uri] + /* Lookup[ranges] + ] + ] + + +rangeToCode[doc_TextDocument, {startLine_Integer, endLine_Integer}] := ( If[startLine == 1 && (doc["text"] // First // StringStartsQ["#!"]), - Return[CellToAST[doc, {2, endLine}]] + Return[rangeToCode[doc, {2, endLine}]] ]; Take[doc["text"], {startLine, endLine}] @@ -351,14 +466,11 @@ CellToAST[doc_TextDocument, {startLine_, endLine_}] := ( {StringRepeat::intp (* before 12.0 *)} ] // Quiet, #]& - // CodeParser`CodeParse[#, "TabWidth" -> 1]& - // Part[#, 2]& ) -CellContainsLine[indexLine_Integer][cell_CellNode] := ( - indexLine // Between[cell["range"]] -) +(* ::Subsection:: *) +(*GetAtPosition*) GetCodeRangeAtPosition[doc_TextDocument, pos_LspPosition] := With[ @@ -366,22 +478,58 @@ GetCodeRangeAtPosition[doc_TextDocument, pos_LspPosition] := With[ line = pos["line"] + 1 }, - FirstCase[ - doc // divideCells, - cell_CellNode?(CellContainsLine[line]) :> cell["codeRange"], - {}, {0, Infinity} - ] + doc + // getCodeRanges + // Keys // SelectFirst[Between[line, #]&] ] +GetTokenAtPosition[doc_TextDocument, pos_LspPosition] := ( + GetCodeRangeAtPosition[doc, pos] + // Replace[{ + codeRange: {startLine_Integer, _Integer} :> ( + Take[doc["text"], codeRange] + // StringRiffle[#, "\n"]& + // CodeParser`CodeTokenize + // SelectFirst[NodeContainsPosition[{ + (pos["line"] + 1) - startLine + 1, + pos["character"] + }]] + ) + }] +) + + +GetAstAtPosition[doc_TextDocument, pos_LspPosition] := ( + GetCodeRangeAtPosition[doc, pos] + // Replace[_?MissingQ -> {}] + // rangeToAst[doc, #]& +) + + +GetSymbolAtPosition[doc_TextDocument, pos_LspPosition] := With[ + { + line = pos["line"] + 1, character = pos["character"] + 1 + }, + + GetAstAtPosition[doc, pos] + // FirstCase[ + #, + AstPattern["Symbol"][symbolName_] + ?(NodeContainsPosition[{line, character}]) :> ( + symbolName + ), + Missing["NotFound"], + AstLevelspec["LeafNodeWithSource"] + ]& +] + + FindAllCodeRanges[doc_TextDocument] := ( - Cases[ - divideCells[doc], - node_CellNode :> node["codeRange"], - {0, Infinity} - ] - // Catenate + doc + // getCodeRanges + // Keys // Map[ToLspRange[doc, #]&] ) @@ -399,7 +547,7 @@ GetDocumentText[doc_TextDocument, range_LspRange] := ( ) -(* ::Section:: *) +(* ::Subsection:: *) (*AST utils*) @@ -451,6 +599,68 @@ SourceToRange[{{startLine_, startCol_}, {endLine_, endCol_}}] := ( ) +(* ::Section:: *) +(*GetFunctionName*) + + +GetFunctionName[doc_TextDocument, pos_LspPosition] := With[ + { + line = pos["line"] + 1, character = pos["character"] + 1 + }, + + GetAstAtPosition[doc, pos] + // (ast \[Function] ( + FirstPosition[ + ast, + _Association?(NodeDataContainsPosition[{line, character}]), + Missing["NotFound", {}], + AstLevelspec["DataWithSource"], + Heads -> False + ] + // Most + // Replace[indices_List :> ( + getFunctionNameImpl[ast, indices] + )] + )) +] + +getFunctionNameImpl[ast_, indices_] := ( + Extract[ast, indices // Replace[{} -> {All}]] + // Replace[{ + AstPattern["Function"][functionName_] :> ( + functionName + // Replace[FunctionPattern["NoSignatureHelp"] -> Missing["NotFound"]] + ), + _ :> ( + indices + // Replace[{ + {} -> Missing["NotFound"], + _ :> ( + getFunctionNameImpl[ast, indices // Most] + ) + }] + ) + }] +) + + +(* ::Section:: *) +(*GetTokenPrefix*) + + +GetTokenPrefix[doc_TextDocument, pos_LspPosition] := ( + GetTokenAtPosition[doc, pos] + // Replace[{ + AstPattern["Token"][tokenString_, data_] :> ( + StringTake[tokenString, pos["character"] - Part[data[CodeParser`Source], 1, 2] + 1] + ), + (* this happens when line is not in codeRange or character == 0 *) + _?MissingQ -> "", + err_ :> (LogError["Unknown token node " <> ToString[err]]; "") + }] +) + + (* ::Section:: *) (*documentSymbol*) @@ -465,18 +675,14 @@ ToDocumentSymbol[doc_TextDocument] := ( ToDocumentSymbolImpl[doc_TextDocument, node_CellNode] := ( Join[ - If[!MissingQ[node["codeRange"]], - node["codeRange"] - // Map[CellToAST[doc, #]&] - // Flatten - // Map[ToDocumentSymbolImpl], - {} - ], - If[!MissingQ[node["children"]], - node["children"] - // Map[ToDocumentSymbolImpl[doc, #]&], - {} - ] + node["codeRange"] + // Replace[_?MissingQ -> {}] + // rangeToAst[doc, #]& + // Flatten + // Map[ToDocumentSymbolImpl], + node["children"] + // Replace[_?MissingQ -> {}] + // Map[ToDocumentSymbolImpl[doc, #]&] ] // Flatten // If[!AnonymousStyleQ[node["style"]], @@ -664,40 +870,35 @@ GetHoverInfo[doc_TextDocument, pos_LspPosition] := With[ line = pos["line"] + 1, character = pos["character"] + 1 }, - GetCodeRangeAtPosition[doc, pos] - // Replace[lineRange:{_Integer, _Integer} :> ( - CellToAST[doc, lineRange] - // (ast \[Function] ( - FirstPosition[ - ast, - _Association?(NodeDataContainsPosition[{line, character}]), - Missing["NotFound", {(* Will be Discarded by Most *)}], - AstLevelspec["DataWithSource"], - Heads -> False - ] - // Most - // Replace[indices_List :> { - getHoverInfoImpl[ast, indices] - // Reap - // Last // Flatten - // DeleteDuplicates, - (* get range *) - ast - // Extract[indices] + GetAstAtPosition[doc, pos] + // (ast \[Function] ( + FirstPosition[ + ast, + _Association?(NodeDataContainsPosition[{line, character}]), + {{(* Will be Discarded by Most *)}}, + AstLevelspec["DataWithSource"], + Heads -> False + ] + // Most + // (indices \[Function] { + getHoverInfoImpl[ast, indices] + // Reap + // Last // Flatten + // DeleteDuplicates, + (* get range *) + ast + // Extract[indices] + // Replace[node:Except[{}] :> ( + node // Last // Key[CodeParser`Source] // Replace[{ _?MissingQ -> Nothing, source_ :> SourceToRange[source] }] - }] - )) - )] - // Replace[ - (* This happens when line not in codeRange or position not in node *) - _?MissingQ :> {{(* empty hover text: *)} (*, no range *)} - ] - + )] + }) + )) ] @@ -738,86 +939,6 @@ getHoverInfoImpl[ast_, {index_Integer, restIndices___}] := ( ) -(* ::Section:: *) -(*GetFunctionName*) - - -GetFunctionName[doc_TextDocument, pos_LspPosition] := With[ - { - line = pos["line"] + 1, character = pos["character"] + 1 - }, - - GetCodeRangeAtPosition[doc, pos] - // Replace[lineRange:{_Integer, _Integer} :> ( - CellToAST[doc, lineRange] - // (ast \[Function] ( - FirstPosition[ - ast, - _Association?(NodeDataContainsPosition[{line, character}]), - Missing["NotFound", {}], - AstLevelspec["DataWithSource"], - Heads -> False - ] - // Most - // Replace[indices_List :> ( - getFunctionNameImpl[ast, indices] - )] - )) - )] -] - -getFunctionNameImpl[ast_, indices_] := ( - Extract[ast, indices // Replace[{} -> {All}]] - // Replace[{ - AstPattern["Function"][functionName_] :> ( - functionName - // Replace[FunctionPattern["NoSignatureHelp"] -> Missing["NotFound"]] - ), - _ :> ( - indices - // Replace[{ - {} -> Missing["NotFound"], - _ :> ( - getFunctionNameImpl[ast, indices // Most] - ) - }] - ) - }] -) - - -(* ::Section:: *) -(*GetTokenPrefix*) - - -GetTokenPrefix[doc_TextDocument, pos_LspPosition] := With[ - { - line = pos["line"] + 1 - }, - - GetCodeRangeAtPosition[doc, pos] - // Replace[lineRange:{rangeStartLine_Integer, _Integer} :> ( - (* get token list *) - Take[doc["text"], lineRange] - // StringRiffle[#, "\n"]& - // CodeParser`CodeTokenize - // SelectFirst[NodeContainsPosition[{ - line - rangeStartLine + 1, - pos["character"] - }]] - // Replace[{ - AstPattern["Token"][tokenString_, data_] :> ( - StringTake[tokenString, pos["character"] - Part[data[CodeParser`Source], 1, 2] + 1] - ), - err_ :> (LogError["Unknown token node " <> ToString[err]]; "") - }] - )] // Replace[ - (* this happens when line is not in codeRange or character == 0 *) - _?MissingQ -> "" - ] -] - - (* ::Section:: *) (*Diagnostics*) @@ -946,86 +1067,81 @@ Options[FindScopeOccurence] = { "BodySearch" -> True } -FindScopeOccurence[doc_TextDocument, pos_LspPosition, o:OptionsPattern[]] := Block[ +FindScopeOccurence[doc_TextDocument, pos_LspPosition, o:OptionsPattern[]] := With[ { - line = pos["line"] + 1, character = pos["character"] + 1, - ast, name + line = pos["line"] + 1, character = pos["character"] + 1 }, - ast = GetCodeRangeAtPosition[doc, pos] - // Replace[lineRange:{_Integer, _Integer} :> ( - CellToAST[doc, lineRange] - )] - // Replace[_?MissingQ :> Return[{{}, {}}]]; - - name = FirstCase[ - ast, - AstPattern["Symbol"][symbolName_] - ?(NodeContainsPosition[{line, character}]) :> ( - symbolName - ), - Missing["NotFound"], - AstLevelspec["LeafNodeWithSource"] - ] - // Replace[_?MissingQ :> Return[{{}, {}}]]; - - LogDebug["Searching for " <> name]; - - FirstCase[ - ast, - ( - AstPattern["Scope"][head_, body_, op_] - ?(NodeContainsPosition[{line, character}]) | - AstPattern["Delayed"][head_, body_, op_] - ?(NodeContainsPosition[{line, character}]) - ) :> Block[ + GetSymbolAtPosition[doc, pos] + // (LogDebug["FindScopeOccurence: " <> ToString[#]];#)& + // Replace[{ + name_String :> Block[ { - headSource + ast = GetAstAtPosition[doc, pos] }, - { - headSource, - If[OptionValue["BodySearch"], - Replace[op, { - FunctionPattern["StaticLocal"] :> - StaticLocalSource[body, name], - FunctionPattern["DynamicLocal"] :> - DynamicLocalSource[body, name] - }], - {} - ] - } - (* a pattern test with inner side effect *) - /; ( - Replace[op, { - FunctionPattern["Scope"] :> - ScopeHeadSymbolSource[op, head, name], - FunctionPattern["Delayed"] :> - DelayedHeadPatternNameSource[head, name] - }] - // ((headSource = #)&) - // MatchQ[Except[{}, _List]] - ) - ], - (* search it the whole doc as a dynamic local *) - { - {}, - OptionValue["GlobalSearch"] - // Replace[{ - True :> DynamicLocalSource[ - CellToAST[doc, {1, doc["text"] // Length}], - name + LogDebug["Searching for " <> name]; + FirstCase[ + ast, + ( + AstPattern["Scope"][head_, body_, op_] + ?(NodeContainsPosition[{line, character}]) | + AstPattern["Delayed"][head_, body_, op_] + ?(NodeContainsPosition[{line, character}]) + ) :> Block[ + { + headSource + }, + + { + headSource, + If[OptionValue["BodySearch"], + Replace[op, { + FunctionPattern["StaticLocal"] :> + StaticLocalSource[body, name], + FunctionPattern["DynamicLocal"] :> + DynamicLocalSource[body, name] + }], + {} + ] + } + (* a pattern test with inner side effect *) + /; ( + Replace[op, { + FunctionPattern["Scope"] :> + ScopeHeadSymbolSource[op, head, name], + FunctionPattern["Delayed"] :> + DelayedHeadPatternNameSource[head, name] + }] + // ((headSource = #)&) + // MatchQ[Except[{}, _List]] + ) ], - "TopLevelOnly" :> ( - CellToAST[doc, {1, doc["text"] // Length}] - // Map[FindTopLevelSymbols[#, name]&] - // Catenate - ), - _ -> {} - }] - }, - {0, Infinity} - ] + (* search it the whole doc as a dynamic local *) + ast = rangeToAst[doc, All]; + OptionValue["GlobalSearch"] + // Replace[{ + True :> ( + { + {}, + DynamicLocalSource[ast, name] + } + ), + "TopLevelOnly" :> ( + { + {}, + ast + // Map[FindTopLevelSymbols[#, name]&] + // Catenate + } + ), + _ -> {{}, {}} + }], + {0, Infinity} + ] + ], + _?MissingQ :> {{}, {}} + }] ] @@ -1272,36 +1388,31 @@ GetCodeActionsInRange[doc_TextDocument, range_LspRange] := With[ endPos = {range["end"]["line"] + 1, range["end"]["character"]} }, - GetCodeRangeAtPosition[doc, range["start"]] - // Replace[lineRange:{_Integer, _Integer} :> ( - CellToAST[doc, lineRange] - // (ast \[Function] ( - FirstCase[ - ast, - AstPattern["Token"][tokenString_]?(( - (* The token node overlaps the range *) - CompareNodePosition[#, startPos, -1] >= 0 && - CompareNodePosition[#, endPos, 1] <= 0 - )&) :> ( - hasReferencePage[tokenString] - // Replace[referencePath_?(MissingQ /* Not) :> ( - LspCodeAction[<| - "title" -> "Documentation: " <> tokenString, - "kind" -> CodeActionKind["Empty"], - "command" -> <| - "title" -> "Documentation: " <> tokenString, - "command" -> "openRef", - "arguments" -> {referencePath} - |> - |>] - )] - ), - Missing["NotFound"], - AstLevelspec["DataWithSource"], - Heads -> False - ] - )) - )] + GetAstAtPosition[doc, range["start"]] + // FirstCase[ + #, + AstPattern["Token"][tokenString_]?(( + (* The token node overlaps the range *) + CompareNodePosition[#, startPos, -1] >= 0 && + CompareNodePosition[#, endPos, 1] <= 0 + )&) :> ( + hasReferencePage[tokenString] + // Replace[referencePath_?(MissingQ /* Not) :> ( + LspCodeAction[<| + "title" -> "Documentation: " <> tokenString, + "kind" -> CodeActionKind["Empty"], + "command" -> <| + "title" -> "Documentation: " <> tokenString, + "command" -> "openRef", + "arguments" -> {referencePath} + |> + |>] + )] + ), + Missing["NotFound"], + AstLevelspec["DataWithSource"], + Heads -> False + ]& // List // DeleteMissing ] @@ -1311,42 +1422,13 @@ GetCodeActionsInRange[doc_TextDocument, range_LspRange] := With[ (*DocumentColor*) -FindDocumentColor[doc_TextDocument] := With[ - { - ast = CellToAST[doc, {1, doc["text"] // Length}] - }, - - Join[ - Cases[ - ast, - AstPattern["NamedColor"][color_, data_] :> ( - ColorInformation[<| - "range" -> ( - data - // Key[CodeParser`Source] - // SourceToRange - ), - "color" -> ( - ColorConvert[ToExpression[color], "RGB"] - // Apply[List] - // ToLspColor - ) - |>] - ), - AstLevelspec["LeafNodeWithSource"] - ], - Cases[ - ast, - AstPattern["ColorModel"][model_, params_, data_] :> With[ - { - color = ( - params - // Map[CodeParser`FromNode] - // Apply[ToExpression[model]] - ) - }, - - If[ColorQ[color], +FindDocumentColor[doc_TextDocument] := ( + rangeToAst[doc, All] + // (ast \[Function] ( + Join[ + Cases[ + ast, + AstPattern["NamedColor"][color_, data_] :> ( ColorInformation[<| "range" -> ( data @@ -1354,18 +1436,46 @@ FindDocumentColor[doc_TextDocument] := With[ // SourceToRange ), "color" -> ( - ColorConvert[color, "RGB"] + ColorConvert[ToExpression[color], "RGB"] // Apply[List] // ToLspColor ) - |>], - Nothing - ] + |>] + ), + AstLevelspec["LeafNodeWithSource"] ], - AstLevelspec["CallNodeWithArgs"] + Cases[ + ast, + AstPattern["ColorModel"][model_, params_, data_] :> With[ + { + color = ( + params + // Map[CodeParser`FromNode] + // Apply[ToExpression[model]] + ) + }, + + If[ColorQ[color], + ColorInformation[<| + "range" -> ( + data + // Key[CodeParser`Source] + // SourceToRange + ), + "color" -> ( + ColorConvert[color, "RGB"] + // Apply[List] + // ToLspColor + ) + |>], + Nothing + ] + ], + AstLevelspec["CallNodeWithArgs"] + ] ] - ] -] + )) +) GetColorPresentation[doc_TextDocument, color_LspColor, range_LspRange] := With[ diff --git a/src/WolframLanguageServer/Token.wl b/src/WolframLanguageServer/Token.wl index cdf9be3..8d14f40 100644 --- a/src/WolframLanguageServer/Token.wl +++ b/src/WolframLanguageServer/Token.wl @@ -512,7 +512,6 @@ GetTokenCompletionAtPostion[doc_TextDocument, pos_LspPosition] := With[ ] -(* SetDelayed is not needed. Cache it when define it. *) GetTriggerKeyCompletion[doc_TextDocument, pos_LspPosition] := ( If[GetTokenPrefix[doc, pos] == "\\\\", (* double-triggered *) @@ -522,6 +521,7 @@ GetTriggerKeyCompletion[doc_TextDocument, pos_LspPosition] := ( ) +(* SetDelayed is not needed. Cache it when define it. *) NonLetterAliasCompletionItems = ( Join[ AliasToLongName From 7623e7a42b6aa93c054bab899b0e1ad0450e143e Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Thu, 14 Jan 2021 23:34:07 -0800 Subject: [PATCH 21/32] Define FoldWhile only before version 12.2 --- src/WolframLanguageServer/Server.wl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/WolframLanguageServer/Server.wl b/src/WolframLanguageServer/Server.wl index a9daa4d..35cb869 100644 --- a/src/WolframLanguageServer/Server.wl +++ b/src/WolframLanguageServer/Server.wl @@ -33,8 +33,10 @@ Needs["WolframLanguageServer`Token`"] (*Utility*) -FoldWhile[f_, x_, list_List, test_] := FoldWhile[f, Prepend[list, x], test]; -FoldWhile[f_, list_List, test_] := First[NestWhile[Prepend[Drop[#, 2], f @@ Take[#, 2]]&, list, Length[#] > 1 && test[First[#]]&]]; +If[$VersionNumber < 12.2, + FoldWhile[f_, x_, list_List, test_] := FoldWhile[f, Prepend[list, x], test]; + FoldWhile[f_, list_List, test_] := First[NestWhile[Prepend[Drop[#, 2], f @@ Take[#, 2]]&, list, Length[#] > 1 && test[First[#]]&]] +] (* ::Section:: *) From cbc84f1105d6ae7f072babec1c98589230943c1d Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Fri, 31 Jul 2020 07:54:56 -0700 Subject: [PATCH 22/32] :construction: add dap socket server; add evaluate button to codeLens * An extra socket server is setup when language server starts. * request handler of dap 'initialize' . * The evaluation of code block and whole file are added to the codelens and workspace command (currently just stubs). --- src/WolframLanguageServer/Server.wl | 234 +++++++++++++++++++-- src/WolframLanguageServer/Specification.wl | 7 + 2 files changed, 221 insertions(+), 20 deletions(-) diff --git a/src/WolframLanguageServer/Server.wl b/src/WolframLanguageServer/Server.wl index 35cb869..e4982a4 100644 --- a/src/WolframLanguageServer/Server.wl +++ b/src/WolframLanguageServer/Server.wl @@ -48,11 +48,17 @@ DeclareType[WorkState, <| "openedDocs" -> _Association, (* (_DocumentUri -> _DocumentText)... *) "client" -> (_SocketClient | _SocketObject | _NamedPipe | _StdioClient | "stdio" | Null), "clientCapabilities" -> _Association, + "debugSession" -> _DebugSession, "scheduledTasks" -> {___ServerTask}, "caches" -> _Association, "config" -> _Association |>] +DeclareType[DebugSession, <| + "initialized" -> _?BooleanQ, + "server" -> _SocketObject | Null, + "client" -> _SocketObject | Null +|>] DeclareType[RequestCache, <| "cachedTime" -> _DateObject, @@ -72,6 +78,10 @@ InitialState = WorkState[<| "initialized" -> False, "openedDocs" -> <||>, "client" -> Null, + "debugSession" -> DebugSession[<| + "server" -> Null, + "client" -> Null + |>], "scheduledTasks" -> {}, "caches" -> initialCaches, "config" -> <| @@ -94,10 +104,15 @@ ServerCapabilities = <| "documentSymbolProvider" -> True, "codeActionProvider" -> True, "documentHighlightProvider" -> True, + "codeLensProvider" -> <| + "resolveProvider" -> False + |>, "colorProvider" -> True, "executeCommandProvider" -> <| "commands" -> { - "openRef" + "openRef", + "dap-wl.run-file", + "dap-wl.run-range" } |>, Nothing @@ -392,13 +407,26 @@ TcpSocketHandler[{stop_, state_WorkState}]:= ( CloseClient[state["client"]]; {stop, state} ) -TcpSocketHandler[state_WorkState] := Module[ +TcpSocketHandler[state_WorkState] := With[ { - client = state["client"] + client = state["client"], + debugSession = state["debugSession"] }, - If[SocketReadyQ[client], + Which[ + SocketReadyQ[client], handleMessageList[ReadMessages[client], state], + (* new client connected *) + debugSession["server"] =!= Null && + debugSession["client"] === Null && + Length[debugSession["server"]["ConnectedClients"]] > 0, + { + "Continue", + ReplaceKey[state, {"debugSession", "client"} -> First[LogDebug@debugSession["server"]["ConnectedClients"]]] + }, + debugSession["client"] =!= Null && SocketReadyQ[debugSession["client"]], + handleDapMessageList[ReadMessages[debugSession["client"]], state], + True, doNextScheduledTask[state] ] // Replace[{ @@ -701,7 +729,7 @@ NotificationQ = KeyExistsQ["id"] /* Not handleMessageList[msgs:{___Association}, state_WorkState] := ( FoldWhile[handleMessage[#2, Last[#1]]&, {"Continue", state}, msgs, MatchQ[{"Continue", _}]] -); +) handleMessage[msg_Association, state_WorkState] := With[ { @@ -738,7 +766,57 @@ handleMessage[msg_Association, state_WorkState] := With[ handleRequest[method, msg, state] ] ] -]; +] + + +handleDapMessageList[msgs:{___Association}, state_WorkState] := ( + FoldWhile[handleDapMessage[#2, Last[#1]]&, {"Continue", state}, msgs, MatchQ[{"Continue", _}]] +) + +handleDapMessage[msg_Association, state_WorkState] := Module[ + { + newState = state + }, + + Replace[msg["type"], { + "request" :> ( + LogDebug @ Iconize[msg["command"], msg["arguments"]] + ), + "event" :> ( + LogDebug @ Iconize[msg["event"], msg["body"]] + ), + "respond" :> ( + LogDebug @ Iconize[msg["command"], msg["body"], msg["message"]] + ) + }]; + + Which[ + (* wrong message before initialization *) + !state["debuggerInitialized"] && + msg["type"] != "request" && + !MemberQ[{"initialize"}, msg["command"]], + If[msg["type"] == "request", + sendResponse[state["dubugSession"]["client"], <| + "type" -> "response", + "request_seq" -> msg["seq"], + "success" -> False, + "command" -> msg["command"], + "message" -> "ServerNotInitialized", + "body" -> <| + "error" -> "The server is not initialized." + |> + |>] + (* otherwise, dropped the notification *) + ]; + {"Continue", state}, + (* notification*) + msg["event"], + handleDapEvent[msg["event"], msg, newState], + (* resquest *) + True, + handleDapRequest[msg["command"], msg, newState] + ] +] @@ -806,27 +884,24 @@ sendMessage[client_, res:(_ResponseMessage|_NotificationMessage)] := ( (*initialize*) -handleRequest["initialize", msg_, state_WorkState] := Module[ - { - newState = state - }, +handleRequest["initialize", msg_, state_WorkState] := ( - LogDebug["handle/initialize"]; - - (* Check Client Capabilities *) - newState = ReplaceKey[state, - "clientCapabilities" -> msg["params"]["capabilities"] - ]; - sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], "result" -> <| "capabilities" -> ServerCapabilities |> |>]]; - - {"Continue", newState} -]; + + (* TODO(kenkangxgwe): check client capabilities *) + { + "Continue", + Fold[ReplaceKey, state, { + "clientCapabilities" -> msg["params"]["capabilities"] + (* {"debugSession", "server"} -> SocketOpen[8216] *) + }] + } +) (* ::Subsection:: *) @@ -1231,6 +1306,102 @@ handleRequest["textDocument/codeAction", msg_, state_] := With[ ] +(* ::Subsection:: *) +(*textDocuent/codeLens*) + + +handleRequest["textDocument/codeLens", msg_, state_] := With[ + { + id = msg["id"], + uri = msg["params"]["textDocument"]["uri"] + }, + + sendResponse[state["client"], <| + "id" -> id, + "result" -> ( + { + If[state["debugSession"]["initialized"], + { + CodeLens[<| + "range" -> <| + "start" -> <| + "line" -> 0, + "character" -> 0 + |>, + "end" -> <| + "line" -> 0, + "character" -> 0 + |> + |>, + "command" -> <| + "title" -> "$(workflow) Evaluate File", + "command" -> "dap-wl.run-file", + "arguments" -> {<| + "uri" -> uri + |>} + |> + |>], + Table[ + CodeLens[<| + (* range can only span one line *) + "range" -> ( + codeRange + // ReplaceKey["end" -> codeRange["start"]] + (* // ReplaceKey[{"end", "line"} -> codeRange["start"]["line"]] + // ReplaceKey[{"end", "character"} -> 1] *) + ), + "command" -> <| + "title" -> "$(play) Evaluate", + "command" -> "dap-wl.run-range", + "arguments" -> {<| + "uri" -> uri, + "range" -> codeRange + |>} + |> + |>], + { + codeRange, + state["openedDocs"][uri] + // FindAllCodeRanges + } + ] + }, + Nothing + ] + } + // Flatten + // ToAssociation + ) + |>]; + + {"Continue", state} + +] + + +(* ::Subsection:: *) +(*codeLens/resolve*) + + +handleRequest["codeLens/resolve", msg_, state_] := With[ + { + id = msg["id"], + codeLens = msg["params"] + }, + + sendResponse[state["client"], <| + "id" -> id, + "result" -> ToAssociation[ + ConstructType[codeLens, CodeLens] + // ReplaceKeyBy[{"command", "title"} -> (StringJoin[#1, " ", ToString[codeLens["data"]]]&)] + // ReplaceKeyBy["data" -> ((# + 1)&)] + ] + |>]; + + {"Continue", state} +] + + (* ::Subsection:: *) (*textDocument/documentColor*) @@ -1551,6 +1722,29 @@ handleNotification[_, msg_, state_] := ( ) +(* ::Section:: *) +(*Handle Dap Request*) + + +handleDapRequest["initialize", msg_, state_WorkState] := ( + sendResponse[state["debugSession"]["client"], <| + "type" -> "response", + "request_seq" -> msg["request_seq"], + "success" -> True, + "command" -> msg["command"], + "body" -> <| + Nothing + |> + |>]; + + { + "Continue", + state + // ReplaceKey[{"debugSession", "initialized"} -> True] + } +) + + (* ::Section:: *) (*Send Message*) diff --git a/src/WolframLanguageServer/Specification.wl b/src/WolframLanguageServer/Specification.wl index 17f9255..810001b 100644 --- a/src/WolframLanguageServer/Specification.wl +++ b/src/WolframLanguageServer/Specification.wl @@ -37,6 +37,7 @@ DocumentSymbol::usage = "is the type of DocumentSymbol interface in LSP." CompletionItem::usage = "is the type of CompletionItem interface in LSP." DocumentHighlight ::usage = "is the type of Location interface in LSP." LspCodeAction::usage = "is the type of CodeAction interface in LSP." +CodeLens::usage = "is type of CodeLens Interface in LSP." ColorInformation::usage = "is the type of ColorInformation interface in LSP." LspColor::usage = "is the type of Color interface in LSP." ColorPresentation::usage = "is the type of ColorPresentation interface in LSP." @@ -346,6 +347,12 @@ DeclareType[LspCodeAction, <| "command" -> _Command |>] +DeclareType[CodeLens, <| + "range" -> _LspRange, + "command" -> _Command, + "data" -> _ +|>] + DeclareType[ColorInformation, <| "range" -> _LspRange, "color" -> _LspColor From bdf5be252520609840004d312a4bce468f444ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=B7=E5=B0=8F=E5=B9=BF?= Date: Wed, 5 Feb 2020 02:05:27 -0800 Subject: [PATCH 23/32] Add workspace/ApplyEdit request and response This is a ad hoc method to tirgger the codelens after the debugger is attached. Two code lens stub commands are added: dap-wl.run-file and dap-wl.run-range. --- src/WolframLanguageServer/Server.wl | 97 ++++++++++++++++++++-- src/WolframLanguageServer/Specification.wl | 8 ++ 2 files changed, 97 insertions(+), 8 deletions(-) diff --git a/src/WolframLanguageServer/Server.wl b/src/WolframLanguageServer/Server.wl index e4982a4..61a6692 100644 --- a/src/WolframLanguageServer/Server.wl +++ b/src/WolframLanguageServer/Server.wl @@ -51,6 +51,7 @@ DeclareType[WorkState, <| "debugSession" -> _DebugSession, "scheduledTasks" -> {___ServerTask}, "caches" -> _Association, + "pendingServerRequests" -> _Association, "config" -> _Association |>] @@ -84,6 +85,7 @@ InitialState = WorkState[<| |>], "scheduledTasks" -> {}, "caches" -> initialCaches, + "pendingServerRequests" -> <||>, "config" -> <| "configFileConfig" -> loadConfig[] |> @@ -726,6 +728,7 @@ CloseClient[client_NamedPipe] := With[ NotificationQ = KeyExistsQ["id"] /* Not (* NotificationQ[msg_Association] := MissingQ[msg["id"]] *) +ResponseQ = And[KeyExistsQ["method"] /* Not, KeyExistsQ["id"]] /* Through handleMessageList[msgs:{___Association}, state_WorkState] := ( FoldWhile[handleMessage[#2, Last[#1]]&, {"Continue", state}, msgs, MatchQ[{"Continue", _}]] @@ -753,6 +756,9 @@ handleMessage[msg_Association, state_WorkState] := With[ (* notification*) NotificationQ[msg], handleNotification[method, msg, state], + (* response *) + ResponseQ[msg], + handleResponse[state["pendingServerRequests"][msg["id"]], msg, state], (* resquest *) True, Which[ @@ -1316,7 +1322,7 @@ handleRequest["textDocument/codeLens", msg_, state_] := With[ uri = msg["params"]["textDocument"]["uri"] }, - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "id" -> id, "result" -> ( { @@ -1370,9 +1376,8 @@ handleRequest["textDocument/codeLens", msg_, state_] := With[ ] } // Flatten - // ToAssociation ) - |>]; + |>]]; {"Continue", state} @@ -1389,14 +1394,14 @@ handleRequest["codeLens/resolve", msg_, state_] := With[ codeLens = msg["params"] }, - sendResponse[state["client"], <| + sendMessage[state["client"], ResponseMessage[<| "id" -> id, - "result" -> ToAssociation[ - ConstructType[codeLens, CodeLens] + "result" -> ( + ConstructType[codeLens, CodeLens] // ReplaceKeyBy[{"command", "title"} -> (StringJoin[#1, " ", ToString[codeLens["data"]]]&)] // ReplaceKeyBy["data" -> ((# + 1)&)] - ] - |>]; + ) + |>]]; {"Continue", state} ] @@ -1722,6 +1727,81 @@ handleNotification[_, msg_, state_] := ( ) +(* ::Section:: *) +(*Send Request*) + + +$requestId = 0 +getRequestId[] := ( + "req_" <> ToString[($requestId += 1)] +) + +(* ::Subsection:: *) +(*applyEdit*) + +sendRequest[method:"workspace/applyEdit", msg_, state_WorkState] := With[ + { + id = getRequestId[] + }, + + sendMessage[state["client"], NotificationMessage[<| + "id" -> id, + "method" -> "workspace/applyEdit", + "params" -> <| + "edit" -> msg["params"]["edit"] + |> + |>]]; + + { + "Continue", + state + // ReplaceKeyBy["pendingServerRequests" -> Append[id -> method]] + } +] + +applyEdit["dummyAll", state_WorkState] := ( + sendRequest[ + "workspace/applyEdit", + Table[uri -> { + TextEdit[<| + "range" -> LspRange[<| + "start" -> <| + "line" -> 0, + "character" -> 0 + |>, + "end" -> <| + "line" -> 0, + "character" -> 1 + |> + |>], + "newText" -> ( + state["openedDocs"][uri]["text"] + // First + // StringTake[#, 1]& + ) + |>] + }, {uri, Keys[state["openedDocs"]]}] + // WorkspaceEdit[<|"changes" -> <|#|>|>]& + // ToAssociation + // <|"params" -> <|"edit" -> #|>|>&, + state + ] +) + + +(* ::Section:: *) +(*Handle Response*) + + +handleResponse["workspace/applyEdit", msg_, state_WorkState] := ( + { + "Continue", + state + // DeleteKey[{"pendingServerRequests", msg["id"]}] + } +) + + (* ::Section:: *) (*Handle Dap Request*) @@ -2100,6 +2180,7 @@ WLServerVersion[] := WolframLanguageServer`$Version; WLServerDebug[] := Print["This is a debug function."]; + End[]; diff --git a/src/WolframLanguageServer/Specification.wl b/src/WolframLanguageServer/Specification.wl index 810001b..f7e491a 100644 --- a/src/WolframLanguageServer/Specification.wl +++ b/src/WolframLanguageServer/Specification.wl @@ -24,6 +24,7 @@ Location::usage = "is type of Location interface in LSP." Command::usage = "is type of Command interface in LSP." TextEdit::usage = "is type of TextEdit interface in LSP." TextDocumentItem::usage = "is type of TextDocumentItem interface in LSP." +WorkspaceEdit::usage = "is type of WorkspaceEdit Interface in LSP." MarkupContent::usage = "is the type of MarkupContent interface in LSP." TextDocumentContentChangeEvent::usage = "is an event describing a change to a text document. If range and rangeLength are omitted \ the new text is considered to be the full content of the document." @@ -244,6 +245,13 @@ DeclareType[TextEdit, <| "newText" -> _String |>] +DeclareType[WorkspaceEdit, <| + "changes" -> <| + (_DocumentUri -> TextEdit)... + |>, + "documentChanges" -> _ (* not implemented *) +|>] + DeclareType[TextDocumentItem, <| "uri" -> _DocumentUri, "languageId" -> _String, From 2d6c2cc965b76bfce8fd9c5f4fa0eccbeb8ee1b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=B7=E5=B0=8F=E5=B9=BF?= Date: Wed, 5 Feb 2020 01:18:34 -0800 Subject: [PATCH 24/32] Add subKernel and symbolTable to dap. Added Events / Requests (stubs): * initialized * configurationDone * output * stopped * attach * disconnect * process * thread * setBreakPoints * threads * evaulate --- src/WolframLanguageServer/Server.wl | 478 +++++++++++++++++---- src/WolframLanguageServer/Specification.wl | 38 +- 2 files changed, 434 insertions(+), 82 deletions(-) diff --git a/src/WolframLanguageServer/Server.wl b/src/WolframLanguageServer/Server.wl index 61a6692..e871873 100644 --- a/src/WolframLanguageServer/Server.wl +++ b/src/WolframLanguageServer/Server.wl @@ -58,7 +58,10 @@ DeclareType[WorkState, <| DeclareType[DebugSession, <| "initialized" -> _?BooleanQ, "server" -> _SocketObject | Null, - "client" -> _SocketObject | Null + "client" -> _SocketObject | Null, + "subKernel" -> _, + "context" -> _String, + "thread" -> _DapThread |>] DeclareType[RequestCache, <| @@ -80,6 +83,7 @@ InitialState = WorkState[<| "openedDocs" -> <||>, "client" -> Null, "debugSession" -> DebugSession[<| + "initialized" -> False, "server" -> Null, "client" -> Null |>], @@ -107,7 +111,7 @@ ServerCapabilities = <| "codeActionProvider" -> True, "documentHighlightProvider" -> True, "codeLensProvider" -> <| - "resolveProvider" -> False + "resolveProvider" -> True |>, "colorProvider" -> True, "executeCommandProvider" -> <| @@ -424,7 +428,7 @@ TcpSocketHandler[state_WorkState] := With[ Length[debugSession["server"]["ConnectedClients"]] > 0, { "Continue", - ReplaceKey[state, {"debugSession", "client"} -> First[LogDebug@debugSession["server"]["ConnectedClients"]]] + ReplaceKey[state, {"debugSession", "client"} -> First[debugSession["server"]["ConnectedClients"]]] }, debugSession["client"] =!= Null && SocketReadyQ[debugSession["client"]], handleDapMessageList[ReadMessages[debugSession["client"]], state], @@ -798,11 +802,11 @@ handleDapMessage[msg_Association, state_WorkState] := Module[ Which[ (* wrong message before initialization *) - !state["debuggerInitialized"] && + !state["debugSession"]["Initialized"] && msg["type"] != "request" && !MemberQ[{"initialize"}, msg["command"]], If[msg["type"] == "request", - sendResponse[state["dubugSession"]["client"], <| + sendMessage[state["dubugSession"]["client"], DapResponse[<| "type" -> "response", "request_seq" -> msg["seq"], "success" -> False, @@ -811,12 +815,12 @@ handleDapMessage[msg_Association, state_WorkState] := Module[ "body" -> <| "error" -> "The server is not initialized." |> - |>] + |>]] (* otherwise, dropped the notification *) ]; {"Continue", state}, - (* notification*) - msg["event"], + (* event*) + msg["type"] == "event", handleDapEvent[msg["event"], msg, newState], (* resquest *) True, @@ -876,7 +880,7 @@ scheduleDelayedRequest[method_String, msg_, state_WorkState] := ( ) -(* response, notification and request will call this function *) +(* For LSP: response, notification and request *) sendMessage[client_, res:(_ResponseMessage|_NotificationMessage)] := ( res // ReplaceKey["jsonrpc" -> "2.0"] @@ -885,12 +889,25 @@ sendMessage[client_, res:(_ResponseMessage|_NotificationMessage)] := ( // WriteMessage[client] ) +(* For DAP: event and response *) +sendMessage[client_, res:(_DapEvent|_DapResponse)] := ( + res + // ToAssociation + // constructRPCBytes + // WriteMessage[client] +) + (* ::Subsection:: *) (*initialize*) -handleRequest["initialize", msg_, state_WorkState] := ( +handleRequest["initialize", msg_, state_WorkState] := With[ + { + debugPort = Fold[Replace[#1, _?MissingQ -> <||>][#2]&, + msg, {"params", "initializationOptions", "debuggerPort"} + ] + }, sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], @@ -903,11 +920,15 @@ handleRequest["initialize", msg_, state_WorkState] := ( { "Continue", Fold[ReplaceKey, state, { - "clientCapabilities" -> msg["params"]["capabilities"] - (* {"debugSession", "server"} -> SocketOpen[8216] *) + "clientCapabilities" -> msg["params"]["capabilities"], + If[!MissingQ[debugPort], + LogInfo["Debugger listening at port " <> ToString[debugPort]]; + {"debugSession", "server"} -> SocketOpen[debugPort], + Nothing + ] }] } -) +] (* ::Subsection:: *) @@ -937,16 +958,41 @@ handleRequest["workspace/executeCommand", msg_, state_] := With[ args = msg["params"]["arguments"] }, - Replace[command, { - "dap-wl.runfile" -> ( - LogInfo[StringJoin["executing ", command, "with arguments: ", ToString[args]]] - ), - "openRef" -> ( - args - // First - // SystemOpen - // UsingFrontEnd - ) + LogDebug[StringJoin["executing ", command, " with arguments: ", ToString[args]]]; + executeCommand[command, msg, state] +] + + +(* ::Subsubsection:: *) +(*openRef*) + + +executeCommand["openRef", msg_, state_WorkState] := ( + msg["params"]["arguments"] + // First + // SystemOpen + // UsingFrontEnd; + + sendMessage[state["client"], ResponseMessage[<| + "id" -> msg["id"], + "result" -> Null + |>]]; + + {"Continue", state} +) + + +(* ::Subsubsection:: *) +(*dap-wl.run-file*) + + +executeCommand["dap-wl.run-file", msg_, state_WorkState] := With[ + { + args = msg["params"]["arguments"] // First + }, + + LogDebug[{"Running", + args["uri"] }]; sendMessage[state["client"], ResponseMessage[<| @@ -955,7 +1001,57 @@ handleRequest["workspace/executeCommand", msg_, state_] := With[ |>]]; {"Continue", state} +] + + +(* ::Subsubsection:: *) +(*dap-wl.run-range*) + +executeCommand["dap-wl.run-range", msg_, state_WorkState] := Block[ + { + args = msg["params"]["arguments"] // First, + text, res + }, + + text = GetDocumentText[ + state["openedDocs"][args["uri"]], + ConstructType[args["range"], _LspRange] + ]; + + res = With[{text = text}, + ParallelEvaluate[ + ToExpression[text], + state["debugSession"]["subKernel"] + ] + ] // ToString; + + sendMessage[state["debugSession"]["client"], DapEvent[<| + "type" -> "event", + "event" -> "output", + "body" -> <| + "category" -> "stdout", + "output" -> StringJoin[text, "\n", res], + "variableReference" -> 1 + |> + |>]]; + + sendMessage[state["debugSession"]["client"], DapEvent[<| + "type" -> "event", + "event" -> "stopped", + "body" -> <| + "reason" -> "pause", + "description" -> "Cell Evaluated Successfully", + "threadId" -> state["debugSession"]["thread"]["id"] + |> + |>]]; + + sendMessage[state["client"], ResponseMessage[<| + "id" -> msg["id"], + "result" -> Null + |>]]; + + {"Continue", state} ] @@ -1325,56 +1421,54 @@ handleRequest["textDocument/codeLens", msg_, state_] := With[ sendMessage[state["client"], ResponseMessage[<| "id" -> id, "result" -> ( - { - If[state["debugSession"]["initialized"], - { - CodeLens[<| - "range" -> <| - "start" -> <| - "line" -> 0, - "character" -> 0 - |>, - "end" -> <| - "line" -> 0, - "character" -> 0 - |> + If[state["debugSession"]["initialized"], + { + CodeLens[<| + "range" -> <| + "start" -> <| + "line" -> 0, + "character" -> 0 |>, - "command" -> <| - "title" -> "$(workflow) Evaluate File", - "command" -> "dap-wl.run-file", + "end" -> <| + "line" -> 0, + "character" -> 0 + |> + |>, + "data" -> <| + "title" -> "$(workflow) Evaluate File", + "command" -> "dap-wl.run-file", + "arguments" -> {<| + "uri" -> uri + |>} + |> + |>], + Table[ + CodeLens[<| + (* range can only span one line *) + "range" -> ( + codeRange + // ReplaceKey["end" -> codeRange["start"]] + (* // ReplaceKey[{"end", "line"} -> codeRange["start"]["line"]] + // ReplaceKey[{"end", "character"} -> 1] *) + ), + "data" -> <| + "title" -> "$(play) Evaluate", + "command" -> "dap-wl.run-range", "arguments" -> {<| - "uri" -> uri + "uri" -> uri, + "range" -> codeRange |>} |> |>], - Table[ - CodeLens[<| - (* range can only span one line *) - "range" -> ( - codeRange - // ReplaceKey["end" -> codeRange["start"]] - (* // ReplaceKey[{"end", "line"} -> codeRange["start"]["line"]] - // ReplaceKey[{"end", "character"} -> 1] *) - ), - "command" -> <| - "title" -> "$(play) Evaluate", - "command" -> "dap-wl.run-range", - "arguments" -> {<| - "uri" -> uri, - "range" -> codeRange - |>} - |> - |>], - { - codeRange, - state["openedDocs"][uri] - // FindAllCodeRanges - } - ] - }, - Nothing - ] - } + { + codeRange, + state["openedDocs"][uri] + // FindAllCodeRanges + } + ] + }, + {} + ] // Flatten ) |>]]; @@ -1398,8 +1492,7 @@ handleRequest["codeLens/resolve", msg_, state_] := With[ "id" -> id, "result" -> ( ConstructType[codeLens, CodeLens] - // ReplaceKeyBy[{"command", "title"} -> (StringJoin[#1, " ", ToString[codeLens["data"]]]&)] - // ReplaceKeyBy["data" -> ((# + 1)&)] + // ReplaceKey[#, "command" -> #["data"]]& ) |>]]; @@ -1615,7 +1708,7 @@ handleNotification["textDocument/didClose", msg_, state_] := With[ uri = msg["params"]["textDocument"]["uri"] }, - LogDebug @ ("Close Document " <> uri); + LogInfo @ ("Close Document " <> uri); state // ReplaceKeyBy[{"openedDocs"} -> KeyDrop[uri]] @@ -1782,7 +1875,6 @@ applyEdit["dummyAll", state_WorkState] := ( |>] }, {uri, Keys[state["openedDocs"]]}] // WorkspaceEdit[<|"changes" -> <|#|>|>]& - // ToAssociation // <|"params" -> <|"edit" -> #|>|>&, state ] @@ -1806,22 +1898,245 @@ handleResponse["workspace/applyEdit", msg_, state_WorkState] := ( (*Handle Dap Request*) -handleDapRequest["initialize", msg_, state_WorkState] := ( - sendResponse[state["debugSession"]["client"], <| +(* ::Subsection:: *) +(*initialize*) + + +handleDapRequest["initialize", msg_, state_WorkState] := Block[ + { + subKernel = LaunchKernels[1] // First, + context = ToString[Unique["debug"]] <> "`", + newSymbolTable + }, + + LogInfo["Initializing Wolfram Language Debugger"]; + + sendMessage[state["debugSession"]["client"], DapResponse[<| + "type" -> "response", + "request_seq" -> msg["seq"], + "success" -> True, + "command" -> msg["command"], + "body" -> <| + "supportsConfigurationDoneRequest" -> True, + "supportsEvaluateForHovers" -> True + |> + |>]]; + + $DistributedContexts = None; + newSymbolTable = (Begin[context]; SymbolTable; End[]); + (* StringJoin[ + context, "`SymbolTable = <||>;", + "$NewSymbol = ((", context, "`SymbolTable = + Merge[{", context, "`SymbolTable, <|#2 -> #1|>}, Flatten] + )&);", + "$Epilog := " + ] // *) + ParallelEvaluate[ + newSymbolTable = <||>; + $NewSymbol = ((newSymbolTable = + Merge[{newSymbolTable, <|#2 -> #1|>}, Flatten] + )&), + subKernel + ]; + + sendMessage[state["debugSession"]["client"], DapEvent[<| + "type" -> "event", + "event" -> "initialized" + |>]]; + + state + // ReplaceKeyBy["debugSession" -> ( + Fold[ReplaceKey, #, { + "initialized" -> True, + "subKernel" -> subKernel, + "context" -> context, + "thread" -> DapThread[<| + "id" -> ParallelEvaluate[$ProcessID, subKernel], + "name" -> StringJoin[ + "SubKernel ", + ParallelEvaluate[$KernelID, subKernel] + // ToString + ] + |>], + "symbolTable" -> <||> + }]&)] + // applyEdit["dummyAll", #]& +] + + +(* ::Subsection:: *) +(*configurationDone*) + + +handleDapRequest["configurationDone", msg_, state_WorkState] := ( + + LogInfo["Configuration Done for Wolfram Language Debugger"]; + + sendMessage[state["debugSession"]["client"], DapResponse[<| "type" -> "response", - "request_seq" -> msg["request_seq"], + "request_seq" -> msg["seq"], + "success" -> True, + "command" -> msg["command"], + "body" -> <||> + |>]]; + + {"Continue", state} +) + + +(* ::Subsection:: *) +(*attach*) + + +handleDapRequest["attach", msg_, state_WorkState] := ( + + LogInfo["Attaching to Wolfram Language Kernel"]; + + sendMessage[state["debugSession"]["client"], DapResponse[<| + "type" -> "response", + "request_seq" -> msg["seq"], "success" -> True, "command" -> msg["command"], "body" -> <| Nothing |> - |>]; + |>]]; + + sendMessage[state["debugSession"]["client"], DapEvent[<| + "type" -> "event", + "event" -> "process", + "body" -> <| + "name" -> "wolfram.exe", + "systemProcessId" -> state["debugSession"]["thread"]["id"], + "isLocalProcess" -> True, + "startMethod" -> "attach" + |> + |>]]; + + sendMessage[state["debugSession"]["client"], DapEvent[<| + "type" -> "event", + "event" -> "thread", + "body" -> <| + "reason" -> "wolfram.exe", + "threadId" -> state["debugSession"]["thread"]["id"] + |> + |>]]; + + + {"Continue", state} +) + + +(* ::Subsection:: *) +(*disconnect*) + + +handleDapRequest["disconnect", msg_, state_WorkState] := ( + sendMessage[state["debugSession"]["client"], DapResponse[<| + "type" -> "response", + "request_seq" -> msg["seq"], + "success" -> True, + "command" -> msg["command"], + "body" -> <||> + |>]]; + + state + // ReplaceKey[{"debugSession", "initialized"} -> False] + // ReplaceKey[{"debugSession", "client"} -> Null] + // applyEdit["dummyAll", #]& +) + + +(* ::Subsection:: *) +(*setBreakpoints*) + + +handleDapRequest["setBreakPoints", msg_, state_WorkState] := ( + sendMessage[state["debugSession"]["client"], DapResponse[<| + "type" -> "response", + "request_seq" -> msg["seq"], + "success" -> True, + "command" -> msg["command"], + "body" -> <| + "breakpoints" -> {} + |> + |>]]; + + {"Continue", state} +) + + +(* ::Subsection:: *) +(*threads*) + + +handleDapRequest["threads", msg_, state_WorkState] := ( + sendMessage[state["debugSession"]["client"], DapResponse[<| + "type" -> "response", + "request_seq" -> msg["seq"], + "success" -> True, + "command" -> msg["command"], + "body" -> <| + "threads" -> { + state["debugSession"]["thread"] + } + |> + |>]]; + + {"Continue", state} +) + +(* ::Subsection:: *) +(*evaluate*) + + +handleDapRequest["evaluate", msg_, state_WorkState] := Block[ { - "Continue", - state - // ReplaceKey[{"debugSession", "initialized"} -> True] - } + text + }, + + text = msg["arguments"]["expression"]; + res = With[{text = text}, + ParallelEvaluate[ + ToExpression[text], + state["debugSession"]["subKernel"] + ] + ] // ToString; + + sendMessage[state["debugSession"]["client"], DapResponse[<| + "type" -> "response", + "request_seq" -> msg["seq"], + "success" -> True, + "command" -> msg["command"], + "body" -> <| + "result" -> res + |> + |>]]; + + {"Continue", state} + ] + + +(* ::Subsection:: *) +(*Invalid Dap Request*) + + +handleDapRequest[_, msg_, state_] := ( + sendMessage[state["debugSession"]["client"], DapResponse[<| + "type" -> "response", + "request_seq" -> msg["seq"], + "success" -> False, + "command" -> msg["command"], + "body" -> <| + "error" -> <| + "id" -> msg["seq"], + "format" -> ErrorMessageTemplates["DapRequestNotFound"][msg["command"]] + |> + |> + |>]]; + + {"Continue", state} ) @@ -1884,7 +2199,8 @@ ServerError[errorType_String, msg_String] := ResponseError[ ErrorMessageTemplates = <| - "MethodNotFound" -> StringTemplate["The requested method `method` is invalid or not implemented"] + "MethodNotFound" -> StringTemplate["The requested method \"`method`\" is invalid or not implemented"], + "DapRequestNotFound" -> StringTemplate["The request \"`command`\" is invalid or not implemented"] |> diff --git a/src/WolframLanguageServer/Specification.wl b/src/WolframLanguageServer/Specification.wl index f7e491a..4d9055a 100644 --- a/src/WolframLanguageServer/Specification.wl +++ b/src/WolframLanguageServer/Specification.wl @@ -44,6 +44,15 @@ LspColor::usage = "is the type of Color interface in LSP." ColorPresentation::usage = "is the type of ColorPresentation interface in LSP." +(* ::Section:: *) +(* Debug Adaptor Protocol*) + + +DapEvent::usage = "is the type of Event interface in DAP." +DapResponse::usage = "is the type of Response interface in DAP." +DapThread::usage = "is the type of Thread interface in DAP." + + (* ::Section:: *) (*Type Aliases*) @@ -186,7 +195,7 @@ Needs["DataType`"] (* ::Section:: *) -(*Server Communication Related Type*) +(*Language Server Types*) (* ::Subsection:: *) @@ -379,6 +388,33 @@ DeclareType[ColorPresentation, <| "additionalTextEdits" -> {__TextEdit} |>] + +(* ::Section:: *) +(*Debug Adaptor Types*) + + +DeclareType[DapEvent, <| + "seq" -> _Integer, + "type" -> "event", + "body" -> _ +|>] + +DeclareType[DapResponse, <| + "seq" -> _Integer, + "type" -> "response", + "request_seq -> _Integer, + "success" -> ?BooleanQ, + "command" -> _String, + "message" -> _String, + "body" -> _ +|>] + +DeclareType[DapThread, <| + "id" -> _?NumericQ, + "name" -> _String +|>] + + End[] From 534f65cfe2d74f6432202cd88b45a96031a8eaa7 Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Sun, 30 Aug 2020 16:02:56 -0700 Subject: [PATCH 25/32] First draft of evaluation * Move debugger-related methods to a debugger.wl --- src/WolframLanguageServer/Debugger.wl | 128 ++++++++++++++ src/WolframLanguageServer/Server.wl | 194 +++++++++++++++------ src/WolframLanguageServer/Specification.wl | 31 +++- 3 files changed, 299 insertions(+), 54 deletions(-) create mode 100644 src/WolframLanguageServer/Debugger.wl diff --git a/src/WolframLanguageServer/Debugger.wl b/src/WolframLanguageServer/Debugger.wl new file mode 100644 index 0000000..ba00802 --- /dev/null +++ b/src/WolframLanguageServer/Debugger.wl @@ -0,0 +1,128 @@ +(* ::Package:: *) + +(* Copyright 2020 lsp-wl Authors *) +(* SPDX-License-Identifier: MIT *) + + +(* Wolfram Language Server Debugger *) + + +BeginPackage["WolframLanguageServer`Debugger`"] +ClearAll[Evaluate[Context[] <> "*"]] + + +(* Output Symbols *) +CreateDebuggerKernel::usage = "Create a subkernel as a debugger, and returns the KernelObject." +CloseDebuggerKernel::usage = "CloseDebuggerKernel[_KernelObject] closed the specified subkernel." +GetKernelId::usage = "GetKernelId[kernel_KernelObject] returns the $KernelID of the specified kernel." +GetProcessId::usage = "GetProcessId[kernel_KernelObject] returns the $ProcessID of the specified kernel." +GetThreads::usage = "GetThreads[kernel_KernelObject] returns the available thread of the specified kernel. \ +(Currntly only one thread is available.)" +GetStackFrames::usage = "GetStackFrames[stackTraceArguments_Association, kernel_KernelObject] \ +returns the stackframes of the specified thread." +GetScopes::usage = "GetScopes[scopesArguments_Association, kernel_KernelObject] returns the scopes of the specified stackframe." +GetVariables::usage = "" +DebuggerEvaluate::usage = "DebuggerEvaluate[evaluateArguments_Association, kernel_KernelObject] \ +evaluate the given expr in the debugger and return the string form." + + +(* Private Context *) +Begin["`Private`"] +ClearAll[Evaluate[Context[] <> "*"]] + + +Needs["DataType`"] +Needs["WolframLanguageServer`Logger`"] +Needs["WolframLanguageServer`Specification`"] + + +$DistributedContexts = None + + +CreateDebuggerKernel[] := With[ + { + subKernel = LaunchKernels[1] // First + }, + ParallelEvaluate[ + debugger`SymbolTable = <||>; + $NewSymbol = ((debugger`SymbolTable = + Merge[{debugger`SymbolTable, <|#2 -> #1|>}, Flatten] + )&); + $Epilog, + subKernel + ]; + subKernel +] + + +GetKernelId[kernel_KernelObject] := ParallelEvaluate[$KernelID, kernel] + + +GetProcessId[kernel_KernelObject] := ParallelEvaluate[$ProcessID, kernel] + + +GetThreads[kernel_KernelObject] := { + DapThread[<| + "id" -> 1, + "name" -> "default" + |>] +} + + +GetStackFrames[stackTraceArguments_Association, kernel_KernelObject] := { + StackFrame[<| + "id" -> 0, + "name" -> "default", + "line" -> 0, + "column" -> 0 + |>] +} + + +GetScopes[scopesArguments_Association, kernel_KernelObject] := Block[ + { + symbolTable = ParallelEvaluate[debugger`SymbolTable, kernel] + }, + + symbolTable + // KeyValueMap[ + Scope[<| + "name" -> #1, + "variablesReference" -> $ModuleNumber, + "namedVariables" -> Length[#2], + "expensive" -> False + |>]& + ] +] + + +GetVariables[variablesArguments_Association, kernel_KernelObject] := ( + { + DapVariable[<| + "name" -> "test", + "value" -> "1", + "type" -> "SubValues", + "variablesReference" -> 0, + "namedVariables" -> 0, + "indexedVariables" -> 0 + |>] + } +) + + +DebuggerEvaluate[evaluateArguments_Association, kernel_KernelObject] := ( + evaluateArguments["expression"] + // StringTrim + // ParallelEvaluate[ + ToExpression[#, InputForm, Hold] + // ReleaseHold, + kernel, + ToString + ]& +) + + +End[] + + +EndPackage[] \ No newline at end of file diff --git a/src/WolframLanguageServer/Server.wl b/src/WolframLanguageServer/Server.wl index e871873..f2b1d1d 100644 --- a/src/WolframLanguageServer/Server.wl +++ b/src/WolframLanguageServer/Server.wl @@ -27,6 +27,7 @@ Needs["WolframLanguageServer`Logger`"] Needs["WolframLanguageServer`Specification`"] Needs["WolframLanguageServer`TextDocument`"] Needs["WolframLanguageServer`Token`"] +Needs["WolframLanguageServer`Debugger`"] (* ::Section:: *) @@ -1011,38 +1012,90 @@ executeCommand["dap-wl.run-file", msg_, state_WorkState] := With[ executeCommand["dap-wl.run-range", msg_, state_WorkState] := Block[ { args = msg["params"]["arguments"] // First, - text, res + text }, text = GetDocumentText[ state["openedDocs"][args["uri"]], ConstructType[args["range"], _LspRange] - ]; + ] // StringTrim; - res = With[{text = text}, - ParallelEvaluate[ - ToExpression[text], - state["debugSession"]["subKernel"] - ] - ] // ToString; + sendMessage[state["debugSession"]["client"], DapEvent[<| + "type" -> "event", + "event" -> "output", + "body" -> <| + "category" -> "stdout", + "output" -> "Input:", + "group" -> "start" + |> + |>]]; + + sendMessage[state["debugSession"]["client"], DapEvent[<| + "type" -> "event", + "event" -> "output", + "body" -> <| + "category" -> "stdout", + "output" -> StringJoin[text], + "variableReference" -> 1 + |> + |>]]; + + sendMessage[state["debugSession"]["client"], DapEvent[<| + "type" -> "event", + "event" -> "output", + "body" -> <| + "category" -> "stdout", + "output" -> "", + "group" -> "end" + |> + |>]]; + + sendMessage[state["debugSession"]["client"], DapEvent[<| + "type" -> "event", + "event" -> "output", + "body" -> <| + "category" -> "stdout", + "output" -> "Output:", + "group" -> "start" + |> + |>]]; sendMessage[state["debugSession"]["client"], DapEvent[<| "type" -> "event", "event" -> "output", "body" -> <| "category" -> "stdout", - "output" -> StringJoin[text, "\n", res], + "output" -> StringJoin[DebuggerEvaluate[ + <|"expression" -> text|>, + state["debugSession"]["subKernel"] + ] + ], "variableReference" -> 1 |> |>]]; + sendMessage[state["debugSession"]["client"], DapEvent[<| + "type" -> "event", + "event" -> "output", + "body" -> <| + "category" -> "stdout", + "output" -> "", + "group" -> "end" + |> + |>]]; + sendMessage[state["debugSession"]["client"], DapEvent[<| "type" -> "event", "event" -> "stopped", "body" -> <| "reason" -> "pause", "description" -> "Cell Evaluated Successfully", - "threadId" -> state["debugSession"]["thread"]["id"] + "threadId" -> ( + GetThreads[state["debugSession"]["subKernel"]] + // First + // Key["id"] + ) + (* , "allThreadsStopped" -> True *) |> |>]]; @@ -1904,9 +1957,7 @@ handleResponse["workspace/applyEdit", msg_, state_WorkState] := ( handleDapRequest["initialize", msg_, state_WorkState] := Block[ { - subKernel = LaunchKernels[1] // First, - context = ToString[Unique["debug"]] <> "`", - newSymbolTable + subKernel = CreateDebuggerKernel[] }, LogInfo["Initializing Wolfram Language Debugger"]; @@ -1922,23 +1973,6 @@ handleDapRequest["initialize", msg_, state_WorkState] := Block[ |> |>]]; - $DistributedContexts = None; - newSymbolTable = (Begin[context]; SymbolTable; End[]); - (* StringJoin[ - context, "`SymbolTable = <||>;", - "$NewSymbol = ((", context, "`SymbolTable = - Merge[{", context, "`SymbolTable, <|#2 -> #1|>}, Flatten] - )&);", - "$Epilog := " - ] // *) - ParallelEvaluate[ - newSymbolTable = <||>; - $NewSymbol = ((newSymbolTable = - Merge[{newSymbolTable, <|#2 -> #1|>}, Flatten] - )&), - subKernel - ]; - sendMessage[state["debugSession"]["client"], DapEvent[<| "type" -> "event", "event" -> "initialized" @@ -1951,10 +1985,10 @@ handleDapRequest["initialize", msg_, state_WorkState] := Block[ "subKernel" -> subKernel, "context" -> context, "thread" -> DapThread[<| - "id" -> ParallelEvaluate[$ProcessID, subKernel], + "id" -> GetKernelId[subKernel], "name" -> StringJoin[ "SubKernel ", - ParallelEvaluate[$KernelID, subKernel] + GetProcessId[subKernel] // ToString ] |>], @@ -1997,9 +2031,7 @@ handleDapRequest["attach", msg_, state_WorkState] := ( "request_seq" -> msg["seq"], "success" -> True, "command" -> msg["command"], - "body" -> <| - Nothing - |> + "body" -> <||> |>]]; sendMessage[state["debugSession"]["client"], DapEvent[<| @@ -2077,9 +2109,9 @@ handleDapRequest["threads", msg_, state_WorkState] := ( "success" -> True, "command" -> msg["command"], "body" -> <| - "threads" -> { - state["debugSession"]["thread"] - } + "threads" -> GetThreads[ + state["debugSession"]["subKernel"] + ] |> |>]]; @@ -2088,34 +2120,92 @@ handleDapRequest["threads", msg_, state_WorkState] := ( (* ::Subsection:: *) -(*evaluate*) +(*stackTrace*) -handleDapRequest["evaluate", msg_, state_WorkState] := Block[ - { - text - }, +handleDapRequest["stackTrace", msg_, state_WorkState] := ( + sendMessage[state["debugSession"]["client"], DapResponse[<| + "type" -> "response", + "request_seq" -> msg["seq"], + "success" -> True, + "command" -> msg["command"], + "body" -> <| + "stackFrames" -> GetStackFrames[ + msg["arguments"], + state["debugSession"]["subKernel"] + ] + |> + |>]]; + + {"Continue", state} +) - text = msg["arguments"]["expression"]; - res = With[{text = text}, - ParallelEvaluate[ - ToExpression[text], - state["debugSession"]["subKernel"] - ] - ] // ToString; +(* ::Subsection:: *) +(*scopes*) + + +handleDapRequest["scopes", msg_, state_WorkState] := ( sendMessage[state["debugSession"]["client"], DapResponse[<| "type" -> "response", "request_seq" -> msg["seq"], "success" -> True, "command" -> msg["command"], "body" -> <| - "result" -> res + "scopes" -> GetScopes[ + msg["arguments"], + state["debugSession"]["subKernel"] + ] |> |>]]; {"Continue", state} - ] +) + + +(* ::Subsection:: *) +(*variables*) + + +handleDapRequest["variables", msg_, state_WorkState] := ( + sendMessage[state["debugSession"]["client"], DapResponse[<| + "type" -> "response", + "request_seq" -> msg["seq"], + "success" -> True, + "command" -> msg["command"], + "body" -> <| + "variables" -> GetVariables[ + msg["arguments"], + state["debugSession"]["subKernel"] + ] + |> + |>]]; + + {"Continue", state} +) + + +(* ::Subsection:: *) +(*evaluate*) + + +handleDapRequest["evaluate", msg_, state_WorkState] := ( + + sendMessage[state["debugSession"]["client"], DapResponse[<| + "type" -> "response", + "request_seq" -> msg["seq"], + "success" -> True, + "command" -> msg["command"], + "body" -> <| + "result" -> DebuggerEvaluate[ + msg["arguments"], + state["debugSession"]["subKernel"] + ] + |> + |>]]; + + {"Continue", state} + ) (* ::Subsection:: *) diff --git a/src/WolframLanguageServer/Specification.wl b/src/WolframLanguageServer/Specification.wl index 4d9055a..4140906 100644 --- a/src/WolframLanguageServer/Specification.wl +++ b/src/WolframLanguageServer/Specification.wl @@ -51,6 +51,9 @@ ColorPresentation::usage = "is the type of ColorPresentation interface in LSP." DapEvent::usage = "is the type of Event interface in DAP." DapResponse::usage = "is the type of Response interface in DAP." DapThread::usage = "is the type of Thread interface in DAP." +StackFrame::usage = "is the type of StackFrame interface in DAP." +Scope::usage = "is the type of Scope interface in DAP." +DapVariable::usage = "is the type of Variable interface in DAP." (* ::Section:: *) @@ -402,8 +405,8 @@ DeclareType[DapEvent, <| DeclareType[DapResponse, <| "seq" -> _Integer, "type" -> "response", - "request_seq -> _Integer, - "success" -> ?BooleanQ, + "request_seq" -> _Integer, + "success" -> _?BooleanQ, "command" -> _String, "message" -> _String, "body" -> _ @@ -414,6 +417,30 @@ DeclareType[DapThread, <| "name" -> _String |>] +DeclareType[StackFrame, <| + "id" -> _?NumericQ, + "name" -> _String, + "line" -> _Integer, + "column" -> _Integer +|>] + +DeclareType[Scope, <| + "name" -> _String, + "variablesReference" -> _Integer, + "namedVariables" -> _Integer, + "indexedVariables" -> _Integer, + "expensive" -> _?BooleanQ +|>] + +DeclareType[DapVariable, <| + "name" -> _String, + "value" -> _String, + "type" -> _String, + "variablesReference" -> _Integer, + "namedVariables" -> _Integer, + "indexedVariables" -> _Integer +|>] + End[] From 6695f596b44686341d22bf9cacc046899e7aef84 Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Wed, 9 Sep 2020 17:31:35 -0700 Subject: [PATCH 26/32] :sparkles: Pre-alpha version of the Variables handler Known issues: some expressions are parsed to have children --- src/WolframLanguageServer/Adaptor.wl | 117 +++++++++ src/WolframLanguageServer/Debugger.wl | 363 ++++++++++++++++++++------ src/WolframLanguageServer/Server.wl | 81 +++--- 3 files changed, 445 insertions(+), 116 deletions(-) create mode 100644 src/WolframLanguageServer/Adaptor.wl diff --git a/src/WolframLanguageServer/Adaptor.wl b/src/WolframLanguageServer/Adaptor.wl new file mode 100644 index 0000000..f4d31ad --- /dev/null +++ b/src/WolframLanguageServer/Adaptor.wl @@ -0,0 +1,117 @@ +(* ::Package:: *) + +(* Copyright 2020 lsp-wl Authors *) +(* SPDX-License-Identifier: MIT *) + + +(* Wolfram Language Server Debugger Adaptor *) + + +BeginPackage["WolframLanguageServer`Adaptor`"] +ClearAll[Evaluate[Context[] <> "*"]] + + +(* Output Symbols *) +CreateDebuggerKernel::usage = "Create a subkernel as a debugger, and returns the KernelObject." +CloseDebuggerKernel::usage = "CloseDebuggerKernel[_KernelObject] closed the specified subkernel." +GetKernelId::usage = "GetKernelId[kernel_KernelObject] returns the $KernelID of the specified kernel." +GetProcessId::usage = "GetProcessId[kernel_KernelObject] returns the $ProcessID of the specified kernel." +GetThreads::usage = "GetThreads[kernel_KernelObject] returns the available thread of the specified kernel. \ +(Currntly only one thread is available.)" +GetStackFrames::usage = "GetStackFrames[stackTraceArguments_Association, kernel_KernelObject] \ +returns the stackframes of the specified thread." +GetScopes::usage = "GetScopes[scopesArguments_Association, kernel_KernelObject] returns the scopes of the specified stackframe." +GetVariables::usage = "GetVariables[variablesArguments_Association, kernel_KernelObject] returns the variables according to the arguments." +DebuggerEvaluate::usage = "DebuggerEvaluate[evaluateArguments_Association, kernel_KernelObject] \ +evaluate the given expr in the debugger and return the string form." + + +(* Private Context *) +Begin["`Private`"] +ClearAll[Evaluate[Context[] <> "*"]] + + +Needs["DataType`"] +Needs["WolframLanguageServer`Logger`"] +Needs["WolframLanguageServer`Specification`"] +Needs["WolframLanguageServer`Debugger`"] + + +$DistributedContexts = "WolframLanguageServer`Debugger`" + + +CreateDebuggerKernel[] := With[ + { + subKernel = LaunchKernels[1] // First, + path = $Path + }, + ParallelEvaluate[ + $Path = path; + Needs["WolframLanguageServer`Debugger`"]; + DebuggerInit[], + subKernel + ]; + subKernel +] + + +GetKernelId[kernel_KernelObject] := ParallelEvaluate[$KernelID, kernel] + + +GetProcessId[kernel_KernelObject] := ParallelEvaluate[$ProcessID, kernel] + + +GetThreads[kernel_KernelObject] := { + DapThread[<| + "id" -> 1, + "name" -> "default" + |>] +} + + +GetStackFrames[stackTraceArguments_Association, kernel_KernelObject] := { + StackFrame[<| + "id" -> 0, + "name" -> "default", + "line" -> 0, + "column" -> 0 + |>] +} + + +GetScopes[scopesArguments_Association, kernel_KernelObject] := ( + ParallelEvaluate[GetContextsReferences[], kernel] +) + + +GetVariables[variablesArguments_Association, kernel_KernelObject] := ( + ParallelEvaluate[GetVariablesReference[variablesArguments], kernel] + // LogDebug +) + + +DebuggerEvaluate[evaluateArguments_Association, kernel_KernelObject] := ( + evaluateArguments["expression"] + // StringTrim + // ParallelEvaluate[ + (* Keeps Stack[] clean *) + ToExpression[#, InputForm, Hold] + // ReleaseHold, + kernel, + (* This will save all the results (per line) into a sequence. *) + Hold + ]& + (* Do not let the results evaluate anymore in the adaptor-side *) + // DeleteCases[Null] + // Map[Unevaluated] + // Apply[List] + // Map[ToString] + // StringRiffle[#, "\n"]& + // (ParallelEvaluate[GetContextsReferences[], kernel]; #)& +) + + +End[] + + +EndPackage[] \ No newline at end of file diff --git a/src/WolframLanguageServer/Debugger.wl b/src/WolframLanguageServer/Debugger.wl index ba00802..5da702b 100644 --- a/src/WolframLanguageServer/Debugger.wl +++ b/src/WolframLanguageServer/Debugger.wl @@ -12,19 +12,9 @@ ClearAll[Evaluate[Context[] <> "*"]] (* Output Symbols *) -CreateDebuggerKernel::usage = "Create a subkernel as a debugger, and returns the KernelObject." -CloseDebuggerKernel::usage = "CloseDebuggerKernel[_KernelObject] closed the specified subkernel." -GetKernelId::usage = "GetKernelId[kernel_KernelObject] returns the $KernelID of the specified kernel." -GetProcessId::usage = "GetProcessId[kernel_KernelObject] returns the $ProcessID of the specified kernel." -GetThreads::usage = "GetThreads[kernel_KernelObject] returns the available thread of the specified kernel. \ -(Currntly only one thread is available.)" -GetStackFrames::usage = "GetStackFrames[stackTraceArguments_Association, kernel_KernelObject] \ -returns the stackframes of the specified thread." -GetScopes::usage = "GetScopes[scopesArguments_Association, kernel_KernelObject] returns the scopes of the specified stackframe." -GetVariables::usage = "" -DebuggerEvaluate::usage = "DebuggerEvaluate[evaluateArguments_Association, kernel_KernelObject] \ -evaluate the given expr in the debugger and return the string form." - +DebuggerInit::usage = "" +GetContextsReferences::usage = "" +GetVariablesReference::usage = "" (* Private Context *) Begin["`Private`"] @@ -36,89 +26,310 @@ Needs["WolframLanguageServer`Logger`"] Needs["WolframLanguageServer`Specification`"] -$DistributedContexts = None +$ExpensiveContexts = {"System`"} -CreateDebuggerKernel[] := With[ - { - subKernel = LaunchKernels[1] // First - }, - ParallelEvaluate[ - debugger`SymbolTable = <||>; - $NewSymbol = ((debugger`SymbolTable = - Merge[{debugger`SymbolTable, <|#2 -> #1|>}, Flatten] - )&); - $Epilog, - subKernel - ]; - subKernel -] +DebuggerInit[] := ( + $SymbolTable = <|"Global`" -> {}|>; + $ReferenceTable = <||>; + $NewSymbol = (($SymbolTable = + Merge[{$SymbolTable, <|#2 -> #1|>}, Flatten] + )&) +) -GetKernelId[kernel_KernelObject] := ParallelEvaluate[$KernelID, kernel] +appendReference[type_, name_String, pos_:Nothing] := With[ + { + newKey = (Length[$ReferenceTable] + 1) + }, + AppendTo[$ReferenceTable, newKey -> ( + {type, name, pos} + )]; + newKey +] -GetProcessId[kernel_KernelObject] := ParallelEvaluate[$ProcessID, kernel] +GetContextsReferences[] := ( + $ReferenceTable = <| 1 -> {} |>; + Table[ + Scope[<| + "name" -> context, + "variablesReference" -> appendReference["Context", context], + "expensive" -> If[MemberQ[$ExpensiveContexts, context], True, False], + If[MemberQ[$ExpensiveContexts, context], + "namedVariables" -> Length[Names[context <> "*"]], + Nothing + ] + |>], + {context, Keys[$SymbolTable]} + ] +) -GetThreads[kernel_KernelObject] := { - DapThread[<| - "id" -> 1, - "name" -> "default" - |>] -} +(* Shall return the pre-side-effect results *) +GetVariablesReference[variablesArguments_Association] := ( + $ReferenceTable + // Key[variablesArguments["variablesReference"]] + // Replace[{ + _?MissingQ -> {}, + {"Context", context_String} :> ( + Names[context <> "*"] + // Map[analyzeSymbol] + // DeleteMissing + ), + {"Symbol", symbolName_String} :> ( + {analyzeSymbol[symbolName]} + // DeleteMissing + ), + {"AssocList", symbolName_String} :> ( + Table[ + symbolName + // ToExpression[#, InputForm, Unevaluated]& + // valuesFunc[#]& + // Replace[{ + valueList:Except[{}] :> ( + DapVariable[<| + "name" -> (valuesFunc // ToString), + "value" -> ( + If[valuesFunc === Attributes, + valueList, + (valueList // Keys) + ] // ToNonContextString[#]& + ), + "type" -> If[valuesFunc === Attributes, "List", "Rule List"], + "variablesReference" -> ( + appendReference["AssocValues", symbolName, valuesFunc] + ), + If[valuesFunc === Attributes, + "indexedVariables", + "namedVariables" + ] -> Length[valueList] + |>] + ), + {} -> Nothing + }], + {valuesFunc, {DownValues, SubValues, UpValues, Options, Attributes}} + ] + ), + { + "AssocValues", + symbolName_String, + valuesFunc:(OwnValues | DownValues | SubValues | UpValues | Options | Attributes) + } :> Block[ + { + values = symbolName + // ToExpression[#, InputForm, Unevaluated]& + // valuesFunc[#]& + }, + Table[ + createVariableWithTag[ + If[valuesFunc === Attributes, + index // ToString, + Part[values, index, 1] // ToNonContextString[#]& + ], + symbolName, + values // Extract[#, If[valuesFunc === Attributes, + {index}, + {index, 2} + ], Unevaluated]&, + {{valuesFunc, index}, {}} + ], + {index, Length[values]} + ] + ], + { + listType: "List" | "Association", + symbolName_String, + { + { + valuesFunc:(OwnValues | DownValues | SubValues | UpValues | Attributes | Options), + valueIndex_Integer + }, + {pos___} + } + } :> Block[ + { + list = symbolName + // ToExpression[#, InputForm, Unevaluated]& + // valuesFunc[#]& + // Extract[#, {valueIndex, 2, pos}, Unevaluated]& + }, + Table[ + createVariableWithTag[ + If[listType == "List", + index // ToString, + index // First // ToString + ], + symbolName, + list // Extract[#, index, Unevaluated]&, + {{valuesFunc, valueIndex}, {pos, index}} + ], + { + index, + If[listType == "List", + list + // Length[#]&, + list + // Keys[#]& + // Map[Key] + ] + } + ] + ], + _ -> {} + }] +) -GetStackFrames[stackTraceArguments_Association, kernel_KernelObject] := { - StackFrame[<| - "id" -> 0, - "name" -> "default", - "line" -> 0, - "column" -> 0 - |>] -} +SetAttributes[ToNonContextString, HoldFirst] + +(* + This will turn "x_pattern" into "Pattern[x, patter]". + A complete but slow solution is to parse the string with CodeParser and + delete contexts by source. +*) +ToNonContextString[expr_] := ( + expr + // Hold + // ReplaceAll[ + symbol_Symbol :> With[ + { + symbolName = SymbolName[Unevaluated[symbol]] + }, + symbolName + /; ( + (* Removes the context other than System` (operators) and Global` (not necessary) *) + {"System`", "Global`"} + // MemberQ[Context[symbol]] + // Not + )] + ] + // Extract[#, {1}, Unevaluated]& + // ToString[#]& +) -GetScopes[scopesArguments_Association, kernel_KernelObject] := Block[ - { - symbolTable = ParallelEvaluate[debugger`SymbolTable, kernel] - }, - symbolTable - // KeyValueMap[ - Scope[<| - "name" -> #1, - "variablesReference" -> $ModuleNumber, - "namedVariables" -> Length[#2], - "expensive" -> False - |>]& - ] -] +analyzeSymbol[symbolName_String] := ( + symbolName + // ToExpression[#, InputForm, Unevaluated]& + // Replace[{ + _?(Attributes /* MemberQ[ReadProtected]) -> Missing["NoValues"], + symbol_ :> Block[ + { + ownValues + }, + createVariableWithTag[ + SymbolName[symbol], + symbolName, + ownValues + // Extract[#, {1, 2}, Unevaluated]&, + {{OwnValues, 1}, {}} + ] + /; ( + symbol + // OwnValues + // If[# =!= {}, + ownValues = symbol // OwnValues; + True, + False + ]& + ) + ], + symbol_ :> Block[ + { + length + }, + DapVariable[<| + "name" -> SymbolName[symbol], + "value" -> "", + "type" -> "Function", + "variablesReference" -> appendReference["AssocList", symbolName], + "namedVariables" -> length, + "expensive" -> False + |>] + /; ( + symbol + // {DownValues, SubValues, UpValues} + // Through + // DeleteCases[{}] + // Length + // (length = #)& + // (# > 0)& + ) + ], + _ -> Nothing + }] +) -GetVariables[variablesArguments_Association, kernel_KernelObject] := ( - { +createVariableWithTag[tag_String, symbolName_String, expr_, nextPos_] := ( + expr + // Unevaluated + // analyzeExpr + // Apply[{value, type, length} \[Function] ( DapVariable[<| - "name" -> "test", - "value" -> "1", - "type" -> "SubValues", - "variablesReference" -> 0, - "namedVariables" -> 0, - "indexedVariables" -> 0 + "name" -> (tag // ToString), + "value" -> (value), + "type" -> ( + type + // Replace["Symbol"|"Expression" -> "Variable"] + ), + "variablesReference" -> If[length == 0, + 0, + If[type == "Symbol", + appendReference["Symbol", value], + appendReference[type, symbolName, nextPos] + ] + ], + type + // Replace[{ + "List" -> ( + "indexedVariables" -> length + ), + "Association" | "Symbol" -> ( + "namedVariables" -> length + ), + "Expression" -> Nothing + }], + "expensive" -> False |>] - } + )] ) -DebuggerEvaluate[evaluateArguments_Association, kernel_KernelObject] := ( - evaluateArguments["expression"] - // StringTrim - // ParallelEvaluate[ - ToExpression[#, InputForm, Hold] - // ReleaseHold, - kernel, - ToString - ]& +SetAttributes[analyzeExpr, HoldAll] +analyzeExpr[expr_] := ( + expr + // Unevaluated + // Replace[{ + list_?ListQ :> { + "List", + list // Length + }, + association_?AssociationQ :> { + "Association", + association + // Length + }, + symbol:Unevaluated[_Symbol] /; ( + symbol + // Attributes + // MemberQ[ReadProtected] + ) -> {"Expression", 0}, + symbol:Unevaluated[_Symbol] :> { + "Symbol", + symbol + // {OwnValues, DownValues, SubValues, UpValues} + // Through + // If[SameQ[#, Range[{}, 4]], 0, 1]& + }, + _ -> {"Expression", 0} + }] + // Prepend[ + expr + // ToNonContextString + ] ) diff --git a/src/WolframLanguageServer/Server.wl b/src/WolframLanguageServer/Server.wl index f2b1d1d..5a309df 100644 --- a/src/WolframLanguageServer/Server.wl +++ b/src/WolframLanguageServer/Server.wl @@ -27,7 +27,7 @@ Needs["WolframLanguageServer`Logger`"] Needs["WolframLanguageServer`Specification`"] Needs["WolframLanguageServer`TextDocument`"] Needs["WolframLanguageServer`Token`"] -Needs["WolframLanguageServer`Debugger`"] +Needs["WolframLanguageServer`Adaptor`"] (* ::Section:: *) @@ -893,6 +893,7 @@ sendMessage[client_, res:(_ResponseMessage|_NotificationMessage)] := ( (* For DAP: event and response *) sendMessage[client_, res:(_DapEvent|_DapResponse)] := ( res + // LogDebug // ToAssociation // constructRPCBytes // WriteMessage[client] @@ -1015,47 +1016,30 @@ executeCommand["dap-wl.run-range", msg_, state_WorkState] := Block[ text }, - text = GetDocumentText[ - state["openedDocs"][args["uri"]], - ConstructType[args["range"], _LspRange] - ] // StringTrim; - sendMessage[state["debugSession"]["client"], DapEvent[<| "type" -> "event", - "event" -> "output", + "event" -> "continued", "body" -> <| - "category" -> "stdout", - "output" -> "Input:", - "group" -> "start" - |> - |>]]; - - sendMessage[state["debugSession"]["client"], DapEvent[<| - "type" -> "event", - "event" -> "output", - "body" -> <| - "category" -> "stdout", - "output" -> StringJoin[text], - "variableReference" -> 1 + "threadId" -> ( + GetThreads[state["debugSession"]["subKernel"]] + // First + // Key["id"] + ) + (* , "allThreadsContinued" -> True *) |> |>]]; - sendMessage[state["debugSession"]["client"], DapEvent[<| - "type" -> "event", - "event" -> "output", - "body" -> <| - "category" -> "stdout", - "output" -> "", - "group" -> "end" - |> - |>]]; + text = GetDocumentText[ + state["openedDocs"][args["uri"]], + ConstructType[args["range"], _LspRange] + ] // StringTrim; sendMessage[state["debugSession"]["client"], DapEvent[<| "type" -> "event", "event" -> "output", "body" -> <| - "category" -> "stdout", - "output" -> "Output:", + (* "category" -> "stdout", *) + "output" -> text, "group" -> "start" |> |>]]; @@ -1064,13 +1048,13 @@ executeCommand["dap-wl.run-range", msg_, state_WorkState] := Block[ "type" -> "event", "event" -> "output", "body" -> <| - "category" -> "stdout", - "output" -> StringJoin[DebuggerEvaluate[ - <|"expression" -> text|>, - state["debugSession"]["subKernel"] + (* "category" -> "stdout", *) + "output" -> ( + DebuggerEvaluate[ + <|"expression" -> text|>, + state["debugSession"]["subKernel"] ] - ], - "variableReference" -> 1 + ) |> |>]]; @@ -1078,9 +1062,10 @@ executeCommand["dap-wl.run-range", msg_, state_WorkState] := Block[ "type" -> "event", "event" -> "output", "body" -> <| - "category" -> "stdout", + (* "category" -> "stdout", *) "output" -> "", - "group" -> "end" + "group" -> "end", + "variablesReference" -> 2 |> |>]]; @@ -2014,6 +1999,21 @@ handleDapRequest["configurationDone", msg_, state_WorkState] := ( "body" -> <||> |>]]; + sendMessage[state["debugSession"]["client"], DapEvent[<| + "type" -> "event", + "event" -> "stopped", + "body" -> <| + "reason" -> "pause", + "description" -> "Cell Evaluated Successfully", + "threadId" -> ( + GetThreads[state["debugSession"]["subKernel"]] + // First + // Key["id"] + ) + (* , "allThreadsStopped" -> True *) + |> + |>]]; + {"Continue", state} ) @@ -2200,7 +2200,8 @@ handleDapRequest["evaluate", msg_, state_WorkState] := ( "result" -> DebuggerEvaluate[ msg["arguments"], state["debugSession"]["subKernel"] - ] + ], + "variablesReference" -> 0 |> |>]]; From 5e8ed5f456a87d517962a3233905acdcd2a3c8bc Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Sun, 24 Jan 2021 01:01:47 -0800 Subject: [PATCH 27/32] Replace dummyAll with codeLens/refresh - Add RequestMessage to send request from server side. --- src/WolframLanguageServer/Server.wl | 63 +++++++++++----------- src/WolframLanguageServer/Specification.wl | 12 ++++- 2 files changed, 40 insertions(+), 35 deletions(-) diff --git a/src/WolframLanguageServer/Server.wl b/src/WolframLanguageServer/Server.wl index 5a309df..49d86d6 100644 --- a/src/WolframLanguageServer/Server.wl +++ b/src/WolframLanguageServer/Server.wl @@ -882,7 +882,7 @@ scheduleDelayedRequest[method_String, msg_, state_WorkState] := ( (* For LSP: response, notification and request *) -sendMessage[client_, res:(_ResponseMessage|_NotificationMessage)] := ( +sendMessage[client_, res:(_RequestMessage|_ResponseMessage|_NotificationMessage)] := ( res // ReplaceKey["jsonrpc" -> "2.0"] // ToAssociation @@ -1867,17 +1867,19 @@ getRequestId[] := ( "req_" <> ToString[($requestId += 1)] ) + (* ::Subsection:: *) -(*applyEdit*) +(*workspace/applyEdit*) + sendRequest[method:"workspace/applyEdit", msg_, state_WorkState] := With[ { id = getRequestId[] }, - sendMessage[state["client"], NotificationMessage[<| + sendMessage[state["client"], RequestMessage[<| "id" -> id, - "method" -> "workspace/applyEdit", + "method" -> method, "params" -> <| "edit" -> msg["params"]["edit"] |> @@ -1890,40 +1892,35 @@ sendRequest[method:"workspace/applyEdit", msg_, state_WorkState] := With[ } ] -applyEdit["dummyAll", state_WorkState] := ( - sendRequest[ - "workspace/applyEdit", - Table[uri -> { - TextEdit[<| - "range" -> LspRange[<| - "start" -> <| - "line" -> 0, - "character" -> 0 - |>, - "end" -> <| - "line" -> 0, - "character" -> 1 - |> - |>], - "newText" -> ( - state["openedDocs"][uri]["text"] - // First - // StringTake[#, 1]& - ) - |>] - }, {uri, Keys[state["openedDocs"]]}] - // WorkspaceEdit[<|"changes" -> <|#|>|>]& - // <|"params" -> <|"edit" -> #|>|>&, + +(* ::Subsection:: *) +(*workspace/codeLens/refresh*) + + +sendRequest[method:"workspace/codeLens/refresh", state_WorkState] := With[ + { + id = getRequestId[] + }, + + sendMessage[state["client"], RequestMessage[<| + "id" -> id, + "method" -> method, + "params" -> <||> + |>]]; + + { + "Continue", state - ] -) + // ReplaceKeyBy["pendingServerRequests" -> Append[id -> method]] + } +] (* ::Section:: *) (*Handle Response*) -handleResponse["workspace/applyEdit", msg_, state_WorkState] := ( +handleResponse[_, msg_, state_WorkState] := ( { "Continue", state @@ -1979,7 +1976,7 @@ handleDapRequest["initialize", msg_, state_WorkState] := Block[ |>], "symbolTable" -> <||> }]&)] - // applyEdit["dummyAll", #]& + // sendRequest["workspace/codeLens/refresh", #]& ] @@ -2075,7 +2072,7 @@ handleDapRequest["disconnect", msg_, state_WorkState] := ( state // ReplaceKey[{"debugSession", "initialized"} -> False] // ReplaceKey[{"debugSession", "client"} -> Null] - // applyEdit["dummyAll", #]& + // sendRequest["workspace/codeLens/refresh", #]& ) diff --git a/src/WolframLanguageServer/Specification.wl b/src/WolframLanguageServer/Specification.wl index 4140906..9bbf310 100644 --- a/src/WolframLanguageServer/Specification.wl +++ b/src/WolframLanguageServer/Specification.wl @@ -15,7 +15,8 @@ ClearAll[Evaluate[Context[] <> "*"]]; (* Language Server Protocol*) -ResponseMessage::usage = "is type of RequestMessage interface in LSP." +RequestMessage::usage = "is type of RequestMessage interface in LSP." +ResponseMessage::usage = "is type of ResponseMessage interface in LSP." ResponseError::usage = "is type of RequestError interface in LSP." NotificationMessage::usage = "is type of Notification interface in LSP." LspPosition::usage = "is type of Position interface in LSP." @@ -205,11 +206,18 @@ Needs["DataType`"] (*Basic Protocol*) +DeclareType[RequestMessage, <| + "jsonrpc" -> _String, + "id" -> _Integer | _String, + "method" -> _String, + "params" -> _ +|>] + DeclareType[ResponseMessage, <| "jsonrpc" -> _String, "id" -> _Integer | _String, "result" -> _, - "error" -> _ + "error" -> _ResponseError |>] From 4e7a323ebc459c68c594a112d293a0f42de0ef33 Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Sun, 24 Jan 2021 01:55:39 -0800 Subject: [PATCH 28/32] Improve: better evaluation format - rename run-* command to evaluate-* command. - Implement evaluate-file command - Add "Evaluate in Debug Console" to CodeAction - Prevent the expression in variables from being evaluated again. --- src/WolframLanguageServer/Adaptor.wl | 38 +++--- src/WolframLanguageServer/Server.wl | 139 +++++++++++++--------- src/WolframLanguageServer/TextDocument.wl | 20 ++-- 3 files changed, 118 insertions(+), 79 deletions(-) diff --git a/src/WolframLanguageServer/Adaptor.wl b/src/WolframLanguageServer/Adaptor.wl index f4d31ad..25f2c2f 100644 --- a/src/WolframLanguageServer/Adaptor.wl +++ b/src/WolframLanguageServer/Adaptor.wl @@ -91,23 +91,27 @@ GetVariables[variablesArguments_Association, kernel_KernelObject] := ( DebuggerEvaluate[evaluateArguments_Association, kernel_KernelObject] := ( - evaluateArguments["expression"] - // StringTrim - // ParallelEvaluate[ - (* Keeps Stack[] clean *) - ToExpression[#, InputForm, Hold] - // ReleaseHold, - kernel, - (* This will save all the results (per line) into a sequence. *) - Hold - ]& - (* Do not let the results evaluate anymore in the adaptor-side *) - // DeleteCases[Null] - // Map[Unevaluated] - // Apply[List] - // Map[ToString] - // StringRiffle[#, "\n"]& - // (ParallelEvaluate[GetContextsReferences[], kernel]; #)& + If[evaluateArguments["context"] === "variables", + evaluateArguments["expression"], + evaluateArguments["expression"] + // StringTrim + // ParallelEvaluate[ + (* Keeps Stack[] clean *) + ToExpression[#, InputForm, Hold] + // ReleaseHold, + kernel, + (* This will save all the results (per line) into a sequence. *) + Hold + ]& + // Replace[$Failed -> Hold[]] + // DeleteCases[Null] + (* Do not let the results evaluate anymore in the adaptor-side *) + // Map[Unevaluated] + // Apply[List] + // Map[ToString] + // StringRiffle[#, "\n"]& + // (ParallelEvaluate[GetContextsReferences[], kernel]; #)& + ] ) diff --git a/src/WolframLanguageServer/Server.wl b/src/WolframLanguageServer/Server.wl index 49d86d6..bb07909 100644 --- a/src/WolframLanguageServer/Server.wl +++ b/src/WolframLanguageServer/Server.wl @@ -118,8 +118,8 @@ ServerCapabilities = <| "executeCommandProvider" -> <| "commands" -> { "openRef", - "dap-wl.run-file", - "dap-wl.run-range" + "dap-wl.evaluate-file", + "dap-wl.evaluate-range" } |>, Nothing @@ -789,17 +789,7 @@ handleDapMessage[msg_Association, state_WorkState] := Module[ newState = state }, - Replace[msg["type"], { - "request" :> ( - LogDebug @ Iconize[msg["command"], msg["arguments"]] - ), - "event" :> ( - LogDebug @ Iconize[msg["event"], msg["body"]] - ), - "respond" :> ( - LogDebug @ Iconize[msg["command"], msg["body"], msg["message"]] - ) - }]; + LogDebug["handleDapMessage" <> msg]; Which[ (* wrong message before initialization *) @@ -969,8 +959,8 @@ handleRequest["workspace/executeCommand", msg_, state_] := With[ (*openRef*) -executeCommand["openRef", msg_, state_WorkState] := ( - msg["params"]["arguments"] +executeCommand["openRef", msg_, state_WorkState] := ( + msg["params"]["arguments"] // First // SystemOpen // UsingFrontEnd; @@ -981,21 +971,64 @@ executeCommand["openRef", msg_, state_WorkState] := ( |>]]; {"Continue", state} -) +) (* ::Subsubsection:: *) -(*dap-wl.run-file*) +(*dap-wl.evaluate-file*) -executeCommand["dap-wl.run-file", msg_, state_WorkState] := With[ +executeCommand["dap-wl.evaluate-file", msg_, state_WorkState] := With[ { args = msg["params"]["arguments"] // First }, - LogDebug[{"Running", - args["uri"] - }]; + sendMessage[state["debugSession"]["client"], DapEvent[<| + "type" -> "event", + "event" -> "continued", + "body" -> <| + "threadId" -> ( + GetThreads[state["debugSession"]["subKernel"]] + // First + // Key["id"] + ) + (* , "allThreadsContinued" -> True *) + |> + |>]]; + + text = state["openedDocs"][args["uri"]] + // GetDocumentText + // StringTrim + // (LogDebug["Evaluating " <> #]; #)&; + + sendMessage[state["debugSession"]["client"], DapEvent[<| + "type" -> "event", + "event" -> "output", + "body" -> <| + (* "category" -> "stdout", *) + "output" -> ( + DebuggerEvaluate[ + <|"expression" -> text|>, + state["debugSession"]["subKernel"] + ] + ) + |> + |>]]; + + sendMessage[state["debugSession"]["client"], DapEvent[<| + "type" -> "event", + "event" -> "stopped", + "body" -> <| + "reason" -> "pause", + "description" -> "Cell Evaluated Successfully", + "threadId" -> ( + GetThreads[state["debugSession"]["subKernel"]] + // First + // Key["id"] + ) + (* , "allThreadsStopped" -> True *) + |> + |>]]; sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], @@ -1007,10 +1040,10 @@ executeCommand["dap-wl.run-file", msg_, state_WorkState] := With[ (* ::Subsubsection:: *) -(*dap-wl.run-range*) +(*dap-wl.evaluate-range*) -executeCommand["dap-wl.run-range", msg_, state_WorkState] := Block[ +executeCommand["dap-wl.evaluate-range", msg_, state_WorkState] := Block[ { args = msg["params"]["arguments"] // First, text @@ -1029,20 +1062,10 @@ executeCommand["dap-wl.run-range", msg_, state_WorkState] := Block[ |> |>]]; - text = GetDocumentText[ - state["openedDocs"][args["uri"]], - ConstructType[args["range"], _LspRange] - ] // StringTrim; - - sendMessage[state["debugSession"]["client"], DapEvent[<| - "type" -> "event", - "event" -> "output", - "body" -> <| - (* "category" -> "stdout", *) - "output" -> text, - "group" -> "start" - |> - |>]]; + text = state["openedDocs"][args["uri"]] + // GetDocumentText[#, ConstructType[args["range"], _LspRange]]& + // StringTrim + // (LogDebug["Evaluating " <> #]; #)&; sendMessage[state["debugSession"]["client"], DapEvent[<| "type" -> "event", @@ -1054,21 +1077,11 @@ executeCommand["dap-wl.run-range", msg_, state_WorkState] := Block[ <|"expression" -> text|>, state["debugSession"]["subKernel"] ] + // (# <> "\n")& ) |> |>]]; - sendMessage[state["debugSession"]["client"], DapEvent[<| - "type" -> "event", - "event" -> "output", - "body" -> <| - (* "category" -> "stdout", *) - "output" -> "", - "group" -> "end", - "variablesReference" -> 2 - |> - |>]]; - sendMessage[state["debugSession"]["client"], DapEvent[<| "type" -> "event", "event" -> "stopped", @@ -1436,10 +1449,28 @@ handleRequest["textDocument/codeAction", msg_, state_] := With[ sendMessage[state["client"], ResponseMessage[<| "id" -> msg["id"], - "result" -> GetCodeActionsInRange[ - state["openedDocs"][uri], - range - ] + "result" -> ( + GetCodeActionsInRange[ + state["openedDocs"][uri], + range + ] // If[state["debugSession"]["initialized"], + Append[ + LspCodeAction[<| + "title" -> "Evaluate in Debug Console", + "kind" -> CodeActionKind["Empty"], + "command" -> <| + "title" -> "Evaluate in Debug Console", + "command" -> "dap-wl.evaluate-range", + "arguments" -> {<| + "uri" -> uri, + "range" -> range + |>} + |> + |>] + ], + Identity + ] + ) |>]]; {"Continue", state} @@ -1474,7 +1505,7 @@ handleRequest["textDocument/codeLens", msg_, state_] := With[ |>, "data" -> <| "title" -> "$(workflow) Evaluate File", - "command" -> "dap-wl.run-file", + "command" -> "dap-wl.evaluate-file", "arguments" -> {<| "uri" -> uri |>} @@ -1491,7 +1522,7 @@ handleRequest["textDocument/codeLens", msg_, state_] := With[ ), "data" -> <| "title" -> "$(play) Evaluate", - "command" -> "dap-wl.run-range", + "command" -> "dap-wl.evaluate-range", "arguments" -> {<| "uri" -> uri, "range" -> codeRange diff --git a/src/WolframLanguageServer/TextDocument.wl b/src/WolframLanguageServer/TextDocument.wl index a5ac1e1..21f9f3d 100644 --- a/src/WolframLanguageServer/TextDocument.wl +++ b/src/WolframLanguageServer/TextDocument.wl @@ -25,7 +25,8 @@ FindReferences::usage = "FindReferences[doc_TextDocument, pos_LspPosition, o:Opt FindDocumentHighlight::usage = "FindDocumentHighlight[doc_TextDocument, pos_LspPosition] gives a list of DocumentHighlight." FindAllCodeRanges::usage = "FindAllCodeRanges[doc_TextDocument] returns a list of LspRange which locate all the code ranges (cells) in the given doc." GetCodeActionsInRange::usage = "GetCodeActionsInRange[doc_TextDocument, range_LspRange] returns a list of CodeAction related to specified range." -GetDocumentText::usage = "GetDocumentText[doc_TextDocument, range_LspRange] returns the text of the doc at given range." +GetDocumentText::usage = "GetDocumentText[doc_TextDocument] returns the text of the whole doc except for the shebang line (if exists).\n\ +GetDocumentText[doc_TextDocument, range_LspRange] returns the text of the doc at given range." FindDocumentColor::usage = "FindDocumentColor[doc_TextDocument] gives a list of colors in the text document." GetColorPresentation::usage = "GetColorPresentation[doc_TextDocument, color_LspColor, range_LspRange] gives the RGBColor presentation of the color." @@ -533,16 +534,20 @@ FindAllCodeRanges[doc_TextDocument] := ( // Map[ToLspRange[doc, #]&] ) +GetDocumentText[doc_TextDocument] := ( + doc["text"] + // Replace[{_String?(StringStartsQ["#!"]), restLines___} :> ({"", restLines})] + // StringRiffle[#, "\n"]& +) + GetDocumentText[doc_TextDocument, range_LspRange] := ( doc["text"] // Take[#, { range["start"]["line"] + 1, range["end"]["line"] + 1 }]& - // ReplacePart[#, 1 -> - StringDrop[First[#], range["start"]["character"]]]& - // ReplacePart[#, -1 -> - StringTake[Last[#], range["end"]["character"]]]& + // MapAt[StringTake[#, range["end"]["character"]]&, -1] + // MapAt[StringDrop[#, range["start"]["character"]]&, 1] // StringRiffle[#, "\n"]& ) @@ -945,9 +950,8 @@ getHoverInfoImpl[ast_, {index_Integer, restIndices___}] := ( DiagnoseDoc[doc_TextDocument] := ( - doc["text"] - // Replace[{_String?(StringStartsQ["#!"]), restLines___} :> ({"", restLines})] - // StringRiffle[#, "\n"]& + doc + // GetDocumentText // Replace[err:Except[_String] :> (LogError[doc]; "")] // CodeInspector`CodeInspect[#, "TabWidth" -> 1]& // Replace[_?FailureQ -> {}] From 38448afc7d16f7c5e47bf226eb1f184b158d2384 Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Sun, 24 Jan 2021 01:56:22 -0800 Subject: [PATCH 29/32] Fix: HoverInfo in top level --- src/WolframLanguageServer/TextDocument.wl | 16 +++++++--------- test/WolframLanguageServer/TextDocumentTest.wl | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/WolframLanguageServer/TextDocument.wl b/src/WolframLanguageServer/TextDocument.wl index 21f9f3d..5904648 100644 --- a/src/WolframLanguageServer/TextDocument.wl +++ b/src/WolframLanguageServer/TextDocument.wl @@ -893,15 +893,13 @@ GetHoverInfo[doc_TextDocument, pos_LspPosition] := With[ (* get range *) ast // Extract[indices] - // Replace[node:Except[{}] :> ( - node - // Last - // Key[CodeParser`Source] - // Replace[{ - _?MissingQ -> Nothing, - source_ :> SourceToRange[source] - }] - )] + // Replace[{} -> {<||>}] + // Last + // Key[CodeParser`Source] + // Replace[{ + _?MissingQ -> Nothing, + source_ :> SourceToRange[source] + }] }) )) ] diff --git a/test/WolframLanguageServer/TextDocumentTest.wl b/test/WolframLanguageServer/TextDocumentTest.wl index e2ee7f9..8dbdcd7 100644 --- a/test/WolframLanguageServer/TextDocumentTest.wl +++ b/test/WolframLanguageServer/TextDocumentTest.wl @@ -665,6 +665,22 @@ VerificationTest[ |>] }, TestID -> "HoverOperator 2" +], + +VerificationTest[ + GetHoverInfo[ + TextDocument[<| + "text" -> { + "(* this is comment *)" + } + |>], + LspPosition[<| + "line" -> 0, + "character" -> 2 + |>] + ], + {{}}, + TestID -> "HoverComment 1" ] } // Map[Sow[#, CurrentContext]&] From a1385b5fa7a7d2c351fb6101535b8f9adcd21ec4 Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Sun, 24 Jan 2021 23:22:56 -0800 Subject: [PATCH 30/32] :memo: Add documentation for the debugger. --- README.md | 43 ++++++++++++++++++++++++++++-- images/evaluate_code_action.png | Bin 0 -> 67217 bytes images/evaluate_debug_console.png | Bin 0 -> 44446 bytes images/variables.png | Bin 0 -> 94714 bytes 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 images/evaluate_code_action.png create mode 100644 images/evaluate_debug_console.png create mode 100644 images/variables.png diff --git a/README.md b/README.md index eea1de0..fea01af 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ - [Wolfram Language Server](#wolfram-language-server) - [Installation](#installation) - [Run the Server](#run-the-server) - - [Features](#features) + - [Language Server Features](#language-server-features) - [DocumentSymbol](#documentsymbol) - [Hover](#hover) - [Completion](#completion) @@ -21,6 +21,10 @@ - [Code Action](#code-action) - [Document Color / Color Presentation](#document-color--color-presentation) - [Notes](#notes) + - [Debug Adapter Features](#debug-adapter-features) + - [Evaluate](#evaluate) + - [Variables](#variables) + - [Notes](#notes-1) - [Contribute](#contribute) - [Design Principles](#design-principles) - [Todo list](#todo-list) @@ -67,6 +71,8 @@ later) from the Wolfram kernel / Mathematica. 3. Install the client. Currently, we provide the VS Code extension on [Visual Studio Marketplace: Wolfram Language Server](https://marketplace.visualstudio.com/items?itemName=lsp-wl.lsp-wl-client) +For other editors, please refer to the +[wiki](https://github.com/kenkangxgwe/lsp-wl/wiki). 4. You may also want to install [GitLink](https://github.com/WolframResearch/GitLink) packet in order to check @@ -107,7 +113,7 @@ Block[{$ScriptCommandLine = Prepend[args, initfile], Quit = Function[{}, Throw[N This is a good way to see the results from the unit tests. -## Features +## Language Server Features ### DocumentSymbol @@ -174,6 +180,7 @@ Code action is now able to, Wolfram Engine). ![documentation](images/codeActionSymbolDocumentation.png) +- Evaluate the selected code if debugger is running. See [Evaluate](#evaluate). ### Document Color / Color Presentation @@ -193,6 +200,38 @@ available for your editor. Here is a full list of [LSP features](https://microsoft.github.io/language-server-protocol/specification). +## Debug Adapter Features + +### Evaluate + +Code evaluation can be run from the code action of the selection or code lens +below each section title. The results are usually shown in the debug console on +the editor side. + +![evaluate-code-action](images/evaluate_code_action.png) + +Expressions can also be directly input from the debug console. + +![evaluate-debug-console](images/evaluate_debug_console.png) + +### Variables + +After evaluation, the symbol values can be retrieved from the editor. This +includes the own values of variables and the down/up/sub values of functions +defined. + +![variables](images/variables.png) + +The behavior of the variables mimics the workspace in MATLAB, so all the symbols +defined in the debug console as well as evaluated from the file will be +recorded. This also includes contexts other than ``Global` ``. The editor can +also watch on a specific expression after each evaluation if applicable. + +### Notes + +Here is a full list of [DAP +features](https://microsoft.github.io/debug-adapter-protocol/specification). + ## Contribute ### Design Principles diff --git a/images/evaluate_code_action.png b/images/evaluate_code_action.png new file mode 100644 index 0000000000000000000000000000000000000000..18865f4592e820bc5478b1167930026c5a074c76 GIT binary patch literal 67217 zcmdqIWl$YY&^HRfg9rBj!QCB#6EwKHySoN=3GVLh&cPvQa5*?R*umYo{GYmYD|tTM zukSlmTU$Fj)7$c!p4sW1i0|@}$RF`PLO?(uf0GhZf`EVvfPjE(gok~vnacY3_I`tO zR+1EfsF@->e1Cv87nT!-fT)i{d^Uu6e@1YS(sYJ^z^3@`g8ZiRA1ZjLWTQ?#c4&?0Lv zoPePGr|O6j_y5wG5a$X1-}lKat;6Q z59U5~SJp!G_p8KI$zkDt7aK^Z-g)1f^1gl4?0&ROUeJ$42Qfb-+#Ihy1np$Wzng}4 z@Op!&6z_KQqV9l%&sbLI+~=6DFLzqkD#WCJb8vKo3sGWsK#why95rK$@>+4UB|wj3 zzd88D^Y3O2$Vd>%t!$4apLB>^CDN^H*$xECG2`0l8vll!m@e)+hm-TWu~&%O1rwUV z%8)fVsGgHSn9 zIGu*xF{}#zs9Br^!iFOQqE6;>L1EDL$q~Fa-@~`wDdO0vQ}_eCFyV_nYN4NjKCrk9 zJAZN+;fefUp?s{9C07TqMmS-2db*E7$^TZ;QO7cji)3Z_*VkXNe^QrdP^EOpSzcG` z;kOs-(D>*zh@)SIP{w?;dGcKBnNJ^r`}F_r-GKZ*ka?*4{~#0gNcP3|4$DtKvzY($ zY;D-~Y9$8RPC+J<+~cQQoAIT|?@u>8LC6Z(BKB0KJ*tG?AJtKxcX_C6RR1^p)0|gH zkK)^3GGY7l^BMh_7GGT8vBtXG*Bl0&qBN!y)z=znaf`=nD*wP3A_M{cR+5P^uH7*_ z2UUDhRj6l7)-&>xb5euy7b7nk(}wWGx2wTY|8_cO2GEae>-CFbR>^<*(&0k1WXK+G zwjcu{50_qMvv^dw6N>lBczdTeWoQzMyB_mVBJOSAdV%bW5S4`>C z&q0?jaL9cAc4cLMnv_21=xAqFOHcA11{!-xTg1m;Q%%OBAF*B~NToA=bOhP_OxGuLlA>Z-xd$5z~7u>I5G7-)>&`a2eY@dFTbu5Idu>kYC<4oZ7+ zPG2pRxJ$aX!V|~H|79^4(!lx(Kb)?XAc|VubR+3ikxB2$x8s zl0Od=o*2=iaT$uyLL2vwpIyO+4d4sx#_5Mc9Vu(mgTHqB5p>|u*@L25{2(RJh3JjJ zrZ_h7&p0EqqJjStrlv3z6;sr4El5zkCd$VrC)+2xaO&#nAoRbR7o378 z(%GmPXheK{yDz2YmSNaekkFrR?odt6Q~!3|Q+Q;Yltw0GYKxzcAQC6h(gCsZ@?(U5 zkEoe;;lWX{sw#^2l~v~F;b+r5roUfjSBh214o+kYKrpXBb8)%6eEr;{I19f29Jh)dXcpId60H(kiA5Tb8inQe$s|O%&dN;oDe8eD2N;ktYx;nRK7e&+RL&|nPa)@vVr`BZTFMH{Y8Z5PT;ByJwNJ4AOysb6N#zpE?h6)A(5PcbM{t*-<}8veYEf?7P0=0+4kPuI2bETHscd=%438}jPnn3))*(=GKDx1;JP zWzuIlOo;WmdjL8BrfH$jq@u}t$D7kde|^p4PCtCTXCv&;rstTtolvTJwe;%RN>Mwz z7FKh7wt!!h%=a?$3mygbZC*Oq z+rb8)db`PKm^&f-I8=S2h?TkBcxGZ73xeo@hL(krAN1+)pfD0p$W z$+)$$&WbF*ei_!`LyPO?{zUhN6`JliR{NjVaQ&! g>}CgU*QUVZbl(YBY+TIY5M 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 ^EIHEqaIVh% zP~W22Z%d5<#DfH_Xk_nn4jpdQlXjkbc)tgtAm6I4-Q53d{ZM%60nj>X3F|Zjwn#Z3btSo) zmgVo~U4}tDnu>f4Z<1(c4C(a4i;BN<+yy}@DPC=V$l|S;h^$$c%#ey}g}2P67(B3# zS9`r6ZV&_-uus)C@#jtsMF=4g^n$x;ob4=|wS=x`Mz$l*UFifzp(oKOO(|>-`6%Gd z{M%rt<^d$EAKZn?Zn4;Luk1ok7~aj2rCud)v4V| z3^;uwlw9zgAjNjfA6}=Z9Y*DAc0D!au0kRC5|>cvE1#{!#pvMO#*Jlg*25#Q`)roa z?2ocogim~h(u3JCW7^r$^~~;jnH+ZJ08ImejSe;CE!iX{B1I3K5+-B5?XxWDENwM> zKQ}S+=L(MF^tHAE`e8-0tZbGY#xY`Wx%2f0flZh9va_B9u^7UnT&(OToQobrI zC_)Ud>8R8T9U@7l^-m4ONL&B%1s1C7-=#E1vXM870EL)qkdYn#NSWj_|vV>S<7FBB$baZ~&Y1K~CsooySKz`(5JmmBKFU1y?qVMYf1GCfdKC|Mr z(I14zb4w9E=yv1-RQQ>M`<0tik#mvSI1|@y^8jq|WZQ|~RfCZv$eKIEqU0?4S#094 z2j81_X2e;)8uQm%$kX>Hqv|I5VIrSiG=DeC_loB2{q?=Ifu}FuuI;V-ikVcz){xP* zbn{xVVGqwdK)-Ap0ySqD!?+Mh^YBT~8sztlsDO+~S4c1=>g;P~SsptToppLT3&71t zGR1Js7}cEse0M4P2?f)VE*;iYTsyu*t^fD~nnByyl52BOCk@~Vttv-CzLdOuJ6o?3 z`0Cq|-|3&5lXfxfu*ULxvBEq<7Pu%o-DG6&Y&QfGmwKcA0kGkqcE?l7N{B4mUnucI z-}v#XgT^Pn3mVa8uYsOnZ3O7M5&sGsdbBk_Zhqxng($U_ZB6CKI`z7rRPOpd=GyyT z(#73=!o`Z6FPcWQ=n>l~@wM*>RItmBuXTkoJdysd8tHSRG=u3CR`axp3&}(xzoyOX zs@}SwLUROOwtaGxRFKc$+?yYl=Q%6yaVaFV)178RvR;)5e$TJQ{1ieKW-=E7>#nzn z<2Se|ocRM?QtBMGY%vdUXrNCeT;)rb;a@J4GCQz}AM+{_V1Yd$I$4SrVG*vb%y6nl zg1Y%&?6o|64y)EZxMJg{fJ+B*9_qKb-`xES%UXD?EW6?ed~n=%^>$)Ct39lW2`4TwK3K&E!!CvV)@%X2R4m2PiRQ(uOuG(3E#q1 zxRgPpHr8_Pw46;RopC8*=Za^yu)Ud`+M&2{-#OXGhFxmNj zm4&M>66We#wQF#$D=F`BYub(G4R>hdSSBA;?l#tm*ujvVK@$C}pta<;s7VHP8u-0o zHEhchduD}}ZJ*OrgXFCO1ljRM*d88%vYA5V?}muxI@Mq;uG^UD{ke(jrBjzUU}jkv zZ2b*pe+>=y)Qp5|74O4s4{%BBG>Zo@Q;s>r(kSppb7{{g7Z_wg3T~SztoeOQd%b7e zbpIeT$*z(w?`B5FI`7c9M|dxpBiyS(3V;M%+{oNSKDo<{!fL$eR;pQM(aeOIg|aJ|AY=R>la7DJHLq z?P*$}U;m=<8(yhSVU?PLSJBGBkTdNE(D#yJ*2sdUS5ls zWc*etbDqw+q@?Q)bQkjEk-Uyc6ZkOI`VzWgju;OVldua_VNzew8aTdeAm1AS2zWz> zJR-(JB;1)msqGXAD$^ku3J>G0Wb@M}n!H7gUx$;k=1UfGSrL~R%n#A z?lp_Mm1nW-uvn7|&4`sq$;_ppllDkw%3=7T0<%REJCycwY2PDu7?}mFpW38w(4-Yt zTR@ZSa?*4h#{}|>lo`CPu0cNNHF{ezSUV<-3{8{g?)Be4_Yl(w$}DjaJ48kd6s6Vt zgtUq)zq|e~^|4?5p^oh8?!?@wuI~dn4C+~E40}fxntd=_8^=eT)VJi|p>f(tt`wF^ zL)bQZ5&qDM>T9}&x{IL2oUpQ8o8E1*8^-ifmwNkVVKiPT$oE4rdi2w*i9~BR$+_Q@ zrAt8BmbTL0_s7R)*A6V^*XPBqZ&d+n9}^Phco@QOYrpjYGx9}DbKD z0=r4i!QX!r3R`Wpj!mR#GAI_KLceVlwr_FMy)YCngC4vOf+B93Csp{r$9mlu^$wl$ zCPs4RT|Z>TU!JOA;^2v7yXBz6XEs9%yoZDC6u-qPey(y>;hM9xmQ&Vut6nin7ng(1 z=xh*C;j)OgF;S4T8NqF@w996l=TzRWqT%pS;KN1sJ1Mi=(%qb(v~~h!k}aTi_6x2v zq7#QcT8VqIeE;ivoe&jS?q96M!;kDyxeZ|Uz$P+S`Sl?EVdG2G*MtAa0TlkppfZS5H ztOX!bgA?KNH3I*wsdk6uX+N;1vtl`j{>70bhFg33YbEX193}R0kvV4IaFSY;x^zjE zWP~7u4c{kcUNmWc?iu}g0J2Rj=!CYDDj5LlTS_X71MvXM8P%pn!q8m<}PfXFP?oIRY;c(>bC%5Au~@TDaP%JX=$~n8o4T@7+HP>g(*R-8%M|>Kj?St`gW($R{616S_wzpHgmLRCO7hVw zAEJLb>^wV^B29C+iou-|O+PXF&(L3eVT+sMa+jg5vSCsT&JaZBLVLew9t(wb5W;x` z!+qwjh@k1Zf+Z@q+m%zB={~h)iXEI7_aft+FN|`D0)Nm~2CMVIENk4FLETD)t68^k zaJ#m>=P6Rr(yw0HU6me6&76v2Y5qUVy=7D!-TLMY0RjYv;0^&oaCbTc3l=oETN4QG z9^4@W_ux+B4vjY!++BmyKyYZ7&Y5}6Isf&(UuUhU5A<41)vnsoyLR1m-Pdo*ENkX; zWfcqK$sP^=LsEHCy4vxBIpBAPL2Qxpfo^dN`)XSzAM*uCTvW<2FYE494ym~sGDy@cxdK*Oh7(%k)cWJ`tz+oxDfhwS zw)=fZbP72x%=Pur`dSQAf%Cq>$k)vkXuusK8JJIG^-FKia#NBdqFWke(>#`BVwm2$ z%ZiM<^I$QIYNa#=T2!EWd-Sm^)rX}3xCkI&z)y^;Z35)lss=xEc zWv$31k7Y-XSc*kRk7$|cMHWql-59St#(92JvD7QJv3+osknt0q(c@Y3+ZMuPaLud( z&?=nNE;GOC;5E>)3A2C)n@iQnmVyldI5$T4CRPrbSusHX&_Hc5b)SWnBE(FI!D1N! zp4$FqRF)J$O-*&_5+OHC{wk=qXK19E+Jz235E5+Gy-tT$5mPuf`?IJ5GwlE+y|f~Y zJ)`7v4-#%AcCB7gT*3AA_k+`5F8Rr8A>-@b^KdQY8$!>8+Y1TSMe8&XZ$IlOGxB7g z`d2pMAA2)HNb^qlP@H3*@eHRsxj7f_M#jxwq+b{Pq%DiqIYXu z@%n_#yf&Nvs~ogOoI5V9v!aJD{J2(cD_fXXlF1RzUkC<~&;y<3$RXwH?7)X;$Ksb-2+`7zzm?njEiEj3)PmZ$ z`*Z0U=1G);QOZd7dvQ++Nt2mDFtds#Lo~Z|UTxiX(qq`H89W#`9hbD7Pq5UBpSETQ z3y0P!)B5YjKuk8qtjvX{P}W@hAt;O;xY1zcRaEZ2x(g5HAELu{oum;P{m)Q;|5}XF zd(MuAtKQ4fYL_@Wt-N2G)|I+iTh_*Wr3!+JKvlNEgRBE&g$wUQSaPXXC#DNm4P2C) zguUTQ-Gh;u}X0gO^5Y2YcpGp~1=Iu+#B-pHP1zCSe0KO1&kwREG?WC{G*tdwb)A z`9P*m)q+bGyMR*;73u>5ov(Z^4FJR@^Tp#^M|N{qVn|(@KR)Lvy9OqdtMNi+zwdrl z?+w5l!e802i4Snd+Qj?$T4Wl*|57a_zX*5P2oNdIH1F>4bDn}28AmH}bt09-Rli5L z0Jb=HsLT*K`#YE24$XeNlawHs8T~#zA=~}~=I^E&y-oi3WV0!i2DuM}YIDWLA<_^U zx3C;>c9jUHWjPccmYXBf{p|(z*n7u9y>}j_Zy5VaBpj2F@;GApT-I7n?l0hS3|lcM zWt0cshJ?B8(rJ-?{-mCMN*lIps+X^yFy)XnO=VDv2r1dUIpd=BGed|+UrCK#R1y37 zZ8g2dCShE!JWVt5to5(RKi1an#5h4KTY! z!~#GY0^tx*rD8*tGFs~g<(WCBbJB(%I$`-$wiWBZ*JQN-7@e+IaDe7MTin*++JKmm zQ2zLC+}1TVMbxB-DcRQSr?RqynuD$=BFv{(L3kULuN}!O_;WOpgDfDJs$ZGr=lqFX zl5G8}|0P7&H%ixj)f=wd>*5sEFnO3`ml zzE$aaH8r)$sWk~s*`tOH*B%oM<3I z2AI(#@ye>Rw@eVa<8`NK%*g_~u(IqP&a9T$9Y!roFV+gMD^Y?-cSB7I3IKDy%gpyb z9QS4;6Yi{<5u?rP)(t6q&dgVVd`TtquRHZ7EMs z7P05Le;dbNF!bI%{DU%phMh*f<@Z6mN!l0+Jmxu+TkirxF*n|ENCp2o2r`Kekk=8$ z0a9zoyPTFuHweE2h~j=N%bA(%4A+9FM4e5b%JL{&a!5J2OuKUjf}*;^>@gFH=TEJ* zGg#*|aD(W{fO=%jw9WLoxNU-sq7J-$ELr9XB+p||_n#yTc zgDmQxRDIkv@IhgQk8-=80l;3tEMM_Vw*G#y7OpU(bRWF2DElmh8hm48ox{!HVX7P* zqn2SoXtthiTA`azHID2a899LIN`hW~(KoSn$fX{VxE{%t9YY0Jng33kp%;lIUsD*W zPZT?ENErcte&_*$228pwvev73Jo4f;7g&XY0Py#_aOJKV&zutxzrSwijsMsbH5eX8 z)k{G)wAuG@Wu*jJ1sw1Y(DAk6yZ8gpheNdBVReRYJD5&zf;k|8x_~6DS9%dy;1Tp}#&!^>G0|7QK4 zuN!2r0&{9{JY_@QA`T*zsDj%I$iUvuMdZYS>o7zw@O7bRh@vW$#*8LeEKyT7Ptp%& z^^GOOD{s`4WC09^Xza;#3Oh}`xOsJYi2*e0tl?D$7o+5*{Kd!CZ6q>W2)NgeWvQcVOi_1h2luSUS3l0lRi@v zj%p{Y--WBVCm)(q3^w?fj(u0foA5u`q3G2uT;Qhtm$An6GS=<6_ZM9l5mnC0;_92Q zjaDnkyo z2cf#*t8g&EA&U8aNV7?FT83~w&^Yl)5MVkTeuy1VypzRQQS3`H&w5yu2 z^`~p^grKs8!F=9I=#NKWHn7Z7@#YI5#_80akl+YB_2c1|r^ z-`Ac%<`Yyp*-Lilfeus4rhxB2(C$_k>OlVVZpO&|4m;9n>ppahjn89P1TUc{!EvST zC6LluO*AyQ@ zT0f;PS)TH2P8XBw8#OFzYV4L9eV{rEx6jPABep@)oFgtc1e3p0-g(PV%KsWbYG(;% zD(Yex!--`do6-o<%@+nL$%j69I7XAALJfzbYZTbODWlFnZK5vk@*QGH%*DG{P9*)# z@2hqGEcvvb%7iSn;XZEodlE=osM2o`(9G5G&XVv+9c)?VaLqvL3>3asChFN^T<549 zZH(=U9cvb@ypr78w1fScu+Ch#pj`tnu+7U-Z>8{Fd7w-8ocvju?RQZKHxAM5;22*v z4S_CpH@7#@YI|)hG|88S*!jJ7H=;Wkw^sNFMN>?@dTXa!S+P8Uh(Zcf3xk- zMZd`j|6UFAw3A^SZ{BKLlhUN6tyL9qE-}4ei3_*Al#(h7CR;D|* zlCD>txtACnXTbRSeSYuCDmo7}**A2&;R9Wl6Pp!ndv(OA8FH>qPb54{_&nQ*p3*KK zaI@3NUKW%2lItBUst2lDOOG+55l~@wA#Z#A%)$8mY{QBFs9Ps$vEZ)gYlef_!Hk)} z7j}?J?rl~=Yk>#hGoWDtjekgQjbG_?m<9B5uHKS=Ff4is*J7EpKv@9t#E6Pc3h2Y+ zIb@cSy$V-kM4TO^Y#EO6qjTrq?h9bG(aR1zzw(7w$FhvB)js21fv2sV%VlbGCd zAx5b*d&a0~c7)~SAO0c--YV;0=3qqU1GpSAuixcC$O3zJ<&5|+-w0fA$*W@tguJk1 z4X*3##C-uFy`j;5-pvTOV3V)Wo30|@Ssu0L_Mysp;eSv+H>NAqq|!2vJL5{pOYlT& zMfSE&?&13FFYz?%5`#<_)c!5}h5AG8o;t=RzV%GAM8#1E4_*n#nX328cExp$d%lJd z7)N3ulffa*=2qN|KIi>7Gi+nxV&t-M*>h^5YuO&T2bzN}vc2K9l7)7^cd2LcGXy#aeYOM++iVDXAPJ;pV46Hzlf_zl)>Kb(!s!V{mEEcnc)8V3Y+Cb}WO949 z3)k^>O&m6O7?El6T6?I(x_ZyV-Z6-qq&`;H!y}}9CcEVH=Z^+lXvv=r9}jAKMgrFGmX0!|HQ**r-|Sli`M1n&ZlFL#>6= z0cO4hhxdR5q&!HlNg($;>o(>DWhAmsowlnv)+=_|T@a>Q_0@vb@ z1Q>Ee-1wlXf;xiM2;k9}&tu2W=*5+4x%1IDK#%a&>o|6!1-6OlloA%tkfk- z-yAS%ZmycE>vMqRi({fZ()rI>f%#Yu(Po!BGUE?!Rxm%TSC?1b0OZwsY}1tXd_Ao{ zkZy%lW(6FYXAC8Wk}a{FC%7~?_4R{MeLb|l^AgcZl=fP}!r(^bh-voi-7Xe^gxmoX zI@=C%2S|;ktnD?7PpJFlfF|AN?xvCzrPoY)lN-|2p}A+xCd>w?pJa^=nh)736RJa~ zf2P!nI{qo{T35aoty~F%-}(J+xN3Q3ESy3g99Frnyfl}LvXjTChlMYu)_WDC#~zHB zdH8?M;rPzP?WqQXmPJnY2Cf$T<(Wk;2SS{6CllQcMfF#eSAion&){*>6mF&mn2t|Y ziJdg?5^POXrT&}Mo=%i^*8ADvq`{g%Jqqrq$N4}eSzUiW;l`>yuZNLU#pOI+Ieq9+ z$EgX1l{q?$*kB#)E7c4)a!59mihgHTN3|0vw|#(nr>TS5GEFCok;p7F%2>z_tBPVf zl4l6p0`<5$yPcxGka-<)@b^xP-`)G zQsYkh>BJbsPw5>eF#Fw-1DlZabp7!4mWSpZ*2tS_zhXJy@hyfe7%_qfscTNd5dp;U zj0~wKPGS#0i;4`6i-Kp0&7yzRyR-M{K3p#UiW3q9Y?D8e)UA6BG zUvbZhWKZQDzF||Z?gVkmDbezC*i)#Cu4YH{=VVINT)!CXN9tC6I=gZSOn5Sz%k@yk zH`M>q8UcNZi#0VX^Fw?y#XSh!#=s$VJbWvAQC#}*^c%9V$)91#h0I)-+0=!Hm;AB} zYuv5M)!Qc)QhU%0yE8_?Z8~|AH^Xey&aOGq=lQi6 ze>Hv}oa$8WQ_F~WdXcd0b0q+Qi+tiM5|`;ceT!(-=Q;4zbyG{1X^`q^kjUqFv7Hz| za=qew539@wX7-m~@3bc>cgl-m(Tm0i8JYZ%Gsux8={*#cqvKT9!6XlzcD-1szwSbh zT{_;qc3*OjCh*!H2PFyC))nRqA^rg!mX0@8$tm@|A|Sma*DNFzw~ylPvN?lrNe1C} zXvQk6Fu1nZu4_vhwstBQ5w8b?s|;&y7e!0vrv>r`5em14@Og=A|M8h;Flf5v)3@Wg zv#~Swyd@KZ62G*W%QfhTe#%Z2#lkRmGOz)^`&GiC8%)j3n+PU%!4YgUL;+$t;Qf<;aCw!IcAM!zxj`KJ(bCZk=5yu|5 zU~{UQDRfK7{ba_t?{tV|-qC`{kbd8EFK&oPz$i)wBMpuI&YiokLjhA649Hi? z0DnUfl6yuUh+p5-)o}E{(!76gr#dRMyZN`Ab1zc87J@$Cd}2&h&Mzgl_A2ZSKPvX4 z%Bs7s$M<f7^r(+qfMD)Tyv5|8(#YIeyDjj*PVGwzhODw+!# z(<;lx9kw8>o`@Q4;9H9$uFqOCa|Q;YQ1pLN%S|MeC-Ar6qY`c62=1CUwxTO{WcgX`bDcvhpOtIBepIH7H;ni zM?Kppz6|rk%$m$%Je>dDEB(k)65Lt5*GiTM%x~3no6gQr9PoU3 z(QWEkTUYJ7OEv1*P@zPa2D6DZ-5h@X2AE7PUaYOf(Eo95BG4)ky8<;rJwj){6i}{wgeanuKl_OPj3JU$^c@_dYv&wf& zBg02jm3(PMsOC4uHH~5lUr-4tJg&7IPC4^1O z*3eVMoqcj;vy8dCHrH$>n!V{nNwAKP)Vi>{fn%#9kvm>T=c5QeD)q@T_0;gNO8HC2 zad`4ME9H<}RP{TH2A8CX=)W%&Mb_+#V|fZxX|srD`I$dq-{+cyj|oB6CD#aWXkE7v z6NwV@HPvB4EC%XYMburT?$jUdZJ-Brv`WH{{I$faU5@Hdb@dpY#jx+NesaP489I$7 zIuG^de*J>Z2Z9S_KA&;;8@ShYzT~nwzWE2choOhy_{RAuzCm%_U~47U+(up`Pos`# z7w3R`=h<_C?^l=0g03`3K;8i38vhjPmB^V8XUF^I3u7dq5(6<}NMEaXDt$*I*%An( zyL$!6@k3iQvi(fOXCW|b|vW9iOkTqac2MbWuxgNI{JGO8GKlRW%8}Ekz$lJud<}bDTir4y4!Q|QK9md#H z_Nw1nFb=q|{s2i{WLrd#pDF6Sa3{|h2;9|k&;TSi7u1q0R*t@DB(VodDw7pTTOHU+ z9N9ilRCt{7Rjg(qo>cbKPMOIMh3Ysi@9k1d3XEQ(39jE`)eT)|8N?|0v-X6}ynZb| zPv_-o@s@QVLJ2(HgOgqp_)9n|nDy7Z%4bKl1Tjz7#N+4>`|8q}SRDhdsDJI~TYD;T z@)$!xb!q@w)+s|c*xjQ6t^1#T7pI*JyzG_B zo!tG~FRfX3>VP^$^_0qPYQ#I&sbG%+{LxB@;q(seg`tkN62@`L;ItrD0ZAwSJIX)e zDN&7}k+0sO(aYj;v4MgsOnTB>P)IxR8QANFNy|&DPWecq*iaiSAsiL+SbD7#v7GMyWKL+=XchUkX`271{>a(wVvO}H-r_WO(qB#!*$8P*X z4Ut@x)Rdonus-?H5|{E7iH9}RC3PA7&*7EPu(EbniKoZj zkla0&!APuc6fx9jty*v0SvBt54?6}24}GR0UQBhvjX41$vg6X3nfE&3 zI|l02Lp!iy2&eC(`Jhlad-|y0!$5uwYTcOfJ^!~U%N9?1w~jxmJf(HE+L}+#U7cF43ZP5SLa44P zYeJ{8-S|yoRuKAnt z?ff=Wlc~&)qhA@#>hzr5yB{HZ-9`=GH`mDi4Cm@WE{N}Go9YRWzFi6aD6}jPrgihd z4SnmJHG-6HI-7iEG5FQQvX*UMczLZp`k4Nn-M<}aK*v>Imo6wG{Ce{9%YLq$q;)>s zJVh|q3a7b*9cklRX*lPCc4hoKvs+c+$K|)g)Ufvff+P37O%9Gpe{-Ojomp!2&$Mn~ ze2;(frF`cSfza}^g(;nnD!<3&#HxA#Ig`l#Y$xqSe;ABB++n?D2f6w8sd312Fdi+Bs$0I0i~tYVAC4S0>T>M@~#E+}(EtKDE>s8HaddOQwNm zOWX3H87j$PCpOd7mfpU_u5A>eLN7TC$l?0&G~d@z#6;TW6-Q(9{f!-R?j{$hdXut- zcRCAgwlDqoxIRVGB54bAM^39Q>?jSoP~18r#=diwiV-B}2j;B~hdWl#jHIV$jc%SZJx`yJzp3!+{r!~XKT8pEVJVQO}m<;z(#l$%en zvD`~cb)m}# zRln$!BrO=957n3|xCqMG&YiEEMINQXhzc{SSh=r#=^t_ok&L|{AWGtVW_xs23--(0 zJbVP49pn|C>qK633DLPcvWkj(lijnpk~mVF_}?JH)qNsum{2Z^kz{2_YEKlP*`I{E zvmWugv`_J8J8d2^Lx}{#Q;i$bP2zn^ePc&tu!~~Az@us_p;6W&ZIxfa)i#^E+}z5w z--si0!^1$V_|o442z|jjCOKh_FrP#VYDQ4Pp4Yu6m@CwyasJT?B06zryk#G#M>7EF z{cdiRrTsYc$4cj=gT1~ukjSpphx5agkujv&Z7fK8Qa?XS;rHh3(Zf;LInNLea(qCm z0DlgmDAgaMRy?G1Kl)&qhl5VDG(<%yB2{giX2EW`bf4`PoQtF1T+XL^6E{EwWv^wm zg}wsZm4jtbc!jRe8a~7*5yD;V_~f;c;@;RWd@Lj~9Xf)idJeQ^wR2tC_trdCG1}L- zv(%`YolW(^u)5b{sgfxEP}q1oo{Zv;Qp_2gwy;j7D1X-UAG0iBwTf+03LdyOn-Hrmq?9URQjx?*YW)H`#80^ZVxqBZx@cmq4(q z^uqHb!3oZjJbR9q1UiCz?8(4G7Re)_n^+SCz??+)$U`O$a!kC|8~1DwXWw~C0)2E2 zy--9w;KIN;hxFhU8^BkTe<7*CXgsP{IE3CG^AN36^<4? zB~ZoAwlr0{0ot%M_2)^S*|1NL+PH@C-CC}CJ>J5OQvGA#4z*+_K9^trz^yJskntEh zr^)}-q)%uu+;B(s1pG%^dN`b;q^1((qxyJP_GT&<0KqK!U4&Uxcpr=alQ3zkfE6QJ zM>dPiuN_RQz*DzSw#<9ufJmj28tmJ0NC-rMX8{O5w1~*vaZ#ro%5sc!4`{(x_;jQb z@WpflZF5=+6Kck;)7}!85d~Bs7NLK_=;rV(A&T6mw?m|!eB<_DQa-c-ev;)*&0&6! zjh(kT+@wxY>HZ3*{gvgZB)-l^l28i|MREL!Y!NSKvz@#QT?dAZbYj)(J| z+Tl+}cmZ$@%-m02gN}1(z&BEiO-kTv2^{@b91U=V9O7$OMPli;E&QSI{vJjn zHR??gi^bmgVZ-ANU+Po%#QGc2giC_aDqzP^J^p(H9ehTHpsP*!BvY#{?gUi=W0>(Vn*Sx5OMDP5<+5}sInkpx z?v}4q%dSDyQeYMMBtm$^H`Q%3=6jefAS0IYxedkN#RKc3@J%~imbYl(QE8d51~DPs zi31fshy&tg2*8W4JUB<_$!>nV7CKBfrkS*@D3R-9_G@;U)NOcstH|{!3@6uE_NFb@ zQsKCWO)^I=6al27rx`q~*2OT0GnQ@}F@+@fbooN#&qKsg9>inR&w=cNpFi%ni%dq_ zC*Gaz6vw7NmaM)9dQn(P&Z~7coZhKb_7jI@M-DeIs9OL+waoG7RPQ5X%!t_cB~4-B zh)AU5j*G|hF5`N7kD~2=w%$k$H$_KP+Z-r9)@2}nYkO>DvSyoSXg0gF@`$=f>(Flc zLu)>$>_X$38ro{Dp*8>WY)6&i(TslW*(yqj$_Ng28T@7IHAD)>t85${;b=7_@0=$v zD77*W@o-$uBL^tqrbXEonS3sTv$3-51}!$5w_gEve6xf5MUqg5x>iwc#^WSkeA?&- zMzty3jK63Si$}Mm1i~>dqvRLJUMr5`?dfFQg`E`r>)_~2dp8ywl|A%;>Cygh3i7}r zuRBq_+5gsfv)G}D47Mv zko?>7n6|vVyL+XFdaBALl@`AgoB#r5k67YI+e+XhpBZWCKEJRpo2^wv^3%j7v*(}o zts}!rM4YHmQ;wMEt&A~OM6*ml1u1h{N%`Ea0!~`~(T86xpWK-LI*Z*2-VWS$_e&4W zRDqJ~0u*j+-#__-dvxZYaFWnqUJs|!nYI4UOY7`-AH2EUgW(xB=@68XGU5Euw7=jF zOd~Ktb89Pnh4bIrUMvrA>}PYy`!pV9kw;Y36ir>V;H7jji`f5$=!o!x*a>eH3tCE(h$whq2Cpv171usWel6aqw7-~WD@9MSEg>}6L7sDY}YAO^m zGT!hb-GzMxu=iMaW=sdV*JUJ(4#GAC$e!5YXH=Dx-cpF??a0G4=OUR3NBih z!xzWY!hZGHGW|CtJ~}|lAdR%E5-5D@P4xM5!-XjP;CP1SWGEmyE{^AMCWd!^Sah_#J^C!S_&Xgmf?1Uh7(@QzcyC`r-gW7 zRs9P5$0YOOE!-fmZ4uT}WX|8gmTb{HHUWF{&k#8DB0)ISU?-s|fwN83- zC#UPG#6P2YCW#YaB|?A86gST2#Z>4s|26NRSbn{-a z{|M)wvmCyHUllL=J~2UF;v&phuk=QzAE%|_0dj^6KTCX}RTYnvYRA%s1?vBB5m~_6LtH@tg1KIWL)T9 z&t+u$7g0Ef9}VK$k^2uG^Y9(K8kl(xK)xrvoQT9;`83^r2ZF}U%K4W%LVw$c$gC^{ zZ`CJ1?BDz`VRZO8y1Z2YCFkp{my*X%i>8mmSXfv9(i$gKJ?K?cck9hVRY^_3QXK{w$Uj`P%LID=C&;&qeINdOGU2a8L@x zzdIU*3t=VqaRSs-^stwI{M52Ky5g}5(}rJbEpOdCar@anRJ}a*V8o5s9r7p=qe6Y> z7tE0ViTT5eM}ZHSfX6M~RX~p53+!hbl+mug@J^fU|Eh?|-&m9fM!6(heKJ5w!VW~3 zr@bn6KcnaQw!Cw!v&zo${QU{0fki1w4fU9XNjIrgK;P_^dMnZx;DKz`~p>;L)z9w>Ro&s$0!t+uxI& z)Of9OOkGK5x%ir(dW(23$N^4{3u-zsAg8nl$|MmGVM(F=j z-}L++Hk>)ZSoZ8m?S=B)b6p82L9EH@ugXr~`Z^m`WbV)v{BfSxb?PdF z^7!P~gb7w(moU6le3|nI=H(~>{O4DW!~;hTjyckWU&cX;(xx;sQ?dPVD`8lFzH3xqq~$bJ&u3dllf}GE{U0U{8D6d83L+?uYmSNpfT7c!h@Z z_x?T_4i1NuG-NBX+^(w`UF+TWe=v@!dRz#KF4zP*(PS@t@>V|fW7q6^2;;}rp z*H60v&rnx=0T}k~j%l-L)%V_wpvcV6$^)8YUV{bLh89t!uQBTu*VmU zcS-ZxV{O!-aUk%cplka`RWmk^eTVYcJz;>Cg!!lB*Tnn_p&=+OPXD<(z;9)-lx^P_ z_3~eO@8lf6XjPhF|9Nz1U30gGwQ$=jyi2-lQf`oA-G;Sx^`Pm~axd1Ri1nrIn_U&g zMR>Sjv+=Q1Uygv{7#dOE-Fugj$IY_&%DF<$wr~8l!}WSQS9GmjM8hM>X3uL`3$=G5 zLI__Kzi$75=m&O7%F^DAcVw->n%PWbetdROhy9R^_x0tce$+ud0p8G3ut z{x{H-s0#sJrYi>1RGi5#Ae2Vrr~p1y+Q&U1y`-;trt!?KP%zJrrErZGzx6)em+-lL zI=XQC4@@~P7EWS>A5Wgww*L#@9~Bwts_bw zUm#g?F$$#Aud#!y)6Y93UA9wmT;D+L*23%*?m`q46yI}lf<8mr1KZob?&?hcUU`1w z)!#R?ymzzCZ?>93{dOFImUgq*;n5=eU=;^GaGiet+;45_?4$y|V{y3LH@aO>=N(p`-I+pdgK^@~#=s!04Lf+{si>FK;wl6m=_J10 z$hY_RBan;*JHp*!$6E({zuVIhH_zx9R_zsI5LvGe&{Gs;)|&$yZq9z(;BZ-5=`4PsxM#rw z*gg*Lc-0I%mEj*bjV>tQQQ*G>0*UWP$}20SJ6(>S8n=lb8(~1v#|7t$+9p^1v%!+J z+vUoe?s1NZofiRm-h+}3(V+5iWc}cYeO*N|=H~+Nmw`Y>+!X6QR%y9BxH+@Xv1a=Brp#uCKQ!7u#8K$OP1$k0?TDYjr^0m6es<-PdqL@%ucq zGJXGE%2e(XqB}}Q)*L~hr$Sa8m2h)-YCUB`mqF= zr*>b1aTcw6q~M=9&r+Lh)rE#$^);wJhms1cvH|9(xH`5s4i4B-Mn;skC%TcG7V+Vg zvUPy^DjN?@OkV7%H6_u^fwb4RTG~!Mk+qatbUcf`oUF!ibbO6`yy;t?wY6V8LUGKD z{G+Rw<2Ou?rE|oSM*p;i#bdb{tAis4`OseP-)1w`gzdCpc${-H>X~U!^v^tk=ZXYh zP#_n-zohnNuxsAM9?kaU_`SlSR0kc%YSNEmfm}<%Q(3*G3|2OSP`}5=Gp0y?%h!rN z*a($3m+3Povwv0J%%j2RB zKn1?TJi>HmE~{AXgWV!{6?jKd6&aMv{6jh!Ogyn85})v@59j@HiwykRF658M_~19d zS%c4?nK{OxVB7A_4lu32USd0TUu|fp^alI}qhdIDe&JNN!>TBO3~q1t-KmvFZjt(F zhD2P5Zj)EQ)J%cVVB7r^Q9jb&hf8o6$oro54zVzHr znisZobvnq`m2y6oc(*Y}?%D1B;U_NP~RM+pjJ)`ImP`hEa zy}bAJ_9U@>bPcfqcbY>#rY5uI*7?hHR9T+aJ#ikpF81`S;I^A9g^1pE8R@ZRmI86^B&@l8Ii_)3MlU zEpO!dy0H8@5uc!ZJZfsJhYIlbQu5j#OB)ql2?huFx}#BD16@xYiA*0AkY7k?^#I4` z=ubQ%DJra(jf?2K9MKUpkiLx*=w zDo^6|ryCw2PKFmWruY)6J%RcyHdc7r1OfBY#R@wXpCfjuJ=PrVgz%8XTsZM`in$Yk z8UAYv-vT7@)_8Q;ftE!AuZ%-NuM>ml!C&uRQu|jcVqcVuJOH<-1!jqR%go^GnNouL^_b|>%4d-9MBA)_{2>I3qRt0t_SG2B4KLXR~;KS;#XjDSjLky<$WFhTm&*gfX`jK8=q{#Sxw_NSsRNN7>A~6UlaXi9_QCjF>Q;A*2VMj z^WQCBxCT=@o`GN=g{#TE3tZ{^w9MZ{SrF+=EefT*9UNC(E8VRhc@tPi_jZnq4tLqU z3)eHP-!I$qqS@~mubT~zl~_Un&xhdQxsAnefc=Xvl~88zPC>`TI>CeCcy>pDg~Jbk zR&qUM!;y5`c=)NefxdqAUfhXxgHtJd+}+?m#G|oBvj1{j9+J~#bZ-`H+fx3LRm$1{ zj{Meub{x3@S}4*J@Q=gM>R^YK=bZ;Gth){7Op@ZMa$P(z*6(K0gxf{;19AI0b59>m z7;3-DMw4KmegS+Rrx$733c4{&_&Y*P#UlRnuZe+a1)Ia#F}K_$3$`1YOAr2n!4Qhs#|muF^(N1n3+@t!D)9 z#H5rl0bg%l7*+Ub8EV$VMS@+A_i69+w-0vN1eWj~f&{Tka(99tySD7tQ8EPZW!V3D zh=4yk!b2%LYdG`zLYM*U!|es9ig->_&MGc4DOilo=zV7_AQ^vmi|mr1@UPVo303_+ zw8S|@#I8VEyN-;Xhx|{If^Y*s1oE>Fid=ySr>aoecUHk#bWtv@dK-#YhO2 zzwE^t?a*XthpBelYqMc!n~y|ov}iNN_p=7Z1N^|6o{hBUq|ylLkA~jiU5#ZG6&lP9 zakmtESY)|SW*RO>;yDnkYv-PD8Jd8TvZ-O&Dhg#k@s9s=~qYivBB}jPVuds@P(a_hzc@=w2DMD)0fD> zGeRA6>(D|E1Dn!-H+bLA{@^5z80dWY5+VMbF|a1jd$AGU;HK~Qy(t$b-kemzjH2%s49#~93Y^R)QQfp!K#_6VH!vzlp3f-JLZ$-wZkR7KG}SC z0!?NdAR*Q{`Jied-uT!q!Ja`Mix~fzt+5=Jh$tW&ZMx$!wEikdLQXuNH z_-SUFV);8=m4k0Z(x+V9Yw3P+!Iv)%)1%&w&+Q=|)|+aqekLd~j3fh-ggTi9A!t<$ z(PUvqz?FzLsPF4%k1c6dFFT>4b-@u_pCs`%H15U_51dJ{8kzwn!lO23I(1bngs6EL zsewTY8(h-30iBqESFfCs)b{WKgj`|sb92MniWoJ%sKW-&ukviZ*fwnyh0jOKW8``3 zJNE(Y-hJc?PXtdGX7LIm|kus&#hU z@nYwk5$Wn(jaa`M;mXSD6(qjq`V^8U;_dkKdaoIeo}+Kwt^*{!{ag1BPg>rSSWN|! zGR1rjlOo=DDHfvC{E=z9^}oXowMy_bStXjs_Qb6;f{$F7i}MphL7}Y1S{%3Vggw+U z5GbMUIk~gG>-cg^Pk{_YzRyN#z9lfDjmhiglqu!&|KjaEpqgy9{!tVJQ9%(Aq$&zX z5u`~68z9mYq!XgjK|o6AAyz|g#yRpOuzxs7sdT)ch$Bf{5tzH zes(9+HnSZwW@%*o9#?dq4thk5vfpTWldv$2Ok^L;;-9|J0%0@z8h`nCwbNy@I^@al zl$CUgG`k+ZGpedVo9jOBP>Qv*OQx4 zwm2f|<~^2@Ny&`7B1UqFzT?Jp?Dh9ea@ZF*=gZ1lYpQ)y#2zE3i)HfqNh@E+KhNcb zxaT~?K2_(A`bxI*94q5%at!BGvD!zp6jGAH^I-a` zXEVF`+&uWOdSgWDKVU_xx8%eKi<8si>DIkPnJR{$r07Uh$WT1zdu# zjfv*KQz3$=8!9&Zq6RkEle>+E907gTYD6trQqLWWE*%i?7%o}bGz~X$RCax7pX@#` zndtq~Po-}u?%-S0HTLr*du>Ym*Cb?;M+tP9^sHQ6N#7^iy6B%1J?GIsWH2A#3u_`bFH_r^O{=U0_j2K6S6l&s7)FI-erV326oyGGq= zDKp*Lf0(Kmmr?h_ImO#uQV*PZuZEVLwPfpnO*Y8shFNy3kjkybl7UMjF(p2r)7h1yE~sF55lmp za1$!}Hm`*V-TP*a>E~upDCIXa(#E9Ox!l@wjgvOCs9tvsE0_zY`YoH}&HorfvcMSX za!>7LQV>Qgyxiu?ie#AhME*iOX;O99RZ#D``qv^ox}p1(Mbk4g@9W)Z7XZXiYViIm zZ5t40l$uBjIpQPnDuY_nUZYx2)kU?VZAh+t_pPBWH_L{td3>||`R_P=)kFQT7UBz0 zmPVtuj?YynapmlA80xF~02U1GUrj_#PJY`GYE-*yDjrs1sIF1-(CY-EXuND^XAg=u zjwSRBZc~CK=CKuVBO!Hlf%B!}pRlBhYPxGk{*%I?;ZM}EMM*iKuWI%tNe>iKv)p_E zC@Or5KI+9|?5OTz44SRuBF6>(gp>hb`K=;xR}ZuB$rlkNA0j^V+#oUaam=IRkga$P z_o8!58uUzBpD=PJkZpT|qx7^iwDMGK9%XEByBRMUEtcl<7v>B6ZoMT5q+K!W^1X2# z)Vgf5B(UVhP3yfL^5NbPSEJDbigd|`qOTuN=|4lLpJ}W3A6yy|HD=xmoi19+c|zwC zl_Nv+mb;$CFrlKnPjLpg#UI3-n-lZjoaE+= z3Mpjc;5e>{A)V-_V`;o$QhL}VU-?@%NXfcwpJ2D1{}wh6eMC27z%0};x{_ZvA?Dc7 z8<8v0V!%WI)?yhi5SZi*6Z3Q91J{U=x(t(+JtqEY`q;*Wg*TS7MNkV&aO?o*Mrd<)p; z((|Fq#Q|dCwXVuPKwf1=%j3lhiA6d#qW4WQn$=HujN;VJv4^LHe(Fu%4AdiOaX*+4 zNa%7e_IXuMAYRrr@+l*;?`j?NwZ5#O9-Zw~e(U*C;j66TFCJgkpnIH}2AON;WW7@f zjGDIZwee0OH+__MLb-KV&EC<@hP2HI8V=mY&YD<3XeTDTgvJ2JVb69z&ch5)2Y}v5 zpv~g17AKW09?!aQMao6<0XbS>)-4nd0zoSiw0l9z!ZowqTz!1`g$yRt? z6C6?9xavN_*>F^Ospg?w9fwfBuW34+B=0a~+{F-P!S+e7(_d5eCE2Y+61rI6>M!(5 zK+PnTbpH_%mD8aO15bV)+mJhbdh4?S=qzJ?BvnUP7?$z1PDeasC$wqAj>`Lwg#iI} zU$!s;80Ck|A5a14SDF$!sqQN0=>?~QFC|>o;2~FCW8@0bc}9{Lf|-vn+>!(;2x7l8HUCnE(yxP&55|R!8nKZQ;J3!k+6-&R7s6 ztg)c=5qlHhX6U`RXV^5yAF6<-3dL#au)aP55Y+Guc5t5Z^UIYz{7#!>Uy7zogVMftPCEczb!o zV0zMbcaMHHZUuKlCGp2wA2!Es{L1WkHk&2fo{4f@JS(`KP+NZS*uuNVb2oQT<2t<3 zE0W)MuRcHAn1sEE?T8nAvsE=kz3f_V8`(zD5QBRc;wMrKJ%D*c0nN3WFt0r3H9ZT+AO`P?-f^!cA}IdR3v_VF+#g3lFA zIh(5$uQDvf4`~mdmqb*eT9>!m}9|LQLUhoL-iW5&;_{r){DKpy|&j&JI-pK zrDH1YPv56=9DEwST2j9HI2bdDEwuP?Hx4 zGIRIyl`oc#-arRYf`6 zuuhMg_Bp(}qvNiIe}1{zg%uLMmBTlHEJ6-IbAMqYj3j20Ozvdt=L5wa4Ma%jGF5%b!2O5dEgWCh*+4lAJmu(bB7j3U^~No}}3 z3WW=$7#5n8;NFF3c0}B2+WZK83Pb>8Liogo?N z(8y0REJ2q0@GTYn4xn18k@_Va-0UUz!k%+|^xOEq3v(&mg))1)OoVYy_J-0!6h_LT zzk^qlXXz_WJ_&8S9cyT+@9Rfs5tkK|>Kwd{JZT*IG??S4|2L@`LI5^Z@B!w>tFhK5C=ImLJm`M*NBzQtb zBc#=hlqy|fq?6UB?-xpg!LTzt-u0;!*QFMl_VR$>QvW(dOzyTryW2P{zIKU?pjDS} z^4v#T1-D-hC?l;REUTl3TXNqD2X8c-y#MhF{AA=uhyd!s#ks0{0=oJ-7!Dmx3nhNg zT}$Av&ZCDl=%TI)BXn`o+JiJ)(yUU}pB+#+mseX=&F*#sCwu%16XgbQXVfn>d-(%t0o z0jfFarvA&MP#U6Qde@`hs;+j{)=%f@M$(Cm{mBXCdCH_O*GF5{6~fA6ivltT$IYP@lZm4!Cor}UA0|3l8H?VRDq#AfXi8x5`W z7mqOnoLR+b`@Ja#0NB$1*hhS*8W%pMvO-8?Bfpx$Ku z(uJ7V-L}z?seWgkwA6*hwk>*mXE+~wz+5|aaDl3bqet2f@KV^ClG z!eL=(Bgd(fbI-J3KZcE>!brc57jqz_|_*-KS@#*x|uk#~$l&;y64-P)i~-0MB&IGNo#KF~2>;p%(2^8MvX zi2A-Gw?a%fUjsNSLai`?4=XjLW`YPj0g$>pF6-J(8RZacZl6<5IJHk*mS_JCOcY`F6U* zn!R^;Ve07NgzvL!mY*#N`JJG>13YUeYToJEJ5kVGtjQG$c4&|I#Z~8_Q*!lw{!t!X z;OGe%mdE3vFM5@TR3#urJx@>51&Pq{DtgY|`!~R4BRX=TT=qm-R9^D5+v9$@z3CKm zE3y@n?xxy=)lx?Zt%A-$W-eIvZobn2pog$?qr*fZiilCC+4IUP8h7{yj^O_n9udKJ zUhqtCRcWwx?C{){OVZ6Vw3Z>AE(Imxc~9h=*CW2Op3gD?OgKoYY6)dQJoNoQcJl*; zm}?1tKkFao;>OI|J{(vWuJC(-g<;sc*j$ho8Ex7!#W64{SXPWj-Hu9dt();9~k7E{q<|$D%Cp0@^6F|sB(Pa9LLgbIj}YNimc6< zuV~1^kIy{vsYuZjg9X;N(d`nogNxR?(SkuqI*I|tH$sm9&JEn#o5V5Fkg!R|(pO_W z{e{Kjmrw5}I?J1+^V<GXFcxM z2`j73Yb4@mhVpN+)}K<@4G>hq$Na?2VmhAc)UE}-yMBe#e@|^T<%mZ!&s*$ifJBp} zm#jD;cbg3af6K#HsL3DuD&?Bg@U~iHS4hq@%AIz!8O7e8{2P%lqZL{68UW;J)PcdPNnr_trHQ&4X&g~$S4Byp# zRx5!F_>2TqbI7*O4f;PcC|12{+Bpuy*Pb3(wX^=N+$ACKSM|hrXe-f0MiR*H`BPBm zlz@k;4E`5;;;|+$FZ|uo-y>P(-)|E5@3GwX-yDYjDF6LU-%*v1+)Jb3-6M?J^U$xa zu7_TKAUMlp=8)`|>@A0qEEuvJ_p0#lIlvjORmBa(yRfZc<0)faL!}5?MB#8LSEl(` z+5?doIX~8^HWw|X;p9^8dK;xz?-l>)(>cPhmZI5i z+V7u2m?Z&r1|##QjZ@!fG+y>fn06%;rY*&>0FAqZX^<{FLQ&WLh{8*ZuJNSEVt}b( zkhLK2op}@^bid@%U)L*E>fH-YS7+~)&vvNli452R*D5(+bOE1o0;`p)m)120oM--X zV-BNJ)ic&rwtQgr^C>xJxwvK^zU)s0sdP_hu0D`&@P!1V!HIyI^Ziv62lNDflh!-C)o3ePHa! zgQ}mKM>P&#no|4$!b7d)@N+ya_^rRh_9`}y_1H~4_4H#!jwwe04CoyI8ryn*ONF0xX^dM}vzU;$eljp^Abke8ixyfJ zrlEO>`gPK6?Wp<<2#;oj?kqZ_2$0G%5xdNP4<`9hMHZU5Cks5smBzZKO=v-}WJT&+ zs1In@F0Zm~Kk)<&&6~ui!pc~!z%_NwO~v)DE}g^x`(DDOz4O%L|7ygSt4sH3!VTkm zXkJjAk8mv1DCkYIhj6qsF_%^Y_5%W%1&t zY02xfKe^gRsy9h@p`m#docauDKTft{`MV11mETo>Jg*ejB36TUc>r;C`OTwlY5w$? z*4{h)uXenO45Q(=^S!{?KFofvz$Y?(*E)4Yg^h+L)sC~rO-bSTw8k5-`adTJ11Dz& zoh+<-^HiTCCLTmX^F>+8ijR}N;o~p%zog5Nkn|1$XG95BE18qhWTbmhy z`80q3l9YT~`lfdwG4Y8pZ%iG}dxJWp&dLfWG03n-E$FbCTiMI>)G2KnJdr;SK^e2^ zFK-PcO1->Y*VMHA`Iqan{yamWvZCZMb72W&jEfSfmAd(m+*jYc8xbveHDt_iYGq|5 zD?fj145?yJT{T!(TVDjKZEbSLKddTxR_e57CtS*lkIIP*5t^jN709(?!FEi)| zih5?FTv=aNz9f-M@SnP+ytiL|Smv?KNf{%GgStOuHTh~SVKq@9AzBYY$-7Mc=^_Qp zW<9uTno$Qn-FgD}Y^FHsh9v3pQDtC-cIEs+$0d8O_|qD>50Og`0<`8*d)%E?P=kbD zt29wlSa+PKi}ln*?Rx{oKF$0>uQm6R-kw&(c9wF9}lhRvj`Th1uBZ96Qp zayf%?L0y4c>4ggX0s`5kL6eI6RTba@6XdN9^M_G5CL9p9ZaDN%*y?<3hw{XC+a+!7 zFq@ckl-hPI_%s}$lfby_1WSHg`LJZ*-lGp6KbEG$f|Fp|OOF+NiyxM%fdwS$etj(j zH*|OIHg5&4;F_|vad81eG{$O7z6bA@Zm*A89UrK_VPfh2{+b~3C)Y_-a>HDEO_aqg zx9AkNn&wVt9p`m>>v2NZR~nld%1| zzgW6`EPks$xb=j!fxz||OhP-4dP1*s7Pz62wmmWN`toGK{v(5*3JNQhOW!a+j*LP^i9nGv7NzP~m7bs%yUX_x7g5-1$1MJ;2gwsqEJ*OWv;HK?JNDVVkao8qPvl{JbYmG} zsb2N4_va=OM$s;6p!#3>l(=*X7U!pLKAyXUtUqvS^WA&KDqc0Xg9S-QG?beX>$db1 z@Ts1Kb9^(viCa4WZ}SEtBO}SOisfo|bZ)x80WWkLX8Ysa>8`odjBhK?DG`HyCRXn6 z#R3=IJdP+0JA}X$drH#>)_D}6s0?2|vDBBjun$v}P&?Z1v2FcyWf&LZYS)mOO6GV5 z3W#tlpa7P_zY19Kgy)H0uW5~9%3KQqp`DKxux$t6o^%!uV}rUpuR#eHQIWG+!}&W_ zMi~(*V95HgF1_H6?2zvPu-hkd$HyMI#QeGg8E#%_r$o{F{ig=klul>AfW7A~D3Bf> zs5SY_C=sBpnYc4FIM|L?NXa_9{nP2G{fRHeR9k$YqslPo7zf+vy}>bpTlaX2JQr0cieL&|_PiDp;qWWzshH4HPc;|j z0@l$&e9-L@bkmh0x74h(fFj0me^R&<_Pv#%Gqxy>fiLh;r3=ngtv{n?$8W60IpoDi zabLMgfxD-t=R%x`mF$+0mzRFccE9#Qs;5TctM%6CtDD;c`W-*);T3!{y%*UU&396w zqpt`H3zKlMoTH_-VP?AkM%88QMedfxIJ|3WZq@=ySb>VHCH9jUt({6Yc{ibBWvIYr zXl2Nw`k>e^d}}B843Efu?UwEEs+nDV|K;r>FiwD${>;q*6q@T+VM33wjn;;h=7P6} zN2#UnjPE6xB{5Is9f)y}rEtR`9GvXrrk{=FdnC$j14co8(n)zun();Y-j!k|M!MTI-LhHm!GqXJ9vX>c8hRNLC z{Z5O8g>1UK(tr9Yj|Dz4ajC{z$-w~v(pgV8fL3dGFRshZ6iJsW`WBmS?yj;bhzkY| z{nEJCu}MpmxSQf%A1v`cprg}s_IYNucEY2)%0)-{6)jO{kD6QcJ0F4*D$T0Fy*9Aq zN)KK47*B~Ol=BpRT{!;Z$G7LlmCS$qxU0)vNq$I@Te312jjBk&jUgLu-6^HN-M!;* z4xRh$zQ2ZgB9^(VvY>TL544zd1s>dG&Ql25Tq*gbX^8o`l`}c&@8OE{E~8Xb6gr!- z=apI+?vC<3W>K+eojx|~{;eW%7n>thPW5({UHIl{(21reh?dH=QfcF?Fnh(2)a^r} zuvkfDpN}sze}Jf`((`_6SWaAn|LTwIuk9$}>5p7uKHjg3W@^0C5JM<|>;*u}*L}B6 zt2&s>k%NGSxmBDH&J7SKU(8-T8Wgvz*kc%ADcbILgFrT|b>E#}2NWWG)4}K$j2@B) zP2ys0B|p4Zb46I|yyMz>WSaUSbSyJKt_^|uN?-+;(e#t7NCj+k~3?pK5 zk4SQ(2SdhK-M3SKh}%Vx;+2t*&@+0xFQB*HY2oUj0>Z*tQ~qR^0A1!TM9)^G8~6i7 zESgForxXR&`mBrBdauQ(sgM#0?x^&mx{Y8)rwa2YqDn(4ZfIL#T2aj+ry)zgQty@h zUq8?47LQ!$!46`E!Ek9{1?VT7fz*Sd+WEHJbeWkVJPEx^C6_w%@e50+UGD4ai?QaZ zTC{4Yhf^Akgybf@Vi{qw*8|rou=6A(*94y_GZApmzU^D=z{mM2oI4+Ea*9j@(nU&% zva1vI(Ao(V`^dwcHlZ#*FIAW=>7gGm96LAoZoNp{Uh2DdutTu#UJl_CFT*ZQ`>D#B zo;`au!3yoKl~@rmCg5wW46pLrvNNOhX_C%IkCxfBy<=q6Q6g`>NZS9Iv)cM9n}YY3Y*CRD3{Jhx_n1;7qV$hm6(&+WnP z(A}lI!t^>W*nsmZgiaHZI!9|{2Rl#3Bt3itbC7G~oaa&Yy3b%%j`tt6%#6d>nK8V-zNtyu`B?G0qgdS_zyL4B%MpN%x}rFFSfZFzaqL7~FE zqB{P`Co`2zqxNu@pv##|Z#;xFPM} z(Z9m6=4uKdj5Uk)t_(^4c%qcgQ+o=O@xqgoV4@ZD`s(#zJi-6C5#ceLX|8QI&;2_y zs)kUm$+D2`a1SrT$qP*FG8YDJW)@dJxy#A8v~${n$w$UPja1=tLQ0WYC5nx7R3*IR!PR0 z&}oQ@9BsA0=A8+FPTBcoF=Z;;s(~Y@Aa1n>dCqg`3^JFw6+Axgiw&-H9Z`NB(Q`kc zpB>9H>@w{*@c9*IaxrC&`y`XKkfr<-U4gAFd7(`R{-Rf)6&$`zP+wnl|YxHotUZZtCHZaN886sX8hAr>r*R_6)QfEssT1< z@g{Pc?xY!?Q7SkdH7Jj&gY-+9$R-XJg+R{q-Cmsly~wBC*@qr%m*Nj3N;Ty?k%X5- zu;!Q7Ci}-U)ot(Io+85sCCrjLK3_u@QU=BSPidiP%5ue{be8*57; z{!sZXy6XtjZk*xsyA@?I2Q@8NS7C%*WEi9_H|%3aC&U8sZOjXT80wIed~A2g91Cb> zx0V_sEiL=@EUj3bT7)AOJu?&OEZ9qoqm|vCZ4+x1v|s!`>OQUAMa z)0znDeE!0@6e-_gM{)t0#QH~8D@q93Y2r*|@%FD7P9zPMZw&1b#XFO;rVvz2AC3OX z6IUA-XTHF0dNLp@M?<85JI+GoYCz(bQ84Elf$b=1@GU%h`~zKG%%z|DZ_RlKrd0@i z9f>kTMBh_1EU?g8tq{ND$(1?j%cng5t-ZgD^8rQV@#XfX-c}a z0lP01oTqOOxN09Lnt-8kH2_fm*|sBLT-zKsYM=XX^UIwakg>7g=%}b}Eg~yB&ABCa z$cnvwkH1Piu0^rM1>bEC7&9RPSaZ$GUY!eT9p4MLD7aK}PQfU!14S-C$ z66;>?J)dM>^vt%vx_WfnkV}E={g)73XrHbH%Z?GBn~DB9&iG1}?x7a%Q&iBf>z6Q+ z;&E<>2EKbS%r5(TF}>mI-u4Ihksq9a%X{y;`aV^|0>dqDF22~}=CSY^k31gWh$%sC zzhZRWsdkL>vi9HkC^Hm4G+Z)-mr?&&-UVKZw}xPJ*cL_ru1Jl&P|9Uq9!z7Fucw~u z-XywqBz?TPvf9{u_E$9LXmWzv6lP=CdwXqQuTn-+^q?ZYrt`qEmk3XLs4dz#f@4uZ zY1zD+(-d$3y?_6Hk$YuheG0wif2z091k*wxm%-$Q;F?jqalm?O9Wd$^ADIQQ-kJ6K zJloJfc}2hP&)|rhNn`qSgS~ z-9hi%*3j@jU3Gf7{LHHgrB-PMhuu_=uC2bc^>(mn)WAbr5oofiS`}?GTP>rtio?7! z1G`Smuq`gl^%NI)7p%4q-d?VTS$b4(Vz$wm`Z@CxS3dazrL?2t72CYTv2#(Ge|Ngu(d*EP;3TV7_$#ns zJ2El`Knu(X+)s~xMvQM}*ZlxV;u0%S#^%p^rp^mbnYiASm^?d)x$Stuy>4eT1M_8s zNFCLBOKfkyCzz^JtZLGG@UWx36Yu2U@J;k{!TJFKnIPfe37w)+fZe5|oE%n1N{TRu zy%+B%0(w-WY+BC=Umv-8=~7fH{AiP%9PMI#yPrln{I$q+2QnXZdu3s%<#77D2x}pO zi0sY_lasJty;pz~DC@D;@`3ZA`&_x}P$>A8{mc*h&XT^}k!E(Vn2A-itAt~^@$8C1 zSDTsjR2657m0vU4=0O8EA%xHWTi@k9o7LQggYD(3uz&+cr1ONI1==aDGg-DH=x>kQ zaLAF3#yR_i&ieIBFzRTG)b+l=JXG$t!66=Js7ioFs{nhN{SRZjuBcAM!}Z43BKu+i z=mPR^2CtuZx%})UQQHuCdvQ@Si@dy;jpTZelLZ z7gVa@y|69|?kD&bm_z*c=Ov{ap29qf`YL69rOBvv5hxxrC$8#LO_*np_mg!?T|$nm zGb(>i=S)v4=hvjvZiX(gh_}&_N3{>J(js?JKI?*-g^*Q=U20w5%Fb74WKT4QGtB(_ z&CEG19i30LL~4aH;DYCHZrcPLfFmuM2m0Xs}4rp@*Xz z_^3})=&zldJhfpql$JHCAz$kC2@63{W0#kZp^2$TZUqrOL~Gn`Jd}unZaWq+su3cw zT7V+=m>FTwjZ7Om&*=}_+u%fEAycY{*EB`)na84Dqmji*bMw`7S9#1je=ja#-3F*cgaWX)Hzi+t@OV3pniLnbC4&ve&+R{-8OT(z{|0o_muJXY`<}&wN1i z&N>Y?V$so!`C$*-`^0L;Z_jmiDgfRyKDV_v8s&6dP7X^b+3iUab!(theoo-KIoDzZ z8H(E+v5ta-p4;=P{55KCyEay_$O$Nm(^^8{Snot#%|?;Ur-;vXONMNP1b}z z0U>oHv6ZrR5tVseR5VWQaGkHN$O6A6*FZto)RT)S5B|tIH-8}ixao4jkw7+s&JOV) zVq9kjWL<8sp0-pJ;p&bR)^A{KqiTHl%^PSA zVQnPwI?^>0l0t5hN40fHT)(w@14#LeC~TYL?@$9dr`^-^`8fvJO?><_FkRYr;1w3D zFq{(nIihdFL-yM6Zb!;`kl<*6T=KapALN5eYNXVQl*<_iz&>{gIXv#R zUk5#G6C!1D=I7;Y_!k-{PkFARJ;q(C+J_MqT{X&XgMJ=cT~#AJ4j>?FzboMWP6eq< zvPaBviEW#*gM`I75m2WGA3MY1yx&ih!DS2OnpOpPh{+%A|9-obUzlGJObW#*PqXXdYVMXA zyEybRIeW@X3`%O1VFMkH8zcDjwDgJ-P*CWqv*Cw)e*WXgy?)pHt5><_=$)n)xzCP( zIencj=Kv`jjxE=S4$M!uV>W3Sc&yU}t1-%JTv!VfWbOHlf|ybY8)+iGK+*Jus47?p z>>_b9BgNQ7(7%p%ERs=4yWKW>$7Al{;413vcvo3nSVV-XlQ(-BUM44wPR@$`Z z4+}^GJ*Os(nT~6w$Ii;;l-jS;l}JY3)D?YIQQ%mf4y2MVQUOq-6pCRUaCPSViYBeY zERbd{$1#tuYL01}W7B`!WxXrPGhR{haIQz~XhL*pK=Px-=1)eZTSmUV)z`)`vgZ_Y zh#7Aqr}%+M?h4}vrlZJP_KmNO4cq^?T(X&H2)aDlDAEuDF@K*$ESq zVnscN_mi^a#*`E0J0r4Uo#B#&q&OajKK5<$S$UE7-Amm4S7@IiP*ZVW8@DhE-;n-c`yR&^TZAg zqjKyMeHyGK951aA$z6aZz+?g7oJY3;eneZ3__uQ?kB)v<(b#>+pI6wQUnG6a_R&{H zKV`NHiY@S={Dybb*YDNrnL}fXi^X%a7y1n_9B;on0PY%V`-)`0Q@0hGUqGa^_DPC6 z*utlw!o%?GgX|e(&_)!=@QI~UcNL>9gFqrd>D-zhVsEa%W#40|_u2B8tWNM5-lymz zt|N}2SMvR1#uVk&{pSS*a>v0{y9tPxStz+{YZgJ-t_pLM(R*9vW(7lE$~B@z6&8vO zym!-lWV*y*IQBJY($HBhfqOGj0DdUxg78Jpq{tXG8?Z4|D!4$&f;fK)xp_pJ z`?onP4-EwX0k4O%Z&_BtB7=b0VUf@Gc#i~onJGww@P0^ z>tU!gIEMXVLQVYxH{I?|lb)m z?rYtaV(R+NmnP9ShLY)O!m zECC`t;exw3E!x3tx4@s_vB+aY^1AJq_VLU)8Q5l9t{q>u@|I4G!|eS4t&Y@ZXv~hZ zmV#*QP}|hsgFIK_`1Hjw3rp9#R+N69UuKN`h=NRKz3j+rTNB(uEPV<104hWrD zzRP@ka|fG~>`cSf{z@p*A!12`>LT?G?^AEZ=f^U~@r6m@zEK(eFL@S%tSG;#qN|U*S-jL))HgX4rf>bB3r+*TiwK~tJPuv_kQunHYoPa=I=m_=XwJ!e6nEfr3BO`?@~`y z_C@hP4Ilv6xg+T@e;qkJynMLTlYpmgqy`?5aaDMkEV4HM7e1(R1?k4s7!&*zNB_o0 zrnd(b1O@$zCYd=U(O-3@bJ-wcJY~q2Il@$Bu&6&Xk0L+ipe3>BDMFzrADsQ&*>>p4 zGV6H9I~1pZ6Gzs}TnRENi3oxe>LV`ea7FXmr;X}&FHMYk86EB?ODq65LV&KTxk?^E zfdolgy)i10?6=~-J&d-HyOb<-JNT^90G&Q+t%k5Q@8E)S($ zNReCmC@Z|T^ZWTbQSD3BBMz6Mlf|@X50cMO3D#f>`yU(JX~XX3npwWMFe6g%N5Z*GnTo)-tU+zvI4;)3G1h-!5*XTaA0V$$vvqdr@5>2 zLlOKNGU*BVfwmQ-GtfmJ5pc@5knh)k68wu`t)XXH+}hK8ri1}LsECUH{lwj$wd;26fbI7UyYa;CXmx9JLUq|W!~!PQqq1x1 z$(;%s2vF7U!9lQAq6r}Xya`|%TATR?>(+ilMkYy5Eq?l`ByfI|FfDE@9M41Smemj{ z-Ei`w72_LkOPz~qG|rzt4?x8~OFglAg!RQFRd{BV__K^sH@MG`;$P;>D?`oSY=={* z&rctCd_8k1^%}WD)OKMdB7)u^f^mKypC#ClM0@a&{>i9wy-d%>g}%&`a7CYQ%l#F? zoaY_XpAiobIjFvby?RM0yZJ`}`R<%vMQxK}fMq`B$&K<}<7dUB5sVNM9EjmT$aVb2 zFHC+#3g}LzXMpSrGO)F{;ixIx+XVVO@d@i-V0u}bEPcLE&l<_Hpb_WNrEHIN#sR};Zfj$XL{Op2y@;&hGg$Qd<4fTOvTq^e>b^Z zOE5iQN6z;ZBkVJNtD7fUOp6iPRk7=Y84{__VV&UZqb&udD6@^OLUa(!H~)*P#b8DlN4^MM!Xi z)na#==y;`D72coR76lU<$nE(Q8p=!J#>pH~8idk6RpElh)91#^9R~^_)Cc~%B;Mpk z_DNNybMU~GJUc8Q%(!&Qo!p}vicmY_*dBdV(`G{y*$Ss_3k5{JrT=-f)d5a!9NSlQ z(lHvkyg4oY5?zVkl9>*gk3M4B zv(z(mH(}a{;cN#+)MKG+b;sa|rJ!acuPpeYC>mSrP42#HM$(E5Oy9E2oN@E?0+~On zao=LzUn;9$`hkmlSW7Q1jQI4i{az`o!N->v1%O2e>ne@PwUMhvMngGd7a^Vkv)aL- zn=eLl6>>p)<1B0!h-KaPDj09aFbGuV6A#_LVjo(J)D*Ph19b1rtD-Y?$8o=%JX3OB zMhS1`{eU`a8GSX{vj8`wc56#FaX%JLeG+sy2bbZbR%QcnfI|RiwfzoeHyz)I)%Xd{ zo+0Slcg19{*E@r-<{N8Nt}hvRMR>j=Up+fO?*zRs4`Y{zch1p^%S{h52cYNYAnhS5 znCCW!W8xw1tlDQVbu(eH`5pX$1la*klOOvJj;JS*_NL@G#~-f*_@k%!BT7*)ahQ!^zVw~-#`9;YQ_IHEAGEV`2QCJ`rnWL=6h z*926cZmlE$Z2^AZmC;g2nV1C$JX)69Xf`utktPS(E(R%Vpy&h{j&&lajX}`M9 z-YT(vSYa8kwnK^`Ws;GYXD#6tHm#pBfF$(gQy0@xF)ky$Gafrb*avV`cT!B_GGX#) z9M`eEEY~spUAP}z>oZb##nsf*WO-c9N}NMU?>zL~5-JkVLdRIWg#^+Ze>{Sw_mcq! zE@>ZS$>&2&EU`;!h+$xg*NPRd%r$py8pXG|5mQ8QWa1eGLg5|NU85=x?P!U$gu{0A zia)WQFr^WdlnxKtpv-gOs_znOcY|v9DIx)X2UCIRk-_Vj3$Wl~OiDckv_Bkyb3Nx@ zDaZ6yNFUWy;9qrPnM$3GAMd0R+#Q?`{QXmRcD`n2f?m6Pajkx4etY~y_l7P``g=t~ zxBT3hB>87CdHycKl<4of^}EpJNu`$b0jXFaFuxowuz<0NqF~3#40dle67*6X#r~nm z12P}eY&`ZdikBrO@n&PX6Ud&4+sojs2z2eVMCx@zWKDOv=cdCi-zh)q)x63~a^N^Z z?Q`_i9_K1+7-dH;c`lG2nahS|6|%hWOHszMVLPUS_7pZ?mv{qLeRwKB9M40KRg<1$hFJ^V(&_jGT` zRbjv{4R=9V>)N37i<$s9Ndv-j@CHm zs%Xxy6ck05SYwhXq%8byhlE_K^HGqxmG6e1A305K0lhmF2Uzf0eN+4jq#em+8x{LW zltePOeWfaslthE_B;7>1SDgjV5ul&rTTh2kh283Rf&(|(Fs0TQ?=twzB+6{OtZ&uX zX=T)7Ad4c83F(j~rxD>`1nKUs`VF%t( zerGz4_p_`x*lXd+Nh0*dre_X08@GiGD_Z&QtG9JqIabQvs*GAbHKv8V#Kr1dR0bt~ zT&rQKA_LBh9keU0hZU;!&-?wdM)%~{?+9hf^&ycaQDn6_;F{S;C`fWroj9_um_47R zLH3s3Snb9M90gH~+I;7)jFvBV&_DCEv9W2iZmxXN)yKIXXjEB8A*C1?dN!pKzTA1H zs~e#*bg(T?;6UvZIB08YFMLqz`Au2MQcy+lJY27G>4`n7c6dA8-~YApfy3{t(qvm| zFW)(b1*APkzKDp(JYUa^&nHe(#S|0@x+EWSau!)6h=yHM5o&fJ<)2_g`z{ML8(1MTUwhM5i3%AkJ>vOs#SY$+FG?&2tp`o#HPfG*fTbX?LPaz zdR_PbzVBD}^X7S;^M!mqc_F`XoX7D!zQ;OWv_o-U8^5Ppxo+jmow6-jsCQH)Hn$U& zXPDD53P{nX|H^b0yFbz%8Apk!McG-^?{?3h809iRtmw@&`Vy$$^;TpIv@J)q;)z#8+u2~dHJ?n}!GA259H(yG4nn!=xI`n+Sj*Qoe@%~mfY(x`VU|5T* z4j_+e(lAMK(8!l&|GWBY!KZ27KyCc%S?(UX_ThT>O`?JHR{BAqj#O)TOvCZk4djj| z=D3=fB)69ZO`&w`g?N$xN4nByS)b?@@-r8jJBoIQ;z-k*SQKWU!_!Q@{-sb!fdM2WnHuyEkvsE*vuqr^>2Hg9IgAN zohrxr5wRPawQF6)q0=%>ZJ;=zi|;W0ZhSEP{s}luCO? zW+JaJ`5psYaM*B3U+xN0f{ywG;IerD{!eXYUK-P=6<85dSy_3Wz{e)#qw|?UKdM>4 z#eYu&07m9&Vn8;P4(HKD2gq|uaD_e%6BXO$_Z~akSL?NF+F|aL^*Jt2qA!I7%spnJ zCYG8F#hkgYkAGen+IG;_&MWU(C1Fs+%gL!&h#@EOdk=Qkc6r;@@A7MtsF#L|Mc&{D(9P7D$CymMVsS`Ype4XT1{=;>DjS{F%- z$4h^e@zxz0e_SGa?ymnj4?o2w?($MmQ3*gcP%W*8yfwQuK3L9Cu4}(~_{BVnfeok^ z>6L48P}|jFpyZp^Ckce2!h?90{iSLeHZtoC*qOeWnVa6Y5;wwtS?|4R{wyuu|6 zSN2}&19fQCoc~to;qbo4<;m0=bFSA>0CxQD^>!g5@xF$Zj-{H=+HYtO8R<)oj$b}( z*to%@6(%D0eu6^mj=tbWVJ_=R^op6Q=Ixr+(9`BDFCYJEXKQO)HqB!r(&xM1G^|Ne zojNsDu{iaoyb`!O7MZ+r^*GqY!^Q;Zjmt;|UO$Bjzw)1c&24eKnc_oHyxP za^BVv#jx3{R3kp@G-$7EiaxS4eR9|Fc$Gk1{c`Y&Gih&{1SY;n5vhJk8O#KMN&)QU zo$LAAc=pP**Pb4ZkJkwdu@hA-lHQK5*&);I?M}~9=a#rC+r`v*$7#X5FYGXxrioai zAhV+jy(WNfWe9YN)gDLg3}|~_{#P`Ae21QS1(iO_%5Q4D{OQvXhsHFGAq&I1WD9!w^$=jNpgN zFp+COK+*czxb=|aioQr&f&~p~UeK<6uv4R^8~!1$p`igl@bV%?Sl-{BSTLh6_EK_+ zOC7%8;M45=vT?}AI1xgYZY zZIlrX4gNtLRg~xR2+2K-qLsdtf$5^V!vAjX9W3ucJx<`PHp`XL8> zzH}(~veq2H&;VMGe+5*Jm;1K373I#w4C`v>RJNo#^@O|p$CL5fdXtfjSY$%L@DSS} z$HkHI4W8_zGOtd~HcIaY7eF8XR%)GMz0T7aMFl@r9*Gb+DJNtKzwLsfvx-?!hjq`R z()0P%p*eVi`4MwSVShG}TeC3L+AnxQfuc{5e#!I~FQ7i1RSwdfy*4g^VI&{^wlT3W!+L%j^pKtdvj|VUF3^gOw^frN+n9vMG08 zeQ{eE2dPPkA!u0hOXpFxDQtebZ)=Xz@X7~3VMMGP97u) zBdr=N;VnQy@{RS^FTD&-cKYt^O$8ZvhsqpnWyDk=cZnZYzz$5IHVpHr&b<&F`a@jUYy9-r>BXhvL0aUQowuyr zH5H6%=vQiQeKr?c$kC0UAF>rG4t2{wWy+#QDv{udGw+InQKD>A9q0 zP7dl?lr4Qr7s=cw3o;@9gZw_u8kO?9U)Nj#D2}8}F1S zT`3J47}%=~+ZUx?T!ww&@~wp#`sy0`+La(Cs|uBn9vc8~<^d9&vLJsdSAn(uD0DV9 zJ^?CF9s*4UT7N4aeYB&usti-8l}8R-)56U60rI5f89;($^)4wQ2X)iVrzeRx@Y}6a zi}3T~P|WE?z6(81w~~=kC$zfdoS5E;mwB#2O}|#~g-JjegmrVJtJ}x?>6TSu9ip_X z3~)XmT>5Bx;jJVM02>WfdA2{ev^b_)t-JioJ_v%jJ@~4+dOw`8g_0f?*Cu_8eF<>? z!dqu%eIP45RRA|a$I5DN$jjcek#v?o85v0*oTkCWK1!MeLOIbP2>ZwF$=iJ5g)Z8I zmoJr&;Vr)grYFYCAz2@3*@T~#1uPaF9ngz8PQ1~bQ##tx($%%fwFgp(5ppkNac@p3 z6s~Bec8}J2#|^)r@U7TFD$14(7EAJSbNLT;*N}Cb4*Ca+V#f}?)mSy@J)Ka1Ao|oQ zf9cBdlwKNZSYjg6Z!Sx(TYqz^?v~K75Lz0anGJY|W}S%x9EU*Z@So?&s8v7Te>Enc zTKUNH<$oIP|I0oF|NPhgH>z|0FL+3CYDTZY|F3hZg60#a8Mj_$HMNZNTi$UQ%i}-B zexSQ<-Tm(&b3p$TB%iAPbTS~!DA1F-4p!6dRE)dlohzfD_b_TTkz0L)N1rF5u1dwa z;~iCq&ckVk6mC8v?SqLIx*O|SV912d*^lA9MYtw?xkM8q?l{6@QEuXx$nfOS3;O3!nVO~x|ZX~Z+3+5!6Af|L%;7sgYlUvGB z9^SZYmLRjXbZ62(EL%1m=NmPK2m#Psz;pg3w2c{#SAdits{1T*HM%m%Fs7vLMB@QL zvOhIw@S5wMsNAj%*zicXy@5D%m2vti@)13A+_mHVmuQ>!TDM(LGG1X(m(UiiR?v(2 z^izymrhS?BL4|0E!*e4V@;dRdpx|g!!WKJW-&=J9X!!8^d9(kWO6t^gk)oKOmKP(M z#}bS&Muww`&~Al!-A-ss%H8%7AueBfyO=oDUNp+8KLL)AdmX`a^HHmWNowOH)uMgL4;~OaB@keU;H|x}5*<-Hr_< z6pmZhjpCBSucVF`8uFxL&XY#{#l8W~p|e0r<$UH|hEROItW{ajLClg}(Afs}J}NEw zoANnW92ZQak#|S`)bXGFfKYFu{9=#fvR%OsPZ7SV)rmzqA+nScASwY_ z9v|yCx%suv8;a}q3BK)cNqK0p|S$f}G{Qp2g2jpH&f-Eu0aGm@gZ zu@Ikh+cM&P|2izY;?VbVk>noX1g8T*#+j?g-uy({7T5BFs+>sQBG&>l*oaa-Wo#jb zj$wU%$r$YJo9aC_7~86TaNY8S#IfBU^L+ZJdlAQk<)kopC~@9*6uNl*)_}a{(d=LW zs>$%`*)l(O+?!E6E?T-XhA*8Cl;0_Bm(^C^+ci-t>sBL8v_Ie#^bO6RMn&}|O6u;! z-3nzJ8!={RU4I>V`S>%x#BLxfhgE-;*huF^#&Y=w1QE>uvU4`J9Jpaq~`F27j4Dkz&hj5rVE>gnlF_VO9`c2ZMh#bUjAh%L|!zl^hX@$ z1|Lm3DWoOvoRQ3h_0sKW?2HMNcJ5)RRbu0v0SD9&bMo&7TkzT(U*?XOLGv3fYon1N zPECe|h`zZ2ggwD+W+>?G9eOFdOyw$vii}LzYAMqt&MnjFD+wB=YUpb9e$!VD73LPrh+d-PE$xb^eQ#Q77s#y=EWrDpiR$iU-)LeVNyCCbBaT0MCAv; z)z%V*xf`QV<5xm>aRxGn`y`D8$HApFrW#f}_ou&yD49Y-F`wz*Ci4C)B;NK{sN5=} zwOh(K{AaUzZ>auyl9$&tzQEdERt?;c0BpQU0$$-e7y6ez>{}CDP$PM-Z?gf4{vD1F4ugdNqPWoBn94qQOXHVWG*n^ zu%|J0IU^+o+LG}+X&rSkG;aRb0W~9`n6nEj1xF`;n!`{%>i^S;2o+!yG&7YJQr0rp z5d|&lJ1V>!_*BE`>xAevc*%d4q^ zFQLPXrKBngQqBv1)tK)&#JI0kji3s&_wAOECB@-7F(u);sJry1TmqRQBk#7s;M_-1 zOra`Enr5-w81L$XAe~uHk~?=SC~SoOlej7owHO&0%dO7ZQtUBctV>Z0_$5gHOjb2& zp=7a-*EPI}eLJG?s}bECUsswl5{*4!026mIA$a|!PMg`OlF(Ie{cj5)PfI!bQ!RsQ z!`d3@K%9NPQr4VG8K(XG!!;hEYc`qJS8aN`@eZ$&*$=JXHm&6#g9S`G-dWK(sFm9i zq_&eeOxRMFawCrx@FHGXW&3ws3Nwyg2SEHBLu|c;;lhRFt$h`}hd0&|xeE>L+6!#9 z#=Y=aaczSe@K-E|`46eQRTQ;Yf>d4zpqcMGn|?Qx0=0v-{4C;rO|!4YT`?owfy;%E z{J?N0Y5Na6pEPf^WsJ5cL_?|$GM^55tz4>OR~%buH?#e0lIym-Rl2=V6&iSF+AmBC zHzyqyU69m4GG|xp=;k*&d^}%O6!8w`5S2_@MzUgip))^|z3QxyB7u@sd6|0_7E=*SQ2{ z`B$tT4@lgkz7!O_@GHk@cuhd9OEl^JE!=LfC;wt!1l|1gfh>oX$9@gURfp&x_J;dCJHnb8 zt8L+dc(vH4gL|JYQ)rMe&Pa~&o!sCwVW6Q1_8A9;!Z&jN5NZ@r68YFI(SW?5$N0KU z%iG?&HS(RD94?n3>C%wC!Vq-&_GD4!J@?nICW}BbxovKM6>pD}L^dMImxXorUZi@H z))rp#FU>UJjRt__5|zZkj>lUWp*ADJw|MNT1k;*9%gqE2bfDY&HL zUsF}gjmG?>`AQ%K5H~Y-O)OkXcMhZ5fMEd_Fg{@4O+FaIxYsIOVZ!L{yvO$)Mwcjw z!vQu1)03l{SCG&ym%W+e9K%UoM{8?h9nh-h_wz*QI+goNb$rdeID;tCQeTCaL+VZ2 z&M0G;7i+XaZdxqz zFO$ce#7w()3i4!?%R(nZa_EeTYAU#iq_p{xi;ED|=5fiEb zfP$kGpX}gj(`3ks8>)D6@1e9MFX~sUL`7bSzSWKCo`5V1oLT?afsRFi&1g%g35u`9 zi^YPDfew7e!AG_^L#@K%Y}9aM@|!89W^xIHit*_)b-k(5lA`A<-N$&%s*o99ra{EW$?6AVbf76rnc4R| zRE>r(Zz2!WU-j=Rp?YK9bI!%aCWhw=#znVtCCD|7v}bG4zdo9`e_x`n>OPYf4Ho_aCNKaqC@-J64WeLX8M|sCpWyjj8CC6B}HAPijA3QrqOnC9pZT&6J7YVw5+Ce>O zF~(DWli}=+3|mcG0u+=_j^Co^@LcYlkeGNa%y4r9*0q4^uz*nBB)6M~jb6f=x8?#t zh*o*V=v9gEAXdm2bTXel)Mn{pUG6%K?p7hjo6z$%oxRy!q>To-jB}A3bO~FB%_yZs)G(y*MNA%&>9wTrfOosI4t1gA4)i)qV1v^Zm|aChK!Cuf0eMd0OOLBXkljdAGGF&JE?wQrC{2gS|Ke6J{Vnc z-SVXruuR?jRU}JVSk=>8$LBMRu9)>f2CK>%5%ePxx*7$P*`Z~NA!qKd0;ctlBAgk_ z^Qy`spIrk*wLj@die)bWe_)BkiIgfV| z2|Y^3G>5FuJhi{ZlvA94q`_$be=4UaD~%bZOL8(zvzgGcIWV}S$Gqg3ot$8dE|@+V zs7uPdW5|thB1qr+#1v_7XBIA>2!5aZ786QHGC`lYmO7KKjgFp8(>XV_zZe-cyL175 z?*$Q@lYZm5@w~za{VE<@F)po-7ZL7SH)V8u&vHC4*Vg>AGk9#k{;F?;H@1ms+`1LMO}eqzACsWd~7u2KdZ5g7RjjJ zd7AJr-7o)?-#6Kg_($Nb9X10lF1ED(ruj$W0apC$S#5M?CwYn=1s*>9jyfs)pfEP; zOyb$)P&!<3W`Fc!!?54z=4XmHn*{*=8;h((qu=lfEpHj5!HjY=&FY`95xUZh8=@jX zcQeh4jUj`civ7H(+f*^~ ztJ5kWaeFmdLjBaGC!CpN8_clb3u-imyW*zae0qX!&Zxlzjj+j$jBJABz;n2i>Nk?x z#!`LDDs&Q(qL3s%fVFeWo6SwFlX1GTa_Lgk)BZW7r2vu(T5_+UDnCa~SiL??*3_cn1pEp35qFATXYv zFEdN1(PinN0VkfX?ntArsJ!KMAy-7FgVf;WY5{FWL^8wR=jE3>N^$DaiM;7@5=Cu_ z!?tj>*^D|CVsVaB))6y!znS{KegS6=PD>vZ5S_Ec{O;j4D!yz<(4q23N zjmllimE#%~_2F$v;TIM5XI@r+_08B7yOgupxm2t{ezBG?$Hw194;KVHd*JDxG60?0 zKt0fsx4Utwn$zpoeZei0d~Eo*CdkJ&Ak3GAL)~JC)|%sbAZv{3SKkc8d%IkBW=1YJ z>0{m(kLizHF>mjaJ`aq9ly0SP%Sk->%F193hX>ghLb&4yYL8;?&F6Y$;OzQRpJ7x-i!wZ(bkiv`_x#1s8sX;*xYtQq}DCv6_Rz06{pw*E(YQ~^%$uum+X8f(y1 zB{S$yJ~{%K6WU@wdaH~%z@3Y9I#5)obI5IyKvX*w%)pS~-E9+7?ra9PyS{#3} zJhSL_8t?BDWc+Tj%mgZ(KH76TXhO|-jHIYZGTQ0`Y0z}KCnRO?!;C4E6bw2=D4R*g z;}vsm=?C~Lj146?{XkiTUP$>A>R0eZ_ai5Z5LSxX;EvUFvdMyeg9ga8nRNby>dGOT z>EQeX!Cle%opxrBmP32A8f%@(cqy0h(cnwHhFcxGlnZsFloEI<tEn0&edGk_tu%YvmXgOU3S-@C(O+v<6mlZ^l7DXY zu4Nqz)myqJz#d+0Cg#MJO$jQ;KzB#N0F$+n2a3*Cw>yDBesdSX(dEc|DdNI{} zrHre!JB)#p*0-vOJ&gPlu$@MtLtCge$$sdlCG$mYJ83SbkYAH?yiQ$PGHI4cQ)2(n zs*~*2gOUy03zTCRtLS;#Z+h5SKzV-_RZxAXf&XiU&uVLI?Z|J-w4=Kpq)N0F+AGD> zZ+!pW>yB$;>wGP3-vr2*8|h`_8f#fCI6XUX$D;4+V@;|3er_adLF7d9DSXb@(4_37=PZ*(B51C(wOzSu4EUYwbB{YS=1flzM9Gj*q+7F zTsm__+#UB}hu)+z)1Ly)8O9Fn%?WmPh6CnC>e<80V(L@C%iCoLlqh?U{7;9yKNvIe zl1+Q+wtUudb~o$+M&?JP57^BRZ`2m)rqtZ_XctxCFp(@489Q+mxdoyG4pJjQg3x;g zwb3xe9YYKw_aJZFy?L`6Qu4-{B)2R6Sq?ktTQ~~4!nYyY!pd5%6-E&9 zZpCXmH+-aaI+D-zS;nx~*lyhV3~&(%)nCleL3=;`w6&XfM&*+zI+qas!KIw})b145 zu!9K5r*fOw46Sb)dX>qHLw3LhFxUt)#& zdX{=GI$;RnOOkHioh>le-&A*=TfJQ3H@LT8g;u)&S&J4(c{}=}ywqeb@LU|!8m$Az&{ya)kq?d1`Pq#W17ta<(eti16P$XSJH~I-d<-!PGOdxz^mtA-i9Gr!!H(`p zeSHr)4JfUpP0vWjy%f5^&Qyku>dx?Z@Zhnbpyo4G9544=dzL$4+Rw(})+L1!I`iWg zD0mbwu@y^U+}3Fk1!2;>n3ZBtc1i6icvyc)8ZkVC?SSG(B5m+i-ltb0-TI?pu2H24 z=TbcU4UWz$7<@s5<9QSG zw9!SqLhIiV^eqf_f`;Pddohd}88ORwYvCl@Zd{iHXyuz*A&DfcAJCODy*gMUuBHu1 z9)DnR96+t)2DMcB@d8|)bgUHpDJf2Vzr%`uDc6dBqfo@GFE~_-5Vv-eo*3Kn*l%Cd z{iR_lNX$HVYqs1?93#O;e)_2bZOk<7iV6JkMFBX}#awig99f22FV`xc4L%ypkt_%Rqu_|OOGluM~Df#L4X&gpU zbdBK|5+M6+EpMD=?9H0_LgQ$FmsmtAu}ftJ;%e-pKug~~(5>a^RpvIc?7s+&d>Xgw z)fr_e7bsU8&kYvoEFM*D1a*v{`Di<%HqwpH_}zy(jLfZSU?`?l6NAH2gAot!<}lI; zbfj+cBPz_R-b)m4*3J}upc^O(4Cio4lvUGpe0FNSO+-JkKeH(D)znoPTt}Yt|vng&y$-x5{z528{uAbd{Dq2K$%7Jp5g8tjkX6=M;eDYRu4`Yntc&cpajyO#IvV z({9YCE@KX%3MvJ7BU>G|K&8(pnJ-LWVj}NH<ws^V&;g2pk2g9QBsgwDn>bReoXC3+u4q8weQgxR!n@ zK0o699&;wUMPT%rrSaAA|B$j`E$aW_WHVb)`VB2)7Ve0tXEsUsCIfkhe1Gq=3nQ7% zpA;Oq%(l|xaDyu5Za0_;5!PIpH9bumBVQ{2n73v%w;>4=zZeYwFaK3gEX zI0NR46hlG%2r*~y_frm-k^a%9Ye`%H???wnR=EakTs<05!A48%#pe^3b0x~V z{D4T`&}hueMI?Y?q1`GmwBz0Gup+%@4fjCeZMDb^UjpFl@Awq|=IR+JqMtaMynu8Y z-=N?a4{zT~U;yd%0U8VzWQt>pYyW~G|3&+21dB9PESaBNGIEG=%{Q2IfM=5YoC|$W zI6`d^l4-Th-|KeES-IU&6yQBBaPP5aH`xT6LfBpxn#6Ixt_93bZiGo!_))ZgDqXWg z1A-6+Fbb->dDi1?POw(F(5RAEHULg z9D0~UOyFf3gXlUjgLST`uz>g=q)S2CRCt|k*Kup#;1Wi<)Q36JUQacA+=9k%ZXJ;% z?>Ss9NiwInqNQAnwS{vFb|2)vxJ7$3Kk|LN`t}Tq+j^{p_Jy&X%S398zfS2#{4OdogymqxgpbNoajy%TODf|z7OCheG zl@hrc4Qj7O>);*QW%fsumyb#sQ;P?5*J?HO7L*zHAz_!-M||2mt+i7Sgh48*^R5cA zPqn5A$AfzKzAfF=RUn(j?AOY1a2?j*^D#N#<^W;?d@vnmu7ez{;K5nHEMJ>eyZGk< zl5Z`XX5wi1^qhY&yX#H{z=PN5YXfp^Lv$8H{{K;~3|OPk@!s<{{nnMCj}hJ&oHB%G-iqZ-ocuBnEM=`8WBpC@BH1%Mq@ueN_fh0oGT#NqW9+jsc6sE zi(cvjeM1vaS}R)|13K?l>O@KIm%h($r z@N`sG;MuENEc|LC_`-h&Mi{W149boi>$7Q(tjo5>>Et+*@e1)x*_{=mEV2t)fqT8T z6J(qriwCnq4{6mU%nP6h^9nnK&$)_AMwdhAw{;aO}8r3xWP`1oi zbyIL6Se-RSpBkh%1llr_{S2`JkMzj?eEhCMkWo~;*FLT_lrlfMQc(;D%|^gYv7&AO zh)j~NN(}j{l`!(pX}dp1{>G&TucuA!h}Z+bA%WwJz2Q9*Ha(t2BJ<)Z=+&sPZdIkp zIj>p$EdvuufPR`0_1Of{22Y28>)UZ{Td+EWV7y37 zt&@@BcXd0`44VI$8KbHTNSXp2$arLn-_PmWbdY(S%gp14l^*Bov9oQtx{E;M{2Yk- zEz%lR5o^&_TGgNV`AT=tarpEmGmDYB-P0@|z)a-WZ}63#nfR?>i5wq1bxZ9wZs$nZ z@~(a|7BKw%T{4*LyH>R8kL+vn{Bj1#J14AU{_7X_DnB7cU?`buS;@&jAZQr~9oZ36a|FsCU+xXAa zn%_&S2M1IAS9S9L^UB!&eE`mX4Wqd8e@xK%`%r%W{r~F^zPz?~Quygbq%2^AfA2%# MxvFg8vsdr_2iZ+k6951J literal 0 HcmV?d00001 diff --git a/images/evaluate_debug_console.png b/images/evaluate_debug_console.png new file mode 100644 index 0000000000000000000000000000000000000000..668cd1b0b6f6efb011a9ec0f11dde5304ec27555 GIT binary patch literal 44446 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 literal 0 HcmV?d00001 diff --git a/images/variables.png b/images/variables.png new file mode 100644 index 0000000000000000000000000000000000000000..9df0bd3a81503422777109444c5e9759367d3e0b GIT binary patch literal 94714 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

c^l;cJ0PT#=1V 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 Date: Mon, 25 Jan 2021 00:44:49 -0800 Subject: [PATCH 31/32] Fix: maintain the old variable reference after a new evaluation This prevent variables from returning empty set if watching on expressions. --- src/WolframLanguageServer/Adaptor.wl | 1 - src/WolframLanguageServer/Debugger.wl | 10 +++++----- src/WolframLanguageServer/Server.wl | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/WolframLanguageServer/Adaptor.wl b/src/WolframLanguageServer/Adaptor.wl index 25f2c2f..d5db6e6 100644 --- a/src/WolframLanguageServer/Adaptor.wl +++ b/src/WolframLanguageServer/Adaptor.wl @@ -86,7 +86,6 @@ GetScopes[scopesArguments_Association, kernel_KernelObject] := ( GetVariables[variablesArguments_Association, kernel_KernelObject] := ( ParallelEvaluate[GetVariablesReference[variablesArguments], kernel] - // LogDebug ) diff --git a/src/WolframLanguageServer/Debugger.wl b/src/WolframLanguageServer/Debugger.wl index 5da702b..b9af83a 100644 --- a/src/WolframLanguageServer/Debugger.wl +++ b/src/WolframLanguageServer/Debugger.wl @@ -12,9 +12,9 @@ ClearAll[Evaluate[Context[] <> "*"]] (* Output Symbols *) -DebuggerInit::usage = "" -GetContextsReferences::usage = "" -GetVariablesReference::usage = "" +DebuggerInit::usage = "DebuggerInit[] initializes the debugger with an empty symbol table to record every symbol defined." +GetContextsReferences::usage = "GetContextsReferences[] refreshes contexts imported in the debugger." +GetVariablesReference::usage = "GetVariablesReference[variablesArguments_Association] returns a set of variables regarding the specified arguments." (* Private Context *) Begin["`Private`"] @@ -43,7 +43,7 @@ appendReference[type_, name_String, pos_:Nothing] := With[ newKey = (Length[$ReferenceTable] + 1) }, - AppendTo[$ReferenceTable, newKey -> ( + AssociateTo[$ReferenceTable, newKey -> ( {type, name, pos} )]; newKey @@ -51,7 +51,7 @@ appendReference[type_, name_String, pos_:Nothing] := With[ GetContextsReferences[] := ( - $ReferenceTable = <| 1 -> {} |>; + AssociateTo[$ReferenceTable, 1 -> {}]; Table[ Scope[<| "name" -> context, diff --git a/src/WolframLanguageServer/Server.wl b/src/WolframLanguageServer/Server.wl index bb07909..98f3792 100644 --- a/src/WolframLanguageServer/Server.wl +++ b/src/WolframLanguageServer/Server.wl @@ -789,7 +789,7 @@ handleDapMessage[msg_Association, state_WorkState] := Module[ newState = state }, - LogDebug["handleDapMessage" <> msg]; + LogDebug["handleDapMessage: " <> ToString[msg]]; Which[ (* wrong message before initialization *) From cf4edc126819582528fb029bc845febe9dc54b51 Mon Sep 17 00:00:00 2001 From: kenkangxgwe Date: Sat, 29 Aug 2020 15:28:42 -0700 Subject: [PATCH 32/32] :memo: Update CHANGELOG for 0.3.0 --- CHANGELOG.md | 12 +++++++++++- README.md | 4 +++- src/WolframLanguageServer/Server.wl | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 896b9d5..1a04acc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.0] - 2021-01-26 ๐Ÿงจ๐Ÿ‚ + +### Added + +- Support for debugger adapter. Code can be evaluated and symbols can be +shown in the editor. + +- CodeAction to show Documentation for system symbols (Thanks @wuyudi for +the help with testing) + ## [0.2.2] - 2020-08-01 ๐Ÿฑโ€๐Ÿ ### Added @@ -94,7 +104,7 @@ error will not popup in Output window in VSCode (reported by - SVG image for document information -## [0.1.1] - 2019-02-05 ๐Ÿงง +## [0.1.1] - 2019-02-05 ๐Ÿงง๐Ÿ– ### Added diff --git a/README.md b/README.md index fea01af..350fa69 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,9 @@ Block[{$ScriptCommandLine = Prepend[args, initfile], Quit = Function[{}, Throw[N ]; ``` -This is a good way to see the results from the unit tests. +To use the debugger adapter (current in a very early stage), you need to include +`debuggerPort` in the `initializationOptions`. And attach the frontend to that +port. For VS Code, it is automatically done by the extension. ## Language Server Features diff --git a/src/WolframLanguageServer/Server.wl b/src/WolframLanguageServer/Server.wl index 98f3792..98380c4 100644 --- a/src/WolframLanguageServer/Server.wl +++ b/src/WolframLanguageServer/Server.wl @@ -863,7 +863,7 @@ scheduleDelayedRequest[method_String, msg_, state_WorkState] := ( ServerConfig["requestDelays"][method], "Second" }], - "id" -> msg["id"], + "id" -> (msg["id"] // Replace[Except[_Integer] -> Missing["NoIdNeeded"]]), "params" -> getScheduleTaskParameter[method, msg, state], "callback" -> (handleRequest[method, msg, #1]&) |>]]