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/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/README.md b/README.md
index 0661f39..b3d1aef 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=master)](https://github.com/kenkangxgwe/lsp-wl/actions?query=workflow%3ACI+branch%3Amaster)
![WolframLanguageServerLogo](images/wolfram-language-server-logo-clipped.png)
-> by [kenkangxgwe](https://github.com/kenkangxgwe) and [hxianglong](https://github.com/huxianglong)
**Table of Contents**
@@ -12,7 +12,19 @@
- [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)
+ - [Diagnostics](#diagnostics)
+ - [Definition / References / Document Highlight](#definition--references--document-highlight)
+ - [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)
@@ -24,17 +36,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 +60,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 or
+later) from the Wolfram kernel / Mathematica.
(_This will cost some time for the first time_) :
``` mathematica
PacletInstall["CodeParser"]
@@ -56,11 +69,14 @@ 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)
+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 for updates.
+[GitLink](https://github.com/WolframResearch/GitLink) packet in order to check
+for updates.
## Run the Server
@@ -78,11 +94,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.
@@ -95,89 +111,148 @@ 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.
-## Features
+## Language Server 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](images/hover.png)
+
+### 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.
+
+![completion-unicode](images/completion_alias.png)
+
+**Completion Resolve:** Further information (such as documentation) would be
+provided for the items in the list.
+
+![completion](images/completion.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 `*^`.
+### Diagnostics
- ![hover](images/hover.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).
-- **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.
+![diagnostics](images/diagnostics.png)
- ![completion-unicode](images/completion_alias.png)
+### Definition / References / Document Highlight
-- **Completion Resolve:** Further information (such as documentation) would be
- provided for the items in the list.
+It is now able to look up the definition and references of a local variable in a
+scope such as `Module` or pattern rules.
- ![completion](images/completion.png)
+![references](images/references.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).
+### Code Action
- ![diagnostics](images/diagnostics.png)
+Code action is now able to,
-- **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.
+- Open the documentation of system symbols in Mathematica (Not available for
+ Wolfram Engine).
+ ![documentation](images/codeActionSymbolDocumentation.png)
- ![references](images/references.png)
+- Evaluate the selected code if debugger is running. See [Evaluate](#evaluate).
-- **Document Color / Color Presentation:** Both Named Colors and
- Color Models with constant parameters are able to show and modify.
- (_Experimental, may have performance issues._)
+### Document Color / Color Presentation
- ![documentColor](images/documentColor.png)
+Both Named Colors and
+Color Models with constant parameters are able to show and modify.
+(_Experimental, may have performance issues._)
-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.
+![documentColor](images/documentColor.png)
-Here is a full list of [LSP features](https://microsoft.github.io/language-server-protocol/specification).
+### 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).
+
+## 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
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 +263,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 +272,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 +285,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/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"
],
diff --git a/images/codeActionSymbolDocumentation.png b/images/codeActionSymbolDocumentation.png
new file mode 100644
index 0000000..c17704b
Binary files /dev/null and b/images/codeActionSymbolDocumentation.png differ
diff --git a/images/evaluate_code_action.png b/images/evaluate_code_action.png
new file mode 100644
index 0000000..18865f4
Binary files /dev/null and b/images/evaluate_code_action.png differ
diff --git a/images/evaluate_debug_console.png b/images/evaluate_debug_console.png
new file mode 100644
index 0000000..668cd1b
Binary files /dev/null and b/images/evaluate_debug_console.png differ
diff --git a/images/variables.png b/images/variables.png
new file mode 100644
index 0000000..9df0bd3
Binary files /dev/null and b/images/variables.png differ
diff --git a/init.wls b/init.wls
index 1e383f1..fc0c0f2 100644
--- a/init.wls
+++ b/init.wls
@@ -1,21 +1,23 @@
#!/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]
+If[$OperatingSystem == "Windows" && $FrontEnd === Null,
+ SetOptions[$Output, FormatType -> OutputForm]
+]
+
(* ::Subsection:: *)
(*RootDirectory*)
@@ -95,6 +97,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)
@@ -115,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[];
];
@@ -211,12 +214,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/PatternTemplate.wl b/src/PatternTemplate.wl
index 915cbd3..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`"]
@@ -57,7 +60,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/Adaptor.wl b/src/WolframLanguageServer/Adaptor.wl
new file mode 100644
index 0000000..d5db6e6
--- /dev/null
+++ b/src/WolframLanguageServer/Adaptor.wl
@@ -0,0 +1,120 @@
+(* ::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]
+)
+
+
+DebuggerEvaluate[evaluateArguments_Association, kernel_KernelObject] := (
+ 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]; #)&
+ ]
+)
+
+
+End[]
+
+
+EndPackage[]
\ No newline at end of file
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/Debugger.wl b/src/WolframLanguageServer/Debugger.wl
new file mode 100644
index 0000000..b9af83a
--- /dev/null
+++ b/src/WolframLanguageServer/Debugger.wl
@@ -0,0 +1,339 @@
+(* ::Package:: *)
+
+(* Copyright 2020 lsp-wl Authors *)
+(* SPDX-License-Identifier: MIT *)
+
+
+(* Wolfram Language Server Debugger *)
+
+
+BeginPackage["WolframLanguageServer`Debugger`"]
+ClearAll[Evaluate[Context[] <> "*"]]
+
+
+(* Output Symbols *)
+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`"]
+ClearAll[Evaluate[Context[] <> "*"]]
+
+
+Needs["DataType`"]
+Needs["WolframLanguageServer`Logger`"]
+Needs["WolframLanguageServer`Specification`"]
+
+
+$ExpensiveContexts = {"System`"}
+
+
+DebuggerInit[] := (
+ $SymbolTable = <|"Global`" -> {}|>;
+ $ReferenceTable = <||>;
+ $NewSymbol = (($SymbolTable =
+ Merge[{$SymbolTable, <|#2 -> #1|>}, Flatten]
+ )&)
+)
+
+
+appendReference[type_, name_String, pos_:Nothing] := With[
+ {
+ newKey = (Length[$ReferenceTable] + 1)
+ },
+
+ AssociateTo[$ReferenceTable, newKey -> (
+ {type, name, pos}
+ )];
+ newKey
+]
+
+
+GetContextsReferences[] := (
+ AssociateTo[$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]}
+ ]
+)
+
+
+(* 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]
+ ]
+ }
+ ]
+ ],
+ _ -> {}
+ }]
+)
+
+
+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[#]&
+)
+
+
+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
+ }]
+)
+
+
+createVariableWithTag[tag_String, symbolName_String, expr_, nextPos_] := (
+ expr
+ // Unevaluated
+ // analyzeExpr
+ // Apply[{value, type, length} \[Function] (
+ DapVariable[<|
+ "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
+ |>]
+ )]
+)
+
+
+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
+ ]
+)
+
+
+End[]
+
+
+EndPackage[]
\ No newline at end of file
diff --git a/src/WolframLanguageServer/Logger.wl b/src/WolframLanguageServer/Logger.wl
index e3fe62e..d0839ee 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`"];
@@ -31,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 07e8220..98380c4 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`"];
@@ -26,14 +27,17 @@ Needs["WolframLanguageServer`Logger`"]
Needs["WolframLanguageServer`Specification`"]
Needs["WolframLanguageServer`TextDocument`"]
Needs["WolframLanguageServer`Token`"]
+Needs["WolframLanguageServer`Adaptor`"]
(* ::Section:: *)
(*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:: *)
@@ -45,11 +49,21 @@ DeclareType[WorkState, <|
"openedDocs" -> _Association, (* (_DocumentUri -> _DocumentText)... *)
"client" -> (_SocketClient | _SocketObject | _NamedPipe | _StdioClient | "stdio" | Null),
"clientCapabilities" -> _Association,
+ "debugSession" -> _DebugSession,
"scheduledTasks" -> {___ServerTask},
"caches" -> _Association,
+ "pendingServerRequests" -> _Association,
"config" -> _Association
|>]
+DeclareType[DebugSession, <|
+ "initialized" -> _?BooleanQ,
+ "server" -> _SocketObject | Null,
+ "client" -> _SocketObject | Null,
+ "subKernel" -> _,
+ "context" -> _String,
+ "thread" -> _DapThread
+|>]
DeclareType[RequestCache, <|
"cachedTime" -> _DateObject,
@@ -69,8 +83,14 @@ InitialState = WorkState[<|
"initialized" -> False,
"openedDocs" -> <||>,
"client" -> Null,
+ "debugSession" -> DebugSession[<|
+ "initialized" -> False,
+ "server" -> Null,
+ "client" -> Null
+ |>],
"scheduledTasks" -> {},
"caches" -> initialCaches,
+ "pendingServerRequests" -> <||>,
"config" -> <|
"configFileConfig" -> loadConfig[]
|>
@@ -89,19 +109,25 @@ ServerCapabilities = <|
"definitionProvider" -> True,
"referencesProvider" -> True,
"documentSymbolProvider" -> True,
+ "codeActionProvider" -> True,
"documentHighlightProvider" -> True,
+ "codeLensProvider" -> <|
+ "resolveProvider" -> True
+ |>,
"colorProvider" -> True,
- (* "executeCommandProvider" -> <|
+ "executeCommandProvider" -> <|
"commands" -> {
- "openRef"
+ "openRef",
+ "dap-wl.evaluate-file",
+ "dap-wl.evaluate-range"
}
- |>, *)
+ |>,
Nothing
|>
ServerConfig = <|
"updateCheckInterval" -> Quantity[7, "Days"],
- (* cached results *)
+ (* cached results *)
"cachedRequests" -> {
"textDocument/signatureHelp",
"textDocument/documentSymbol",
@@ -388,13 +414,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[debugSession["server"]["ConnectedClients"]]]
+ },
+ debugSession["client"] =!= Null && SocketReadyQ[debugSession["client"]],
+ handleDapMessageList[ReadMessages[debugSession["client"]], state],
+ True,
doNextScheduledTask[state]
]
// Replace[{
@@ -449,7 +488,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]],
@@ -635,7 +674,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
@@ -648,7 +687,9 @@ constructRPCBytes[msg_Association] := (
"InternalError",
"The request is not handled correctly."
]],
- "RawJSON"]
+ "RawJSON",
+ "Compact" -> True
+ ]
] // {
(* header *)
Length
@@ -692,10 +733,11 @@ 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", _}]]
-);
+)
handleMessage[msg_Association, state_WorkState] := With[
{
@@ -706,19 +748,22 @@ 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},
(* notification*)
NotificationQ[msg],
handleNotification[method, msg, state],
+ (* response *)
+ ResponseQ[msg],
+ handleResponse[state["pendingServerRequests"][msg["id"]], msg, state],
(* resquest *)
True,
Which[
@@ -732,7 +777,47 @@ 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
+ },
+
+ LogDebug["handleDapMessage: " <> ToString[msg]];
+
+ Which[
+ (* wrong message before initialization *)
+ !state["debugSession"]["Initialized"] &&
+ msg["type"] != "request" &&
+ !MemberQ[{"initialize"}, msg["command"]],
+ If[msg["type"] == "request",
+ sendMessage[state["dubugSession"]["client"], DapResponse[<|
+ "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},
+ (* event*)
+ msg["type"] == "event",
+ handleDapEvent[msg["event"], msg, newState],
+ (* resquest *)
+ True,
+ handleDapRequest[msg["command"], msg, newState]
+ ]
+]
@@ -745,17 +830,21 @@ 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]
},
- sendResponse[state["client"],
- <|"id" -> msg["id"]|>
- // Append[
- If[!MissingQ[cache["result"]],
- "result" -> cache["result"],
- "error" -> cache["error"]
+ sendMessage[state["client"],
+ ResponseMessage[<|"id" -> msg["id"]|>]
+ // ReplaceKey[
+ If[MissingQ[cache],
+ (* File closed, sends Null. *)
+ "result" -> Null,
+ If[!MissingQ[cache["result"]],
+ "result" -> cache["result"],
+ "error" -> cache["error"]
+ ]
]
]
];
@@ -774,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]&)
|>]]
@@ -782,9 +871,20 @@ scheduleDelayedRequest[method_String, msg_, state_WorkState] := (
)
-(* response, notification and request will call this function *)
-sendResponse[client_, res_Association] := (
- Prepend[res, <|"jsonrpc" -> "2.0"|>]
+(* For LSP: response, notification and request *)
+sendMessage[client_, res:(_RequestMessage|_ResponseMessage|_NotificationMessage)] := (
+ res
+ // ReplaceKey["jsonrpc" -> "2.0"]
+ // ToAssociation
+ // constructRPCBytes
+ // WriteMessage[client]
+)
+
+(* For DAP: event and response *)
+sendMessage[client_, res:(_DapEvent|_DapResponse)] := (
+ res
+ // LogDebug
+ // ToAssociation
// constructRPCBytes
// WriteMessage[client]
)
@@ -794,27 +894,33 @@ sendResponse[client_, res_Association] := (
(*initialize*)
-handleRequest["initialize", msg_, state_WorkState] := Module[
- {
- newState = state
- },
+handleRequest["initialize", msg_, state_WorkState] := With[
+ {
+ debugPort = Fold[Replace[#1, _?MissingQ -> <||>][#2]&,
+ msg, {"params", "initializationOptions", "debuggerPort"}
+ ]
+ },
- LogDebug["handle/initialize"];
-
- (* Check Client Capabilities *)
- newState = ReplaceKey[state,
- "clientCapabilities" -> msg["params"]["capabilities"]
- ];
-
- sendResponse[state["client"], <|
+ 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"],
+ If[!MissingQ[debugPort],
+ LogInfo["Debugger listening at port " <> ToString[debugPort]];
+ {"debugSession", "server"} -> SocketOpen[debugPort],
+ Nothing
+ ]
+ }]
+ }
+]
(* ::Subsection:: *)
@@ -825,10 +931,10 @@ handleRequest["shutdown", msg_, state_] := Module[
{
},
- sendResponse[state["client"], <|
+ sendMessage[state["client"], ResponseMessage[<|
"id" -> msg["id"],
"result" -> Null
- |>];
+ |>]];
{"Continue", state}
];
@@ -844,19 +950,159 @@ handleRequest["workspace/executeCommand", msg_, state_] := With[
args = msg["params"]["arguments"]
},
- Replace[command, {
- "dap-wl.runfile" -> (
- LogInfo[StringJoin["executing ", command, "with arguments: ", ToString[args]]]
- )
- }];
+ 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.evaluate-file*)
+
+
+executeCommand["dap-wl.evaluate-file", msg_, state_WorkState] := With[
+ {
+ args = msg["params"]["arguments"] // First
+ },
+
+ 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 *)
+ |>
+ |>]];
- sendResponse[state["client"], <|
+ sendMessage[state["client"], ResponseMessage[<|
"id" -> msg["id"],
"result" -> Null
- |>];
+ |>]];
{"Continue", state}
+]
+
+
+(* ::Subsubsection:: *)
+(*dap-wl.evaluate-range*)
+
+
+executeCommand["dap-wl.evaluate-range", msg_, state_WorkState] := Block[
+ {
+ args = msg["params"]["arguments"] // First,
+ text
+ },
+
+ 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[#, ConstructType[args["range"], _LspRange]]&
+ // StringTrim
+ // (LogDebug["Evaluating " <> #]; #)&;
+
+ sendMessage[state["debugSession"]["client"], DapEvent[<|
+ "type" -> "event",
+ "event" -> "output",
+ "body" -> <|
+ (* "category" -> "stdout", *)
+ "output" -> (
+ DebuggerEvaluate[
+ <|"expression" -> text|>,
+ state["debugSession"]["subKernel"]
+ ]
+ // (# <> "\n")&
+ )
+ |>
+ |>]];
+
+ 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"],
+ "result" -> Null
+ |>]];
+ {"Continue", state}
]
@@ -865,25 +1111,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}
)
@@ -899,15 +1145,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"]]
},
- sendResponse[state["client"], <|
+ sendMessage[state["client"], ResponseMessage[<|
"id" -> msg["id"],
- "result" -> ToAssociation[GetHoverAtPosition[doc, pos]]
- |>];
-
+ "result" -> GetHoverAtPosition[
+ state["openedDocs"][uri],
+ pos
+ ]
+ |>]];
{"Continue", state}
]
@@ -931,19 +1179,22 @@ cacheResponse[method:"textDocument/signatureHelp", msg_, state_WorkState] := Wit
},
state
- // ReplaceKey[
- {"caches", method, uri} -> RequestCache[<|
- "cachedTime" -> Now,
- "result" -> (
- GetSignatureHelp[state["openedDocs"][uri], pos]
- // ToAssociation
- )
- |>]
+ // If[MissingQ[state["openedDocs"][uri]],
+ Identity,
+ ReplaceKey[
+ {"caches", method, uri} -> RequestCache[<|
+ "cachedTime" -> Now,
+ "result" -> GetSignatureHelp[
+ state["openedDocs"][uri],
+ pos
+ ]
+ |>]
+ ]
]
]
-cacheAvailableQ[method:"textDocument/signatureHelp", msg_, state_WorkState] := Block[
+cacheAvailableQ[method:"textDocument/signatureHelp", msg_, state_WorkState] := With[
{
cachedTime = getCache[method, msg, state]["cachedtime"]
},
@@ -966,44 +1217,47 @@ 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"]]
},
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[
+ state["openedDocs"][uri],
+ pos
+ ]
|>
- |>]
+ |>]]
),
CompletionTriggerKind["TriggerCharacter"] :> (
- sendResponse[state["client"], <|
+ sendMessage[state["client"], ResponseMessage[<|
"id" -> msg["id"],
"result" -> <|
"isIncomplete" -> True,
- "items" -> (
- GetTriggerKeyCompletion[doc, pos]
- // ToAssociation
- )
+ "items" -> GetTriggerKeyCompletion[
+ state["openedDocs"][uri],
+ pos
+ ]
|>
- |>]
+ |>]]
),
CompletionTriggerKind["TriggerForIncompleteCompletions"] :> (
- sendResponse[state["client"], <|
+ sendMessage[state["client"], ResponseMessage[<|
"id" -> msg["id"],
"result" -> <|
"isIncomplete" -> False,
- "items" -> (
- GetIncompleteCompletionAtPosition[doc, pos]
- // ToAssociation
- )
+ "items" -> GetIncompleteCompletionAtPosition[
+ state["openedDocs"][uri],
+ pos
+ ]
|>
- |>];
+ |>]];
)
}];
@@ -1030,13 +1284,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"]
@@ -1047,7 +1301,7 @@ handleRequest["completionItem/resolve", msg_, state_] := With[
|>
]
|>
- |>]
+ |>]]
)
}];
@@ -1066,14 +1320,17 @@ 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"]]
},
- sendResponse[state["client"], <|
+ sendMessage[state["client"], ResponseMessage[<|
"id" -> msg["id"],
- "result" -> ToAssociation@FindDefinitions[doc, pos]
- |>] // AbsoluteTiming // First // LogDebug;
+ "result" -> FindDefinitions[
+ state["openedDocs"][uri],
+ pos
+ ]
+ |>]];
{"Continue", state}
]
@@ -1085,15 +1342,19 @@ 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"]
},
- sendResponse[state["client"], <|
+ sendMessage[state["client"], ResponseMessage[<|
"id" -> msg["id"],
- "result" -> ToAssociation@FindReferences[doc, pos, "IncludeDeclaration" -> includeDeclaration]
- |>] // AbsoluteTiming // First // LogDebug;
+ "result" -> FindReferences[
+ state["openedDocs"][uri],
+ pos,
+ "IncludeDeclaration" -> includeDeclaration
+ ]
+ |>]];
{"Continue", state}
]
@@ -1105,18 +1366,17 @@ 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"]]
},
- sendResponse[state["client"], <|
- "id" -> id,
- "result" -> (
- FindDocumentHighlight[state["openedDocs"][uri], pos]
- // ToAssociation
- )
- |>];
+ sendMessage[state["client"], ResponseMessage[<|
+ "id" -> msg["id"],
+ "result" -> FindDocumentHighlight[
+ state["openedDocs"][uri],
+ pos
+ ]
+ |>]];
{"Continue", state}
]
@@ -1144,20 +1404,19 @@ cacheResponse[method:"textDocument/documentSymbol", msg_, state_WorkState] := Wi
},
state
- // ReplaceKey[
- {"caches", method, uri} -> RequestCache[<|
- "cachedTime" -> Now,
- "result" -> (
- state["openedDocs"][uri]
- // ToDocumentSymbol
- // ToAssociation
- )
- |>]
+ // If[MissingQ[state["openedDocs"][uri]],
+ Identity,
+ ReplaceKey[
+ {"caches", method, uri} -> RequestCache[<|
+ "cachedTime" -> Now,
+ "result" -> ToDocumentSymbol[state["openedDocs"][uri]]
+ |>]
+ ]
]
]
-cacheAvailableQ[method:"textDocument/documentSymbol", msg_, state_WorkState] := Block[
+cacheAvailableQ[method:"textDocument/documentSymbol", msg_, state_WorkState] := With[
{
cachedTime = getCache[method, msg, state]["cachedtime"]
},
@@ -1178,6 +1437,138 @@ getCache[method:"textDocument/documentSymbol", msg_, state_WorkState] := (
)
+(* ::Subsection:: *)
+(*textDocument/codeAction*)
+
+
+handleRequest["textDocument/codeAction", msg_, state_] := With[
+ {
+ uri = msg["params"]["textDocument"]["uri"],
+ range = ConstructType[msg["params"]["range"], LspRange]
+ },
+
+ sendMessage[state["client"], ResponseMessage[<|
+ "id" -> msg["id"],
+ "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}
+]
+
+
+(* ::Subsection:: *)
+(*textDocuent/codeLens*)
+
+
+handleRequest["textDocument/codeLens", msg_, state_] := With[
+ {
+ id = msg["id"],
+ uri = msg["params"]["textDocument"]["uri"]
+ },
+
+ sendMessage[state["client"], ResponseMessage[<|
+ "id" -> id,
+ "result" -> (
+ If[state["debugSession"]["initialized"],
+ {
+ CodeLens[<|
+ "range" -> <|
+ "start" -> <|
+ "line" -> 0,
+ "character" -> 0
+ |>,
+ "end" -> <|
+ "line" -> 0,
+ "character" -> 0
+ |>
+ |>,
+ "data" -> <|
+ "title" -> "$(workflow) Evaluate File",
+ "command" -> "dap-wl.evaluate-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.evaluate-range",
+ "arguments" -> {<|
+ "uri" -> uri,
+ "range" -> codeRange
+ |>}
+ |>
+ |>],
+ {
+ codeRange,
+ state["openedDocs"][uri]
+ // FindAllCodeRanges
+ }
+ ]
+ },
+ {}
+ ]
+ // Flatten
+ )
+ |>]];
+
+ {"Continue", state}
+
+]
+
+
+(* ::Subsection:: *)
+(*codeLens/resolve*)
+
+
+handleRequest["codeLens/resolve", msg_, state_] := With[
+ {
+ id = msg["id"],
+ codeLens = msg["params"]
+ },
+
+ sendMessage[state["client"], ResponseMessage[<|
+ "id" -> id,
+ "result" -> (
+ ConstructType[codeLens, CodeLens]
+ // ReplaceKey[#, "command" -> #["data"]]&
+ )
+ |>]];
+
+ {"Continue", state}
+]
+
+
(* ::Subsection:: *)
(*textDocument/documentColor*)
@@ -1195,20 +1586,19 @@ cacheResponse[method:"textDocument/documentColor", msg_, state_WorkState] := Wit
},
state
- // ReplaceKey[
- {"caches", method, uri} -> RequestCache[<|
- "cachedTime" -> Now,
- "result" -> (
- state["openedDocs"][uri]
- // FindDocumentColor
- // ToAssociation
- )
- |>]
+ // If[MissingQ[state["openedDocs"][uri]],
+ Identity,
+ ReplaceKey[
+ {"caches", method, uri} -> RequestCache[<|
+ "cachedTime" -> Now,
+ "result" -> FindDocumentColor[state["openedDocs"][uri]]
+ |>]
+ ]
]
]
-cacheAvailableQ[method:"textDocument/documentColor", msg_, state_WorkState] := Block[
+cacheAvailableQ[method:"textDocument/documentColor", msg_, state_WorkState] := With[
{
cachedTime = getCache[method, msg, state]["cachedTime"]
},
@@ -1237,17 +1627,14 @@ 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"], <|
+ sendMessage[state["client"], ResponseMessage[<|
"id" -> msg["id"],
- "result" -> (
- GetColorPresentation[doc, color, range]
- // ToAssociation
- )
- |>];
+ "result" -> GetColorPresentation[doc, color, range]
+ |>]];
{
"Continue",
@@ -1265,14 +1652,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}
)
@@ -1333,13 +1720,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])]
),
@@ -1390,14 +1777,15 @@ handleNotification["textDocument/didClose", msg_, state_] := With[
uri = msg["params"]["textDocument"]["uri"]
},
- LogDebug @ ("Close Document " <> uri);
+ LogInfo @ ("Close Document " <> uri);
state
// ReplaceKeyBy[{"openedDocs"} -> KeyDrop[uri]]
// 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, #]&
]
@@ -1501,6 +1889,376 @@ handleNotification[_, msg_, state_] := (
)
+(* ::Section:: *)
+(*Send Request*)
+
+
+$requestId = 0
+getRequestId[] := (
+ "req_" <> ToString[($requestId += 1)]
+)
+
+
+(* ::Subsection:: *)
+(*workspace/applyEdit*)
+
+
+sendRequest[method:"workspace/applyEdit", msg_, state_WorkState] := With[
+ {
+ id = getRequestId[]
+ },
+
+ sendMessage[state["client"], RequestMessage[<|
+ "id" -> id,
+ "method" -> method,
+ "params" -> <|
+ "edit" -> msg["params"]["edit"]
+ |>
+ |>]];
+
+ {
+ "Continue",
+ state
+ // ReplaceKeyBy["pendingServerRequests" -> Append[id -> method]]
+ }
+]
+
+
+(* ::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[_, msg_, state_WorkState] := (
+ {
+ "Continue",
+ state
+ // DeleteKey[{"pendingServerRequests", msg["id"]}]
+ }
+)
+
+
+(* ::Section:: *)
+(*Handle Dap Request*)
+
+
+(* ::Subsection:: *)
+(*initialize*)
+
+
+handleDapRequest["initialize", msg_, state_WorkState] := Block[
+ {
+ subKernel = CreateDebuggerKernel[]
+ },
+
+ 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
+ |>
+ |>]];
+
+ sendMessage[state["debugSession"]["client"], DapEvent[<|
+ "type" -> "event",
+ "event" -> "initialized"
+ |>]];
+
+ state
+ // ReplaceKeyBy["debugSession" -> (
+ Fold[ReplaceKey, #, {
+ "initialized" -> True,
+ "subKernel" -> subKernel,
+ "context" -> context,
+ "thread" -> DapThread[<|
+ "id" -> GetKernelId[subKernel],
+ "name" -> StringJoin[
+ "SubKernel ",
+ GetProcessId[subKernel]
+ // ToString
+ ]
+ |>],
+ "symbolTable" -> <||>
+ }]&)]
+ // sendRequest["workspace/codeLens/refresh", #]&
+]
+
+
+(* ::Subsection:: *)
+(*configurationDone*)
+
+
+handleDapRequest["configurationDone", msg_, state_WorkState] := (
+
+ LogInfo["Configuration Done for Wolfram Language Debugger"];
+
+ sendMessage[state["debugSession"]["client"], DapResponse[<|
+ "type" -> "response",
+ "request_seq" -> msg["seq"],
+ "success" -> True,
+ "command" -> msg["command"],
+ "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}
+)
+
+
+(* ::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" -> <||>
+ |>]];
+
+ 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]
+ // sendRequest["workspace/codeLens/refresh", #]&
+)
+
+
+(* ::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" -> GetThreads[
+ state["debugSession"]["subKernel"]
+ ]
+ |>
+ |>]];
+
+ {"Continue", state}
+)
+
+
+(* ::Subsection:: *)
+(*stackTrace*)
+
+
+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}
+)
+
+
+(* ::Subsection:: *)
+(*scopes*)
+
+
+handleDapRequest["scopes", msg_, state_WorkState] := (
+ sendMessage[state["debugSession"]["client"], DapResponse[<|
+ "type" -> "response",
+ "request_seq" -> msg["seq"],
+ "success" -> True,
+ "command" -> msg["command"],
+ "body" -> <|
+ "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"]
+ ],
+ "variablesReference" -> 0
+ |>
+ |>]];
+
+ {"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}
+)
+
+
(* ::Section:: *)
(*Send Message*)
@@ -1514,33 +2272,29 @@ MessageType = <|
|>
showMessage[message_String, msgType_String, state_WorkState] := (
- MessageType[msgType]
- // Replace[_?MissingQ :> MessageType["Error"]]
- // LogDebug
- // (type \[Function]
- sendResponse[state["client"], <|
- "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]
- sendResponse[state["client"], <|
- "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
+ |>
+ |>]]
)
@@ -1548,7 +2302,7 @@ logMessage[message_String, msgType_String, state_WorkState] := (
(*Handle Error*)
-ServerError[errorType_String, msg_String] := (
+ServerError[errorType_String, msg_String] := ResponseError[
<|
"code" -> (
errorType
@@ -1560,11 +2314,12 @@ ServerError[errorType_String, msg_String] := (
),
"message" -> msg
|>
-)
+]
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"]
|>
@@ -1606,7 +2361,7 @@ doNextScheduledTask[state_WorkState] := (
Pause[0.001];
{"Continue", state}
),
- task_ServerTask :> Block[
+ task_ServerTask :> With[
{
newState = state // ReplaceKeyBy["scheduledTasks" -> Rest]
},
@@ -1615,22 +2370,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
@@ -1651,17 +2412,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 *)
@@ -1669,13 +2430,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}
]
}]
@@ -1730,119 +2491,131 @@ 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.*"}
+ }
+ },
+
+ 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]&
+ )
+ ]
+ ]
+]
- With[{repo = GitLink`GitOpen[WolframLanguageServer`RootDirectory]},
- If[GitLink`GitProperties[repo, "HeadBranch"] != "master",
+
+(* ::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."];
+
End[];
diff --git a/src/WolframLanguageServer/Specification.wl b/src/WolframLanguageServer/Specification.wl
index 5e134d0..9bbf310 100644
--- a/src/WolframLanguageServer/Specification.wl
+++ b/src/WolframLanguageServer/Specification.wl
@@ -1,37 +1,62 @@
(* ::Package:: *)
+(* Copyright 2018 lsp-wl Authors *)
+(* SPDX-License-Identifier: MIT *)
+
+
(* Wolfram Language Server Specification *)
-(* Author: kenkangxgwe ,
- huxianglong
-*)
BeginPackage["WolframLanguageServer`Specification`"]
ClearAll[Evaluate[Context[] <> "*"]];
+(* ::Section:: *)
+(* Language Server Protocol*)
+
+
+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."
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."
+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."
-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."
+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."
+
+(* ::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."
+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:: *)
(*Type Aliases*)
@@ -42,6 +67,27 @@ DocumentUri = String
(* ::Section:: *)
(*Enum Type*)
+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
+|>
+
+MarkupKind = <|
+ "PlainText" -> "plaintext",
+ "Markdown" -> "markdown"
+|>
+
TextDocumentSyncKind = <|
"None" -> 0,
"Full" -> 1,
@@ -55,35 +101,16 @@ DiagnosticSeverity = <|
"Hint" -> 4
|>
-
-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
-|>
-
-InsertTextFormat = <|
- "PlainText" -> 1,
- "Snippet" -> 2
-|>
-
-
CompletionTriggerKind = <|
"Invoked" -> 1,
"TriggerCharacter" -> 2,
"TriggerForIncompleteCompletions" -> 3
|>
+InsertTextFormat = <|
+ "PlainText" -> 1,
+ "Snippet" -> 2
+|>
CompletionItemKind = <|
"Text" -> 1,
@@ -113,7 +140,6 @@ CompletionItemKind = <|
"TypeParameter" -> 25
|>
-
SymbolKind = <|
"File" -> 1,
"Module" -> 2,
@@ -143,18 +169,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*)
@@ -169,7 +199,45 @@ Needs["DataType`"]
(* ::Section:: *)
-(*Server Communication Related Type*)
+(*Language Server Types*)
+
+
+(* ::Subsection:: *)
+(*Basic Protocol*)
+
+
+DeclareType[RequestMessage, <|
+ "jsonrpc" -> _String,
+ "id" -> _Integer | _String,
+ "method" -> _String,
+ "params" -> _
+|>]
+
+DeclareType[ResponseMessage, <|
+ "jsonrpc" -> _String,
+ "id" -> _Integer | _String,
+ "result" -> _,
+ "error" -> _ResponseError
+|>]
+
+
+DeclareType[ResponseError, <|
+ "code" -> _Integer,
+ "message" -> _String,
+ "data" -> _
+|>]
+
+
+DeclareType[NotificationMessage, <|
+ "jsonrpc" -> _String,
+ "method" -> _String,
+ "params" -> _
+|>]
+
+
+(* ::Subsection:: *)
+(*Basic Structures*)
+
DeclareType[LspPosition, <|
"line" -> _Integer,
@@ -181,16 +249,29 @@ 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
|>]
+DeclareType[WorkspaceEdit, <|
+ "changes" -> <|
+ (_DocumentUri -> TextEdit)...
+ |>,
+ "documentChanges" -> _ (* not implemented *)
+|>]
+
DeclareType[TextDocumentItem, <|
"uri" -> _DocumentUri,
"languageId" -> _String,
@@ -198,15 +279,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,49 +357,28 @@ DeclareType[ParameterInformation, <|
DeclareType[DocumentSymbol, <|
"name" -> _String,
"detail" -> _String,
- "kind" -> _Integer,
+ "kind" -> _?(MemberQ[SymbolKind, #]&),
"deprecated" -> _?BooleanQ,
"range" -> _LspRange,
"selectionRange" -> _LspRange,
"children" -> {___DocumentSymbol}
|>]
-DeclareType[Diagnostic, <|
+DeclareType[DocumentHighlight, <|
"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}
+ "kind" -> _Integer
|>]
-DeclareType[Location, <|
- "uri" -> DocumentUri,
- "range" -> _LspRange
+DeclareType[LspCodeAction, <|
+ "title" -> _String,
+ "kind" -> _?(MemberQ[CodeActionKind, #]&),
+ "command" -> _Command
|>]
-DeclareType[DocumentHighlight, <|
+DeclareType[CodeLens, <|
"range" -> _LspRange,
- "kind" -> _Integer
+ "command" -> _Command,
+ "data" -> _
|>]
DeclareType[ColorInformation, <|
@@ -297,6 +399,57 @@ 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
+|>]
+
+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[]
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 0c7b59f..5904648 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`"]
@@ -23,6 +24,9 @@ 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."
+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."
@@ -38,6 +42,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*)
@@ -70,10 +93,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]
@@ -104,6 +127,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[{
@@ -148,7 +174,7 @@ ChangeTextDocument[doc_TextDocument, contextChange_TextDocumentContentChangeEven
(* ::Section:: *)
-(*CodeCells*)
+(*Helper Function*)
(* ::Subsection:: *)
@@ -165,51 +191,86 @@ 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,
AdditionalStyle["File"],
Part[doc["text"], styleLine]
- // StringCases["(* "~~"::"~~Shortest[style___]~~"::"~~" *)" :> style]
+ // StringCases["(* " ~~ "::" ~~ Shortest[style___] ~~ "::" ~~ " *)" :> style]
+ // First
+ // StringSplit[#, "::"]&
// First
// 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[<|
@@ -244,10 +305,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] := (
@@ -266,19 +329,20 @@ InsertCell[rootCell_CellNode, nextCell_CellNode] := (
rootCell
// ReplaceKeyBy[{"children", -1} -> (InsertCell[#, nextCell]&)],
rootCell
+ // If[Length[rootCell["children"]] > 0,
+ ReplaceKeyBy[{"children", -1} -> TerminateCell],
+ Identity
+ ]
// 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
@@ -302,15 +366,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
]
)]
))
@@ -334,10 +394,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}]
@@ -349,14 +467,11 @@ CellToAST[doc_TextDocument, {startLine_, endLine_}] := (
{StringRepeat::intp (* before 12.0 *)}
] // Quiet,
#]&
- // CodeParser`CodeParse
- // Part[#, 2]&
)
-CellContainsLine[indexLine_Integer][cell_CellNode] := (
- indexLine // Between[cell["range"]]
-)
+(* ::Subsection:: *)
+(*GetAtPosition*)
GetCodeRangeAtPosition[doc_TextDocument, pos_LspPosition] := With[
@@ -364,28 +479,80 @@ 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, #]&]
]
-FindAllCodeRanges[doc_TextDocument] := (
+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"]
+ }]]
+ )
+ }]
+)
- Cases[
- divideCells[doc],
- node_CellNode :> node["codeRange"],
- {0, Infinity}
- ]
- // Catenate
+
+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] := (
+ doc
+ // getCodeRanges
+ // Keys
// Map[ToLspRange[doc, #]&]
)
+GetDocumentText[doc_TextDocument] := (
+ doc["text"]
+ // Replace[{_String?(StringStartsQ["#!"]), restLines___} :> ({"", restLines})]
+ // StringRiffle[#, "\n"]&
+)
-(* ::Section:: *)
+GetDocumentText[doc_TextDocument, range_LspRange] := (
+ doc["text"]
+ // Take[#, {
+ range["start"]["line"] + 1,
+ range["end"]["line"] + 1
+ }]&
+ // MapAt[StringTake[#, range["end"]["character"]]&, -1]
+ // MapAt[StringDrop[#, range["start"]["character"]]&, 1]
+ // StringRiffle[#, "\n"]&
+)
+
+
+(* ::Subsection:: *)
(*AST utils*)
@@ -437,6 +604,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*)
@@ -449,54 +678,33 @@ 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[
+ node["codeRange"]
+ // Replace[_?MissingQ -> {}]
+ // rangeToAst[doc, #]&
+ // Flatten
+ // Map[ToDocumentSymbolImpl],
+ node["children"]
+ // Replace[_?MissingQ -> {}]
+ // 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_] := (
@@ -619,6 +827,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,
@@ -639,8 +848,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
@@ -665,40 +875,33 @@ 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]
- // Last
- // Key[CodeParser`Source]
- // Replace[{
- _?MissingQ -> Nothing,
- source_ :> SourceToRange[source]
- }]
+ 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[{} -> {<||>}]
+ // 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 *)}
- ]
-
+ })
+ ))
]
@@ -739,99 +942,18 @@ 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*)
DiagnoseDoc[doc_TextDocument] := (
- doc["text"]
- // Replace[{_String?(StringStartsQ["#!"]), restLines___} :> ({"", restLines})]
- // StringRiffle[#, "\n"]&
+ doc
+ // GetDocumentText
// 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]
@@ -845,17 +967,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",
@@ -866,7 +988,7 @@ DiagnoseDoc[doc_TextDocument] := (
(* // ReplaceAll[{CodeInspector`Format`LintMarkup[content_, ___] :> (
ToString[content]
)}] *)
- // StringReplace["``" -> "\""]
+ // StringReplace["``"|"**" -> "\""]
]
)
|>]]
@@ -947,86 +1069,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 :> {{}, {}}
+ }]
]
@@ -1247,46 +1364,73 @@ FindTopLevelSymbols[node_, name_String] := (
)
-(* ::Subsection:: *)
-(*DocumentColor*)
-
+(* ::Section:: *)
+(*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"]
+ ]&
+ ]
+)
-FindDocumentColor[doc_TextDocument] := With[
+GetCodeActionsInRange[doc_TextDocument, range_LspRange] := With[
{
- ast = CellToAST[doc, {1, doc["text"] // Length}]
+ startPos = {range["start"]["line"] + 1, range["start"]["character"] + 1},
+ endPos = {range["end"]["line"] + 1, range["end"]["character"]}
},
- Join[
- Cases[
- ast,
- AstPattern["NamedColor"][color_, data_] :> (
- ColorInformation[<|
- "range" -> (
- data
- // Key[CodeParser`Source]
- // SourceToRange
- ),
- "color" -> (
- ColorConvert[ToExpression[color], "RGB"]
- // Apply[List]
- // ToLspColor
- )
+ 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}
+ |>
|>]
- ),
- AstLevelspec["LeafNodeWithSource"]
- ],
- Cases[
- ast,
- AstPattern["ColorModel"][model_, params_, data_] :> With[
- {
- color = (
- params
- // Map[CodeParser`FromNode]
- // Apply[ToExpression[model]]
- )
- },
+ )]
+ ),
+ Missing["NotFound"],
+ AstLevelspec["DataWithSource"],
+ Heads -> False
+ ]&
+ // List
+ // DeleteMissing
+]
+
- If[ColorQ[color],
+(* ::Section:: *)
+(*DocumentColor*)
+
+
+FindDocumentColor[doc_TextDocument] := (
+ rangeToAst[doc, All]
+ // (ast \[Function] (
+ Join[
+ Cases[
+ ast,
+ AstPattern["NamedColor"][color_, data_] :> (
ColorInformation[<|
"range" -> (
data
@@ -1294,18 +1438,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 3b2e153..8d14f40 100644
--- a/src/WolframLanguageServer/Token.wl
+++ b/src/WolframLanguageServer/Token.wl
@@ -1,16 +1,17 @@
(* ::Package:: *)
+(* Copyright 2019 lsp-wl Authors *)
+(* SPDX-License-Identifier: MIT *)
+
+
(* Wolfram Language Server Token *)
-(* Author: kenkangxgwe ,
- huxianglong
-*)
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\"
"
@@ -164,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"]
]
]
@@ -204,7 +205,8 @@ Options[GenAttributes] = {
GenAttributes[token_String, o:OptionsPattern[]] := (
Attributes[token]
// Replace[_Attributes -> {}]
- // StringRiffle[#, ", "]&
+ // StringRiffle[#, {"(", ", ", ")"}]&
+ // Replace["()" -> ""]
)
Options[GenOptions] = {
@@ -214,7 +216,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"],
@@ -509,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 *)
@@ -519,6 +521,7 @@ GetTriggerKeyCompletion[doc_TextDocument, pos_LspPosition] := (
)
+(* SetDelayed is not needed. Cache it when define it. *)
NonLetterAliasCompletionItems = (
Join[
AliasToLongName
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..8dbdcd7 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[] <> "*"]]
@@ -104,6 +111,148 @@ 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[
+ 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" -> {
@@ -121,7 +270,7 @@ VerificationTest[
"character" -> 30
|>]
|>]},
- TestID -> "FindAllCodeRangePackage1"
+ TestID -> "FindAllCodeRangesPackage1"
],
VerificationTest[
@@ -142,7 +291,7 @@ VerificationTest[
"character" -> 30
|>]
|>]},
- TestID -> "FindAllCodeRangePackage2"
+ TestID -> "FindAllCodeRangesPackage2"
],
VerificationTest[
@@ -172,7 +321,7 @@ VerificationTest[
|>]
|>]
},
- TestID -> "FindAllCodeRangeSection1"
+ TestID -> "FindAllCodeRangesSection1"
],
VerificationTest[
@@ -200,7 +349,7 @@ VerificationTest[
|>]
|>]
},
- TestID -> "FindAllCodeRangeSection2"
+ TestID -> "FindAllCodeRangesSection2"
],
VerificationTest[
@@ -226,7 +375,7 @@ VerificationTest[
|>]
|>]
},
- TestID -> "FindAllCodeRangeSection3"
+ TestID -> "FindAllCodeRangesSection3"
],
VerificationTest[
@@ -255,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[
@@ -471,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]&]
diff --git a/test/WolframLanguageServer/TokenTest.wl b/test/WolframLanguageServer/TokenTest.wl
index 6c4f350..9d3920a 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[] <> "*"]]
@@ -21,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",
@@ -37,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",
@@ -62,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",
@@ -82,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[