diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 0000000..529a0df --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,47 @@ +# This is a basic workflow to help you get started with Actions + +name: CI + +# Controls when the action will run. Triggers the workflow on push or pull request +# events but only for the master branch +on: + push: + branches: + - master + - develop + pull_request: + branches: + - master + - develop + +env: + WOLFRAM_ID: ${{ secrets.WOLFRAM_ID }} + WOLFRAM_PW: ${{ secrets.WOLFRAM_PW }} + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + name: "Run lsp-wl tests" + # The type of runner that the job will run on + runs-on: ubuntu-latest + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - name: Install Wolfram Engine + run: | + wget https://account.wolfram.com/download/public/wolfram-engine/desktop/LINUX + sudo bash LINUX -- -auto -verbose + rm LINUX + - name: Activate Wolfram Engine + run: | + /usr/bin/wolframscript -authenticate $WOLFRAM_ID $WOLFRAM_PW + /usr/bin/wolframscript -activate + - name: Install Paclets + run: | + /usr/bin/wolframscript -code 'PacletInstall["CodeParser"];PacletInstall["CodeInspector"];Exit[]' + - name: Run Tests + run: | + /usr/bin/wolframscript -file init.wls --test \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 37473d4..896b9d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,14 +4,34 @@ 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.2.2] - 2020-08-01 🐱‍🏍 + +### Added + +- SignatureHelp is available for functions when `[` and `,` is input. + +### Changed + +- Change the dependencies from `AST` and `Lint` to `CodeParser` and +`CodeInspector`. +- The alias completion has been improved by reducing the number of +`completionItem`s, and returning `\`-prefixed aliases when triggered twice. + +### Fixed + +- `triggerCharacters` should be a list (thanks to +[@dalanicolai](https://github.com/dalanicolai)). +- The `duplicate requests` +error will not popup in Output window in VSCode (reported by +[@GiovanniBordiga](https://github.com/GiovanniBordiga)). +- MessageName should be shown when hovered + ## [0.2.1] - 2020-01-24 🏮🐀 ### Added - Definition / References are available to show in scopes / the whole file. - - DocumentHighlight is available to show the scoped variables. - - DocumentColor / ColorRepresentation are available to show the color names and models. ### Changed diff --git a/README.md b/README.md index 9dd87d4..0661f39 100644 --- a/README.md +++ b/README.md @@ -47,17 +47,21 @@ client would certainly work too. git clone https://github.com/kenkangxgwe/lsp-wl.git ``` -2. Install the dependent paclets with the correct versions (currently 0.15) +2. Install the dependent paclets with the correct versions (currently 1.0) from the Wolfram kernel / Mathematica. (_This will cost some time for the first time_) : ``` mathematica - PacletInstall["AST", "UpdateSites" -> True] - PacletInstall["Lint", "UpdateSites" -> True] + PacletInstall["CodeParser"] + PacletInstall["CodeInspector"] ``` 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) +4. You may also want to install +[GitLink](https://github.com/WolframResearch/GitLink) packet in order to +check for updates. + ## Run the Server Clients can start the server by running the `init.wls` file from Wolfram @@ -130,9 +134,10 @@ This is a good way to see the results from the unit tests. ![completion](images/completion.png) -- **Diagnostics:** Syntax error would be underlined. This feature is powered by - Brenton's `AST` and `Lint` paclets, thank you - [@bostick](https://github.com/bostick). +- **Diagnostics:** Syntax error would be underlined. This feature is powered + by [CodeParser](https://github.com/WolframResearch/codeparser) and + [CodeInspector](https://github.com/WolframResearch/codeinspector) paclets, + thank you [@bostick](https://github.com/bostick). ![diagnostics](images/diagnostics.png) @@ -191,10 +196,11 @@ It will be nice if you want to make a contribution to the following topic. create a pull request to add the clients in README.md once your client is released. -* Thanks to Brenton's `AST` and `Lint` 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. +* 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. 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. diff --git a/external/Matypetica/src/DataType.wl b/external/Matypetica/src/DataType.wl index 7c09346..d864ded 100644 --- a/external/Matypetica/src/DataType.wl +++ b/external/Matypetica/src/DataType.wl @@ -339,4 +339,14 @@ TypeUsage[typename_Symbol, usage_String] := ( End[] +Begin["`Test`"] + + +AssociationSameQ::usage = "AssociationSameQ[assoc1_Association, assoc2_Association] returns True if two associations are same except for key order." +AssociationSameQ = DataType`Private`AssociationSameQ + + +End[] + + EndPackage[] diff --git a/external/Matypetica/test/DataTypeTest.wl b/external/Matypetica/test/DataTypeTest.wl index 18f21fd..2eb206f 100644 --- a/external/Matypetica/test/DataTypeTest.wl +++ b/external/Matypetica/test/DataTypeTest.wl @@ -16,7 +16,7 @@ Needs[TestingContext] { VerificationTest[ - AssociationSameQ @@@ { + DataType`Test`AssociationSameQ @@@ { {<|"a" -> 1, "b" -> 2|>, <|"b" -> 2, "a" -> 1 |>}, {<|"a" -> 1, "b" -> 2|>, <|"b" -> 1, "a" -> 2 |>}, {<|"a" -> 1, "b" -> 2|>, <|"a" -> 2 |>}, @@ -155,7 +155,7 @@ VerificationTest[ stu1["courses"], <|1-> "ECON101", 2->"COMP202", 3->"PHYS201"|>, TestID -> "Replace Association", - SameTest -> AssociationSameQ + SameTest -> DataType`Test`AssociationSameQ ], VerificationTest[ @@ -178,7 +178,7 @@ VerificationTest[ stu1["courses"], <|1-> "ECON101", 2->"COMP202", 3->"PHYS201"|>, TestID -> "Replace Association", - SameTest -> AssociationSameQ + SameTest -> DataType`Test`AssociationSameQ ], VerificationTest[ @@ -250,7 +250,7 @@ VerificationTest[ TestID -> "Type usage 2" ] -} // Map@Curry[Sow]@CurrentContext +} // Map[Sow[#, CurrentContext]&] End[] diff --git a/init.wls b/init.wls index 6f6b7e1..1e383f1 100644 --- a/init.wls +++ b/init.wls @@ -14,6 +14,8 @@ Please see the help info below. *) +(* For windows, this should be set for Print to correctly format outputs. *) +SetOptions[$Output, FormatType -> OutputForm] (* ::Subsection:: *) (*RootDirectory*) @@ -113,7 +115,7 @@ Options: (*Version*) -WolframLanguageServer`Version = "0.2.1"; +WolframLanguageServer`Version = "0.2.2" If[MemberQ[WolframLanguageServer`CommandLine, "-v" | "--version"], Print[" Wolfram Language Server " <> WolframLanguageServer`Version <> " running on @@ -127,14 +129,16 @@ Wolfram Language " <> $Version <> "\n"]; If[MemberQ[WolframLanguageServer`CommandLine, "-t" | "--test"], - Print[" -Running tests for all. -"]; + Print["\nRunning tests for all.\n"]; + Off[General::shdw]; << RunTest`; - Print[RunTest`TestRunAll[]]; - Quit[]; -]; + RunTest`TestRunAll[] + // Replace[{testAllPass_, testResult_} :> ( + Print[testResult]; + Exit[If[testAllPass, 0, 1]] + )] +] (* ::Subsection:: *) @@ -143,12 +147,6 @@ Running tests for all. Needs["WolframLanguageServer`Logger`"]; -If[$VersionNumber < 11.3, - (* load myCurry *) - Needs["MyCurry`"]; - System`Curry = MyCurry`MyCurry -] - Module[ { stream, loglevel, logstreams, clientPid, port, pipe, @@ -212,6 +210,7 @@ Module[ DeclarePackage["WolframLanguageServer`Server`", {"WLServerStart", "WLServerVersion", "WLServerDebug"}]; *) << WolframLanguageServer`Server`; + WolframLanguageServer`CheckReturnTypeQ = True; LogDebug @ WolframLanguageServer`Server`WLServerStart[ "Stream" -> stream, "ClientPid" -> clientPid, diff --git a/src/MyCurry.wl b/src/MyCurry.wl deleted file mode 100644 index 9bdde6b..0000000 --- a/src/MyCurry.wl +++ /dev/null @@ -1,52 +0,0 @@ -(* ::Package:: *) - - -(* - A handmade Curry for version before 11.3 -*) - -BeginPackage["MyCurry`"] -ClearAll[Evaluate[Context[] <> "*"]] - - -MyCurry::usage = "A handmade Curry for version before 11.3." -CurriedFunction::usage = "The intermediate state of a curried function." - - -Begin["`Private`"] -ClearAll[Evaluate[Context[] <> "*"]] - - -Attributes[Curry] = {NHoldRest} - -MyCurry[func_, (arity_Integer?NonNegative) -> slotMap:{___Integer?Positive}] /; (arity >= Max[slotMap, 0]) := ( - CurriedFunction[func, {arity, 0}, Table[Missing[], Length[slotMap]], slotMap] -) -MyCurry[func_, slotMap:{___Integer?Positive}] := ( - MyCurry[func, Max[slotMap, 0] -> slotMap] -) -MyCurry[func_, arity_Integer?NonNegative] := ( - MyCurry[func, Range[arity]] -) -MyCurry[func_] := MyCurry[func, {2, 1}] - - -CurriedFunction[func_, {arity_, index_Integer}, args_, slotMap_] /; (arity == index) := (func@@args) -CurriedFunction[func_, {arity_, index_Integer}, args_, slotMap_][firstArg_, restArgs___] := ( - CurriedFunction[func, {arity, index + 1}, ReplacePart[args, Position[slotMap, index] -> firstArg], slotMap] -) -(curriedFunction:CurriedFunction[func_, {arity_, index_Integer}, args_, slotMap_])[] := ( - curriedFunction -) -CurriedFunction[func_, {arity_, index_Integer}, args_, slotMap_][firstArg_] := ( - CurriedFunction[func, {arity, index + 1}, ReplacePart[args, Position[slotMap, index + 1] -> firstArg], slotMap] -) -CurriedFunction[func_, {arity_, index_Integer}, args_, slotMap_][firstArg_, restArgs__] := ( - CurriedFunction[func, {arity, index + 1}, ReplacePart[args, Position[slotMap, index + 1] -> firstArg], slotMap][restArgs] -) - - -End[] - - -EndPackage[] \ No newline at end of file diff --git a/src/PatternTemplate.wl b/src/PatternTemplate.wl new file mode 100644 index 0000000..915cbd3 --- /dev/null +++ b/src/PatternTemplate.wl @@ -0,0 +1,113 @@ +(* ::Package:: *) + +(* Pattern Template *) +(* Author: kenkangxgwe *) + + +BeginPackage["PatternTemplate`"] +ClearAll[Evaluate[Context[] <> "*"]] + + +PatternTemplate::usage = "PatternTemplate[pattern_, o:OptionsPattern[]] returns a pattern template that can be applied on a list or association, in which new pattern names and additional patterns are given." +PatternTemplateObject::usage = "PatternTemplateObject[...] represent a templated pattern can be applied on a list or association." + + +Begin["`Private`"] +ClearAll[Evaluate[Context[] <> "*"]] + + +Options[PatternTemplate] = { + "OverwritePostfix" -> True +} + + +Attributes[PatternTemplateSlot] = {HoldFirst} + + +PatternTemplate[pattern_, o:OptionsPattern[]] := ( + PatternTemplateObject[pattern /. {Verbatim[Pattern] -> PatternTemplateSlot}, o] +) + + +Options[PatternTemplateObject] = { + "OverwritePostfix" -> True +} + + +PatternTemplateObject[templatedPattern_, o:OptionsPattern[]][newPatterns___] := ( + {newPatterns} + // Cases[Verbatim[Pattern][newPatternSymbol_Symbol, newPatternObject_] :> ( + With[ + { + newPatternName = SymbolName[Unevaluated[newPatternSymbol]], + newPatternTuple = PatternTemplateSlot[newPatternSymbol, newPatternObject] + }, + + { + newPatternName -> newPatternTuple, + If[OptionValue["OverwritePostfix"] && StringEndsQ[newPatternName, "$"], + StringDrop[newPatternName, -1] -> newPatternTuple, + Nothing + ] + } + ] + )] + // Flatten + // Apply[Association] + // PatternTemplateObject[templatedPattern] +) + +PatternTemplateObject[templatedPattern_, o:OptionsPattern[]][newPatterns_Association] := Block[ + { + newPatternTuples = Replace[ + newPatterns, + newPattern_Pattern :> (PatternTemplateSlot @@ newPattern), + {1} + ] + }, + + templatedPattern + //. { + PatternTemplateSlot[oldPatternSymbol_Symbol, oldPatternObject_] :> With[ + { + oldPatternName = SymbolName[Unevaluated[oldPatternSymbol]] + }, + + (* Renamed function parameter *) + Quiet[ + newPatternTuples[oldPatternName] + // Replace[{ + (* fallbacks to use nameless old pattern*) + _?MissingQ -> oldPatternObject, + (* uses new name and combines two patterns *) + PatternTemplateSlot[newPatternSymbol_, newPatternObject_] :> ( + newPatternSymbol:combinePatterns[newPatternObject, oldPatternObject] + ), + (* otherwise, combines with new pattern without name *) + newPatternObject_ :> combinePatterns[newPatternObject, oldPatternObject] + }], + {RuleDelayed::rhs} + ] + ], + (* Don't replace nested PatternTemplateObjects *) + nestedObject_PatternTemplateObject :> nestedObject + } +] + + +Attributes[combinePatterns] = {Flat} + + +combinePatterns[patterns__] := ( + {patterns} + // DeleteDuplicates + // DeleteCases[Verbatim[_]] + // Replace[{} -> {_}] + // Fold[Except[Except[#1], #2]&] +) + + +End[] + + +EndPackage[] diff --git a/src/WolframLanguageServer/AstPatterns.wl b/src/WolframLanguageServer/AstPatterns.wl index c6fc13d..f92d408 100644 --- a/src/WolframLanguageServer/AstPatterns.wl +++ b/src/WolframLanguageServer/AstPatterns.wl @@ -6,7 +6,7 @@ *) BeginPackage["WolframLanguageServer`AstPatterns`"] -Construct[ClearAll, Context[] <> "*"] +ClearAll[Evaluate[Context[] <> "*"]] FunctionPattern::usage = "A set of function head patterns." @@ -16,6 +16,7 @@ AstLevelspec::usage = "A set of levelspec that is useful to specify when using C Begin["`Private`"] ClearAll[Evaluate[Context[] <> "*"]] +Needs["PatternTemplate`"] Needs["WolframLanguageServer`Logger`"] Needs["WolframLanguageServer`ColorTable`"] @@ -80,106 +81,56 @@ FunctionPattern = <| "ColorDirective" -> ( "Opacity" | "Lighter" | "Darker" | "ColorNegate" + ), + + "NoSignatureHelp" -> ( + "List" | "Association" | "CompoundExpression" | + "Rule" | "RuleDelayed" | + "Set" | "SetDelayed" | "UpSet" | "UpSetDelayed" | + "TagSet" | "TagSetDelayed" | + "With" | "Block" | "Module" | "DynamicModule" ) |> -Options[ExportPattern] = { - "OverwritePostfix" -> True -} - -ExportPattern[pattern_, o:OptionsPattern[]] := With[ - { - exportedPattern = pattern /. {Verbatim[Pattern] -> ExportedPattern} - }, - - Hold[patternNewNameAssocOrList \[Function] Block[ - { - patternNewNamesAssoc = ( - patternNewNameAssocOrList - //Replace[{ - _Association :> patternNewNameAssocOrList, - (* - convert list of patterns to association where pattern - names point to their pattern - *) - {Verbatim[Pattern][_Symbol, _]...} :> ( - patternNewNameAssocOrList - // Map[( - # - // First - // SymbolName - // (patternName \[Function] { - patternName -> #, - If[OptionValue["OverwritePostfix"] && StringEndsQ[patternName, "$"], - StringDrop[patternName, -1] -> #, - Nothing - ] - }) - )&] - // Flatten - // Apply[Association] - ), - (* otherwise, use empty association *) - _ -> <||> - }] - ) - }, - exportedPattern - ]] - //. {ExportedPattern[patternName_Symbol, patternObject_] :> ( - (* Renamed function parameter *) - patternNewNamesAssoc - // Key[SymbolName[patternName]] - // (patternObject - // If[MissingQ[#], - Identity, - Curry[Pattern, 2][# // First] - ] - )& - )} - // ReleaseHold -] - - AstPattern = <| "Token" -> ( - AST`LeafNode[kind_Symbol, tokenString_String, data_Association] + (CodeParser`LeafNode|CodeParser`ErrorNode)[kind_Symbol, tokenString_String, data_Association] ), "Symbol" -> ( - AST`LeafNode[Symbol, symbolName_String, data_Association] + CodeParser`LeafNode[Symbol, symbolName_String, data_Association] ), "Integer" -> ( - AST`LeafNode[Integer, integerLiteral_String, data_Association] + CodeParser`LeafNode[Integer, integerLiteral_String, data_Association] ), "Real" -> ( - AST`LeafNode[Real, realLiteral_String, data_Association] + CodeParser`LeafNode[Real, realLiteral_String, data_Association] ), "Function" -> ( - AST`CallNode[AST`LeafNode[Symbol, functionName_String, _], arguments_List, data_Association] + CodeParser`CallNode[CodeParser`LeafNode[Symbol, functionName_String, _], arguments_List, data_Association] ), "MessageName" -> ( - AST`CallNode[ - AST`LeafNode[Symbol, "MessageName", _], + CodeParser`CallNode[ + CodeParser`LeafNode[Symbol, "MessageName", _], { - AST`LeafNode[Symbol, symbolName_String, _], - message:AST`LeafNode[String, messageLiteral_String, _] + CodeParser`LeafNode[Symbol, symbolName_String, _], + message:CodeParser`LeafNode[String, messageLiteral_String, _] }, data_Association ] ), "Definable" -> ( - AST`CallNode[ - AST`LeafNode[Symbol, op:(FunctionPattern["BinarySet"]), _], + CodeParser`CallNode[ + CodeParser`LeafNode[Symbol, op:(FunctionPattern["BinarySet"]), _], { - head:AST`CallNode[AST`LeafNode[Symbol, func:FunctionPattern["Definable"], _], { - AST`LeafNode[Symbol, (key_), _], + head:CodeParser`CallNode[CodeParser`LeafNode[Symbol, func:FunctionPattern["Definable"], _], { + CodeParser`LeafNode[Symbol, (key_), _], ___ }, _], body_ @@ -189,10 +140,10 @@ AstPattern = <| ), "Set" -> ( - AST`CallNode[ - AST`LeafNode[Symbol, op:(FunctionPattern["BinarySet"] | FunctionPattern["TenarySet"]), _], + CodeParser`CallNode[ + CodeParser`LeafNode[Symbol, op:(FunctionPattern["BinarySet"] | FunctionPattern["TenarySet"]), _], { - Repeated[AST`LeafNode[Symbol, tag_String, _], {0, 1}], + Repeated[CodeParser`LeafNode[Symbol, tag_String, _], {0, 1}], head_?lhsQ, body_ }, @@ -201,8 +152,8 @@ AstPattern = <| ), "Scope" -> ( - AST`CallNode[ - AST`LeafNode[Symbol, op:(FunctionPattern["Scope"]), _], + CodeParser`CallNode[ + CodeParser`LeafNode[Symbol, op:(FunctionPattern["Scope"]), _], { head_, body_, @@ -214,10 +165,10 @@ AstPattern = <| ), "InscopeSet" -> ( - AST`CallNode[ - AST`LeafNode[Symbol, op:("Set" | "SetDelayed"), _], + CodeParser`CallNode[ + CodeParser`LeafNode[Symbol, op:("Set" | "SetDelayed"), _], { - AST`LeafNode[Symbol, symbolName_String, symbolData_Association], + CodeParser`LeafNode[Symbol, symbolName_String, symbolData_Association], value_ }, data_Association @@ -225,18 +176,18 @@ AstPattern = <| ), "Delayed" -> ( - AST`CallNode[ - AST`LeafNode[Symbol, op:(FunctionPattern["Delayed"]), _], + CodeParser`CallNode[ + CodeParser`LeafNode[Symbol, op:(FunctionPattern["Delayed"]), _], { (* Optional Tag: *) _:Null, head_, body_}, data_Association ] ), "DelayedPattern" -> ( - AST`CallNode[ - AST`LeafNode[Symbol, "Pattern", _], + CodeParser`CallNode[ + CodeParser`LeafNode[Symbol, "Pattern", _], { - AST`LeafNode[Symbol, patternName_String, patternData_Association], + CodeParser`LeafNode[Symbol, patternName_String, patternData_Association], patternObject_ }, data_Association @@ -244,28 +195,28 @@ AstPattern = <| ), "CompoundExpression" -> ( - AST`CallNode[ - AST`LeafNode[Symbol, op:("CompoundExpression"), _], + CodeParser`CallNode[ + CodeParser`LeafNode[Symbol, op:("CompoundExpression"), _], exprs_List, data_Association ] ), "NamedColor" -> ( - AST`LeafNode[Symbol, color:FunctionPattern["NamedColor"], data_Association] + CodeParser`LeafNode[Symbol, color:FunctionPattern["NamedColor"], data_Association] ), "ColorModel" -> ( - AST`CallNode[ - AST`LeafNode[Symbol, model:FunctionPattern["ColorModel"], _], + CodeParser`CallNode[ + CodeParser`LeafNode[Symbol, model:FunctionPattern["ColorModel"], _], params: { - (AST`LeafNode[Integer | Real | String, _, _]).. + (CodeParser`LeafNode[Integer | Real | String, _, _]).. }, data_Association ] ) -|> // Map[ExportPattern] +|> // Map[PatternTemplate] AstLevelspec = <| @@ -280,7 +231,7 @@ AstLevelspec = <| lhsQ[node_] := ( - FreeQ[node, _AST`AbstractSyntaxErrorNode] && + FreeQ[node, _CodeParser`AbstractSyntaxErrorNode] && MatchQ[FirstPosition[node, Symbol], {(1)...}] ) diff --git a/src/WolframLanguageServer/ColorTable.wl b/src/WolframLanguageServer/ColorTable.wl index ed77820..ecce2a1 100644 --- a/src/WolframLanguageServer/ColorTable.wl +++ b/src/WolframLanguageServer/ColorTable.wl @@ -6,7 +6,7 @@ *) BeginPackage["WolframLanguageServer`ColorTable`"] -Construct[ClearAll, Context[] <> "*"] +ClearAll[Evaluate[Context[] <> "*"]] WolframLanguageServer`ColorTable`Colorspace = { "RGB", "CMYK", "HSB", "XYZ", "LAB", "LCH", "LUV", "GrayScale" diff --git a/src/WolframLanguageServer/Server.wl b/src/WolframLanguageServer/Server.wl index 96e0c7a..07e8220 100644 --- a/src/WolframLanguageServer/Server.wl +++ b/src/WolframLanguageServer/Server.wl @@ -1,7 +1,7 @@ (* ::Package:: *) (* Wolfram Language Server *) -(* Author: kenkangxgwe , +(* Author: kenkangxgwe , huxianglong *) @@ -45,24 +45,46 @@ DeclareType[WorkState, <| "openedDocs" -> _Association, (* (_DocumentUri -> _DocumentText)... *) "client" -> (_SocketClient | _SocketObject | _NamedPipe | _StdioClient | "stdio" | Null), "clientCapabilities" -> _Association, - "config" -> _Association, - "scheduledTasks" -> {___ServerTask} -|>]; + "scheduledTasks" -> {___ServerTask}, + "caches" -> _Association, + "config" -> _Association +|>] + + +DeclareType[RequestCache, <| + "cachedTime" -> _DateObject, + "result" -> _, + "error" -> _Association +|>] + +initialCaches = <| + "textDocument/signatureHelp" -> <||>, + "textDocument/documentSymbol" -> <||>, + "textDocument/documentColor" -> <||>, + "textDocument/codeLens" -> <||>, + "textDocument/publishDiagnostics" -> <||> +|> InitialState = WorkState[<| "initialized" -> False, "openedDocs" -> <||>, "client" -> Null, - "config" -> loadConfig[], - "scheduledTasks" -> {} -|>]; + "scheduledTasks" -> {}, + "caches" -> initialCaches, + "config" -> <| + "configFileConfig" -> loadConfig[] + |> +|>] ServerCapabilities = <| "textDocumentSync" -> TextDocumentSyncKind["Full"], "hoverProvider" -> True, + "signatureHelpProvider" -> <| + "triggerCharacters" -> {"[", ","} + |>, "completionProvider" -> <| "resolveProvider" -> True, - "triggerCharacters" -> "\\" + "triggerCharacters" -> {"\\"} |>, "definitionProvider" -> True, "referencesProvider" -> True, @@ -76,10 +98,29 @@ ServerCapabilities = <| |>, *) Nothing |> -(*Place where the temporary img would be stored, delete after usage.*) -(* tempImgPath = $TemporaryDirectory <> $PathnameSeparator <> "temp.svg"; *) -(* tempDirPath = WolframLanguageServer`Directory <> $PathnameSeparator <> "Cache"; *) -(* If[!FileExistsQ[tempImgPath], CreateFile[tempImgPath], ]; *) + +ServerConfig = <| + "updateCheckInterval" -> Quantity[7, "Days"], + (* cached results *) + "cachedRequests" -> { + "textDocument/signatureHelp", + "textDocument/documentSymbol", + "textDocument/documentColor" + (* "textDocument/codeLens" *) + }, + "delayedRequests" -> { + "textDocument/documentHighlight", + "textDocument/documentColor" + }, + (* default delays (seconds) *) + "requestDelays" -> <| + "textDocument/publishDiagnostics" -> 2.5, + "textDocument/signatureHelp" -> 0.5, + "textDocument/documentSymbol" -> 4.0, + "textDocument/documentHighlight" -> 0.5, + "textDocument/documentColor" -> 5.0 + |> +|> (* ::Section:: *) @@ -111,7 +152,7 @@ WLServerStart[o:OptionsPattern[]] := Module[ (*Temporary cache.*) tempDirPath = FileNameJoin[{workingDir, "wlServerCache"}]; - If[DirectoryQ[tempDirPath], + If[DirectoryQ[tempDirPath], (*Clear the cache of last time usage.*) DeleteDirectory[tempDirPath, DeleteContents -> True], (*Create temporary file.*) @@ -276,7 +317,7 @@ SocketHandler[packet_Association] := Module[ { bytearray = packet["DataByteArray"], client = serverState["client"], - headerEndPosition, headertext, contentlength, + headerEndPosition, headertext, contentlength, consumelength, message, serverStatus }, @@ -353,16 +394,17 @@ TcpSocketHandler[state_WorkState] := Module[ }, If[SocketReadyQ[client], - handleMessageList[ReadMessages[client], state] - // Replace[{ - {"Continue", newstate_} :> newstate, - {stop_, newstate_} :> ( - {stop, newstate} - ), - {} :> state (* No message *) - }], + handleMessageList[ReadMessages[client], state], doNextScheduledTask[state] ] + // Replace[{ + {"Continue", newstate_} :> newstate, + {stop_, newstate_} :> ( + {stop, newstate} + ), + {} :> state, (* No message *) + err_ :> {LogError[err], state} + }] ] // TcpSocketHandler (* Put the recursive call out of the module to turn on the tail-recursion optimization *) @@ -598,6 +640,7 @@ constructRPCBytes[msg_Association] := ( if the result is not able to convert to JSON, returns an error respond *) + LogError[msg]; ExportByteArray[ msg // KeyDrop["result"] @@ -647,25 +690,18 @@ CloseClient[client_NamedPipe] := With[ (*Handle Message*) -NotificationQ[msg_Association] := MissingQ[msg["id"]]; +NotificationQ = KeyExistsQ["id"] /* Not +(* NotificationQ[msg_Association] := MissingQ[msg["id"]] *) handleMessageList[msgs:{___Association}, state_WorkState] := ( FoldWhile[handleMessage[#2, Last[#1]]&, {"Continue", state}, msgs, MatchQ[{"Continue", _}]] ); -handleMessage[msg_Association, state_WorkState] := Module[ +handleMessage[msg_Association, state_WorkState] := With[ { - method, newState = state + method = msg["method"] }, - - method = msg["method"]; - Replace[method, { - "textDocument/didOpen" | "textDocument/didChange" :> ( - LogDebug @ Iconize[method] - ), - _ :> LogDebug @ Iconize[msg, method] - }]; - + Which[ (* wrong message before initialization *) !state["initialized"] && !MemberQ[{"initialize", "initialized", "exit"}, method], @@ -682,31 +718,78 @@ handleMessage[msg_Association, state_WorkState] := Module[ {"Continue", state}, (* notification*) NotificationQ[msg], - handleNotification[method, msg, newState], + handleNotification[method, msg, state], (* resquest *) True, - handleRequest[method, msg, newState] + Which[ + MemberQ[ServerConfig["cachedRequests"], method] && + cacheAvailableQ[method, msg, state], + LogInfo["Sending cached results of " <> ToString[method]]; + sendCachedResult[method, msg, state], + MemberQ[ServerConfig["delayedRequests"], method], + scheduleDelayedRequest[method, msg, state], + True, + handleRequest[method, msg, state] + ] ] ]; -(* ::Subsection:: *) -(*Send Response*) +(* ::Section:: *) +(*Handle Requests*) -(* Both response and notification will call this function *) + +(* generic currying *) +cacheResponse[method_String, msg_][state_WorkState] = + cacheResponse[method, msg, state] + + +sendCachedResult[method_String, msg_, state_WorkState] := Block[ + { + cache = getCache[method, msg, state] + }, + + sendResponse[state["client"], + <|"id" -> msg["id"]|> + // Append[ + If[!MissingQ[cache["result"]], + "result" -> cache["result"], + "error" -> cache["error"] + ] + ] + ]; + + {"Continue", state} +] + + +scheduleDelayedRequest[method_String, msg_, state_WorkState] := ( + { + "Continue", + + addScheduledTask[state, ServerTask[<| + "type" -> method, + "scheduledTime" -> DatePlus[Now, { + ServerConfig["requestDelays"][method], + "Second" + }], + "id" -> msg["id"], + "params" -> getScheduleTaskParameter[method, msg, state], + "callback" -> (handleRequest[method, msg, #1]&) + |>]] + } +) + + +(* response, notification and request will call this function *) sendResponse[client_, res_Association] := ( Prepend[res, <|"jsonrpc" -> "2.0"|>] - // LogDebug // constructRPCBytes // WriteMessage[client] ) -(* ::Section:: *) -(*Handle Requests*) - - (* ::Subsection:: *) (*initialize*) @@ -741,17 +824,75 @@ handleRequest["initialize", msg_, state_WorkState] := Module[ handleRequest["shutdown", msg_, state_] := Module[ { }, - - + sendResponse[state["client"], <| "id" -> msg["id"], "result" -> Null |>]; - + {"Continue", state} ]; +(* ::Subsection:: *) +(*workspace/executeCommand*) + + +handleRequest["workspace/executeCommand", msg_, state_] := With[ + { + command = msg["params"]["command"], + args = msg["params"]["arguments"] + }, + + Replace[command, { + "dap-wl.runfile" -> ( + LogInfo[StringJoin["executing ", command, "with arguments: ", ToString[args]]] + ) + }]; + + sendResponse[state["client"], <| + "id" -> msg["id"], + "result" -> Null + |>]; + + {"Continue", state} + +] + + +(* ::Subsection:: *) +(*textDocument/publishDiagnostics*) + + +handleRequest["textDocument/publishDiagnostics", uri_String, state_WorkState] := ( + sendResponse[state["client"], <| + "method" -> "textDocument/publishDiagnostics", + "params" -> <| + "uri" -> uri, + "diagnostics" -> ToAssociation[DiagnoseDoc[state["openedDocs"][uri]]] + |> + |>]; + {"Continue", state} +) + + +handleRequest["textDocument/clearDiagnostics", uri_String, state_WorkState] := ( + sendResponse[state["client"], <| + "method" -> "textDocument/publishDiagnostics", + "params" -> <| + "uri" -> uri, + "diagnostics" -> {} + |> + |>]; + {"Continue", state} +) + + +getScheduleTaskParameter[method:"textDocument/publishDiagnostics", uri_String, state_WorkState] := ( + uri +) + + (* ::Subsection:: *) (*textDocument/hover*) @@ -771,6 +912,54 @@ handleRequest["textDocument/hover", msg_, state_] := With[ ] +(* ::Subsection:: *) +(*textDocument/signatureHelp*) + + +handleRequest[method:"textDocument/signatureHelp", msg_, state_] := ( + state + // cacheResponse[method, msg] + // sendCachedResult[method, msg, #]& + ) + + +cacheResponse[method:"textDocument/signatureHelp", msg_, state_WorkState] := With[ + { + uri = msg["params"]["textDocument"]["uri"], + pos = LspPosition[msg["params"]["position"]] + + }, + + state + // ReplaceKey[ + {"caches", method, uri} -> RequestCache[<| + "cachedTime" -> Now, + "result" -> ( + GetSignatureHelp[state["openedDocs"][uri], pos] + // ToAssociation + ) + |>] + ] +] + + +cacheAvailableQ[method:"textDocument/signatureHelp", msg_, state_WorkState] := Block[ + { + cachedTime = getCache[method, msg, state]["cachedtime"] + }, + + !MissingQ[cachedTime] && ( + cachedTime + > DatePlus[Now, {-ServerConfig["requestDelays"][method], "Second"}] + ) +] + + +getCache[method:"textDocument/signatureHelp", msg_, state_WorkState] := ( + state["caches"][method][msg["params"]["textDocument"]["uri"]] +) + + (* ::Subsection:: *) (*textDocument/completion*) @@ -798,7 +987,7 @@ handleRequest["textDocument/completion", msg_, state_] := Module[ "result" -> <| "isIncomplete" -> True, "items" -> ( - GetTriggerKeyCompletion[] + GetTriggerKeyCompletion[doc, pos] // ToAssociation ) |> @@ -832,7 +1021,7 @@ handleRequest["completionItem/resolve", msg_, state_] := With[ markupKind = ( state["clientCapabilities"]["textDocument"]["completion"]["completionItem"]["documentationFormat"] // First - // Replace[Except[_?(Curry[MemberQ, 2][Values[MarkupKind]])] -> MarkupKind["PlainText"]] + // Replace[Except[_?(MemberQ[Values[MarkupKind], #]&)] -> MarkupKind["PlainText"]] ) }, @@ -862,8 +1051,7 @@ handleRequest["completionItem/resolve", msg_, state_] := With[ ) }]; - state - // Curry[addScheduledTask][ServerTask[<| + addScheduledTask[state, ServerTask[<| "type" -> "JustContinue", "scheduledTime" -> Now |>]] @@ -915,113 +1103,133 @@ handleRequest["textDocument/references", msg_, state_] := With[ (*textDocument/documentHighlight*) -handleRequest["textDocument/documentHighlight", msg_, state_] := With[ +handleRequest[method:"textDocument/documentHighlight", msg_, state_WorkState] := With[ { id = msg["id"], uri = msg["params"]["textDocument"]["uri"], - pos = LspPosition[msg["params"]["position"]], - scheduledTime = DatePlus[Now, { - state["config"]["documentHighlightDelay"], - "Second" - }] + pos = LspPosition[msg["params"]["position"]] }, - state - // Curry[addScheduledTask][ServerTask[<| - "type" -> "documentHighlight", - "scheduledTime" -> scheduledTime, + sendResponse[state["client"], <| "id" -> id, - "params" -> {uri, pos}, - "callback" -> (sendResponse[#1["client"], <| - "id" -> id, - "result" -> ( - { - #1["openedDocs"][uri], - pos - } - // Apply[FindDocumentHighlight] - // ToAssociation - ) - |>]&) - |>]] - // List - // Prepend["Continue"] + "result" -> ( + FindDocumentHighlight[state["openedDocs"][uri], pos] + // ToAssociation + ) + |>]; + {"Continue", state} ] +getScheduleTaskParameter["textDocument/documentHighlight", msg_, state_WorkState] := ( + msg +) + + (* ::Subsection:: *) (*textDocument/documentSymbol*) -handleRequest["textDocument/documentSymbol", msg_, state_] := With[ +handleRequest[method:"textDocument/documentSymbol", msg_, state_WorkState] := ( + state + // cacheResponse[method, msg] + // sendCachedResult[method, msg, #]& +) + + +cacheResponse[method:"textDocument/documentSymbol", msg_, state_WorkState] := With[ { - id = msg["id"], - uri = msg["params"]["textDocument"]["uri"], - scheduledTime = DatePlus[ - state["openedDocs"][msg["params"]["textDocument"]["uri"]]["lastUpdate"], - {state["config"]["documentSymbolDelay"], "Second"} - ] + uri = msg["params"]["textDocument"]["uri"] }, state - // Curry[addScheduledTask][ServerTask[<| - "type" -> "documentSymbol", - "scheduledTime" -> scheduledTime, - "id" -> id, - "params" -> uri, - "callback" -> (sendResponse[#1["client"], <| - "id" -> id, + // ReplaceKey[ + {"caches", method, uri} -> RequestCache[<| + "cachedTime" -> Now, "result" -> ( - #1["openedDocs"][uri] + state["openedDocs"][uri] // ToDocumentSymbol // ToAssociation ) - |>]&) - |>]] - // List - // Prepend["Continue"] + |>] + ] ] +cacheAvailableQ[method:"textDocument/documentSymbol", msg_, state_WorkState] := Block[ + { + cachedTime = getCache[method, msg, state]["cachedtime"] + }, + + !MissingQ[cachedTime] && + !( + cachedTime < + state["openedDocs"][ + msg["params"]["textDocument"]["uri"] + ]["lastUpdate"] < + DateDifference[{ServerConfig["requestDelays"][method], "Second"}, Now] + ) +] + + +getCache[method:"textDocument/documentSymbol", msg_, state_WorkState] := ( + state["caches"][method][msg["params"]["textDocument"]["uri"]] +) + + (* ::Subsection:: *) (*textDocument/documentColor*) -handleRequest["textDocument/documentColor", msg_, state_] := With[ +handleRequest[method:"textDocument/documentColor", msg_, state_WorkState] := ( + state + // cacheResponse[method, msg] + // sendCachedResult[method, msg, #]& +) + + +cacheResponse[method:"textDocument/documentColor", msg_, state_WorkState] := With[ { - id = msg["id"], - uri = msg["params"]["textDocument"]["uri"], - scheduledTime = DatePlus[ - state["openedDocs"][msg["params"]["textDocument"]["uri"]]["lastUpdate"], - {state["config"]["documentColorDelay"], "Second"} - ] + uri = msg["params"]["textDocument"]["uri"] }, state - // Curry[addScheduledTask][ServerTask[<| - "type" -> "documentColor", - "scheduledTime" -> scheduledTime, - "id" -> id, - "params" -> uri, - "callback" -> (sendResponse[#1["client"], <| - "id" -> id, + // ReplaceKey[ + {"caches", method, uri} -> RequestCache[<| + "cachedTime" -> Now, "result" -> ( - #1["openedDocs"][uri] + state["openedDocs"][uri] // FindDocumentColor // ToAssociation ) - |>]&), - "duplicateFallback" -> (sendResponse[#1["client"], <| - "id" -> id, - "result" -> {} - |>]&) - |>]] - // List - // Prepend["Continue"] + |>] + ] ] +cacheAvailableQ[method:"textDocument/documentColor", msg_, state_WorkState] := Block[ + { + cachedTime = getCache[method, msg, state]["cachedTime"] + }, + + !MissingQ[cachedTime] && + (state["openedDocs"][ + msg["params"]["textDocument"]["uri"] + ]["lastUpdate"] < cachedTime) +] + + +getCache[method:"textDocument/documentColor", msg_, state_WorkState] := ( + state["caches"][method][msg["params"]["textDocument"]["uri"]] +) + + +getScheduleTaskParameter[method:"textDocument/documentColor", msg_, state_WorkState] := ( + msg["params"]["textDocument"]["uri"] +) + + (* ::Subsection:: *) (*textDocument/colorPresentation*) @@ -1043,8 +1251,7 @@ handleRequest["textDocument/colorPresentation", msg_, state_] := With[ { "Continue", - state - // Curry[addScheduledTask][ServerTask[<| + addScheduledTask[state, ServerTask[<| "type" -> "JustContinue", "scheduledTime" -> Now |>]] @@ -1084,10 +1291,10 @@ handleNotification["initialized", msg_, state_] := ( "Continue", state // ReplaceKey["initialized" -> True] - // Curry[addScheduledTask][ServerTask[<| - "type" -> "CheckUpgrades", + // addScheduledTask[#, ServerTask[<| + "type" -> "InitialCheck", "scheduledTime" -> Now - |>]] + |>]]& } ) @@ -1124,7 +1331,7 @@ handleNotification["$/cancelRequest", msg_, state_] := With[ // Replace[{ {pos_} :> ( Part[state["scheduledTasks"], pos]["type"] - // Curry[StringJoin][" request is cancelled."] + // StringJoin[#, " request is cancelled."]& // LogDebug; sendResponse[state["client"], <| "id" -> id, @@ -1158,10 +1365,19 @@ handleNotification["textDocument/didOpen", msg_, state_] := With[ // ReplaceKeyBy["openedDocs" -> Append[textDocumentItem["uri"] -> CreateTextDocument[textDocumentItem]] ] - // (newState \[Function] ( - publishDiagnostics[newState, textDocumentItem["uri"]]; - {"Continue", newState} - )) + // ReplaceKeyBy["caches" -> (Fold[ReplaceKeyBy, #, { + "textDocument/signatureHelp" -> + Append[textDocumentItem["uri"] -> RequestCache[<||>]], + "textDocument/documentSymbol" -> + Append[textDocumentItem["uri"] -> RequestCache[<||>]], + "textDocument/documentColor" -> + Append[textDocumentItem["uri"] -> RequestCache[<||>]], + "textDocument/codeLens" -> + Append[textDocumentItem["uri"] -> RequestCache[<||>]], + "textDocument/publishDiagnostics" -> + Append[textDocumentItem["uri"] -> <|"scheduledQ" -> False|>] + }]&)] + // handleRequest["textDocument/publishDiagnostics", textDocumentItem["uri"], #]& ] @@ -1173,11 +1389,17 @@ handleNotification["textDocument/didClose", msg_, state_] := With[ { uri = msg["params"]["textDocument"]["uri"] }, - + LogDebug @ ("Close Document " <> uri); - clearDiagnostics[state, uri]; - {"Continue", ReplaceKeyBy[state, {"openedDocs"} -> KeyDrop[uri]]} + state + // ReplaceKeyBy[{"openedDocs"} -> KeyDrop[uri]] + // ReplaceKeyBy["caches" -> (Fold[ReplaceKeyBy, #, { + "textDocument/documentSymbol" -> KeyDrop[uri], + "textDocument/documentColor" -> KeyDrop[uri], + "textDocument/codeLens" -> KeyDrop[uri] + }]&)] + // handleRequest["textDocument/clearDiagnostics", uri, #]& ] @@ -1185,10 +1407,14 @@ handleNotification["textDocument/didClose", msg_, state_] := With[ (*textSync/didChange*) -handleNotification["textDocument/didChange", msg_, state_] := With[ +handleNotification["textDocument/didChange", msg_, state_WorkState] := With[ { doc = msg["params"]["textDocument"], - uri = msg["params"]["textDocument"]["uri"] + uri = msg["params"]["textDocument"]["uri"], + (* lastUpdate = state["openedDocs"][msg["params"]["textDocument"]["uri"]]["lastUpdate"] *) + diagScheduledQ = state["caches"]["textDocument/publishDiagnostics"][ + msg["params"]["textDocument"]["uri"] + ]["scheduledQ"] }, (* Because of concurrency, we have to make sure the changed message brings a newer version. *) @@ -1214,14 +1440,7 @@ handleNotification["textDocument/didChange", msg_, state_] := With[ (* newState["openedDocs"][uri]["version"] = doc["version"]; *) LogDebug @ ("Change Document " <> uri); - (* Clean the diagnostics only if lastUpdate time is before delay *) - If[DatePlus[state["openedDocs"][uri]["lastUpdate"], { - state["config"]["diagnosticsDelay"], - "Second" - }] < Now, - clearDiagnostics[state, uri] - ]; state // ReplaceKey[{"openedDocs", uri} -> ( @@ -1233,23 +1452,26 @@ handleNotification["textDocument/didChange", msg_, state_] := With[ ] // ReplaceKey["version" -> doc["version"]] )] - // Curry[addScheduledTask][ServerTask[<| + // addScheduledTask[#, ServerTask[<| "type" -> "JustContinue", "scheduledTime" -> Now - |>]] - (* Give diagnostics after delay *) - // Curry[addScheduledTask][ServerTask[<| - "type" -> "PublishDiagnostics", - "params" -> uri, - "scheduledTime" -> DatePlus[Now, { - state["config"]["diagnosticsDelay"], + |>]]& + // If[diagScheduledQ, + List + /* Prepend["Continue"], + ReplaceKey[{"caches", "textDocument/publishDiagnostics", uri, "scheduledQ"} -> True] + /* (scheduleDelayedRequest["textDocument/publishDiagnostics", uri, #]&) + ] + (* // If[DatePlus[lastUpdate, { + ServerConfig["requestDelays"]["textDocument/publishDiagnostics"], "Second" - }], - "callback" -> (publishDiagnostics[#1, uri]&) - |>]] - // List - // Prepend["Continue"] - + }] < Now, + handleRequest["textDocument/clearDiagnostics", uri, #]& + (* Give diagnostics after delay *) + /* Last + /* scheduleDelayedRequest["textDocument/publishDiagnostics", uri, #]& + /* Last, + ] *) ] @@ -1259,51 +1481,13 @@ handleNotification["textDocument/didChange", msg_, state_] := With[ handleNotification["textDocument/didSave", msg_, state_] := With[ { - uri = msg["params"]["textDocument"]["uri"] + (* uri = msg["params"]["textDocument"]["uri"] *) }, - - (* Give diagnostics after save *) - publishDiagnostics[state, uri]; - + (* do nothing *) {"Continue", state} ] -(* ::Subsection:: *) -(*textDocument/publishDiagnostics*) - - -diagnoseTextDocument[doc_TextDocument] := ( - <| - "uri" -> doc["uri"], - "diagnostics" -> ToAssociation/@DiagnoseDoc[doc["uri"], doc] - |> -) - - -publishDiagnostics[state_WorkState, uri_String] := ( - sendResponse[state["client"], <| - "method" -> "textDocument/publishDiagnostics", - "params" -> <| - "uri" -> uri, - "diagnostics" -> ToAssociation[DiagnoseDoc[state["openedDocs"][uri]]] - |> - |>] - (* Null *) -) - - -clearDiagnostics[state_WorkState, uri_String] := ( - sendResponse[state["client"], <| - "method" -> "textDocument/publishDiagnostics", - "params" -> <| - "uri" -> uri, - "diagnostics" -> {} - |> - |>] -) - - (* ::Subsection:: *) (*Invalid Notification*) @@ -1420,45 +1604,39 @@ doNextScheduledTask[state_WorkState] := ( // Replace[{ _?MissingQ :> ( Pause[0.001]; - state + {"Continue", state} ), task_ServerTask :> Block[ { newState = state // ReplaceKeyBy["scheduledTasks" -> Rest] }, - {task["type"], DateDifference[task["scheduledTime"], Now, "Second"] // InputForm} // LogDebug; + {task["type"], Chop[DateDifference[task["scheduledTime"], Now, "Second"], 10*^-6] // InputForm} // LogInfo; task["type"] // Replace[{ - "PublishDiagnostics" :> ( - newState = newState - (* delete all same tasks out of date *) - // ReplaceKeyBy["scheduledTasks" -> DeleteCases[_?(t \[Function] ( - t["type"] == "PublishDiagnostics" && - t["scheduledTime"] < Now && - t["params"] == task["params"] - ))]]; - - FirstPosition[ - newState["scheduledTasks"], - t_ /; ( - t["type"] == "PublishDiagnostics" && - t["params"] == task["params"] - ), - Missing["NotFound"], - {1} - ] // Replace[{ - (* if there will not be a same task in the future, do it now *) - _?MissingQ :> If[!MissingQ[task["callback"]], - task["callback"][newState, task["params"]] + 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"]]&) + ] ), - "CheckUpgrades" :> ( - state - // checkUpgrades; + "InitialCheck" :> ( + newState + // initialCheck ), - "JustContinue" -> Null, + "JustContinue" :> {"Continue", newState}, _ :> ( FirstPosition[ newState["scheduledTasks"], @@ -1475,15 +1653,16 @@ doNextScheduledTask[state_WorkState] := ( (* 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" -> <||>|>]], *) task["callback"][newState, task["params"]] - (* // AbsoluteTiming - // Apply[(LogDebug[{task["type"], #1}];#2)&] *), + // AbsoluteTiming + // Apply[(LogInfo[{task["type"], #1}];#2)&], sendResponse[newState["client"], <| "id" -> task["id"], "error" -> ServerError[ "InternalError", "There is no callback function for this scheduled task." ] - |>] + |>]; + {"Continue", newState} ], (* find a recent duplicate request *) _ :> If[!MissingQ[task["duplicateFallback"]], @@ -1493,19 +1672,23 @@ doNextScheduledTask[state_WorkState] := ( sendResponse[newState["client"], <| "id" -> task["id"], "error" -> ServerError[ - "ContentModified", + "RequestCancelled", "There is a more recent duplicate request." ] - |>] + |>]; + {"Continue", newState} ] }] ) - }]; - newState + }] ] }] -) + // If[WolframLanguageServer`CheckReturnTypeQ, + Replace[err:Except[{_String, _WorkState}] :> LogError[err]], + Identity + ] +) (* ::Section:: *) @@ -1542,13 +1725,7 @@ saveConfig[newConfig_Association] := With[ defaultConfig = <| - "lastCheckForUpgrade" -> DateString[Today], - (* default delays (seconds) *) - "diagnosticsDelay" -> 5.0, - "signatrueHelpDelay" -> 0.5, - "documentSymbolDelay" -> 3.0, - "documentHighlightDelay" -> 0.5, - "documentColorDelay" -> 5.0 + "lastCheckForUpgrade" -> DateString[Today] |> @@ -1556,28 +1733,25 @@ defaultConfig = <| (*check upgrades*) -checkUpgrades[state_WorkState] := With[ - { - checkInterval = Quantity[2, "Days"] - }, - - If[DateDifference[DateObject[state["config"]["lastCheckForUpgrade"]], Today] < checkInterval, +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 - ]; - Return[] + ], + (* check for upgrade if not checked for more than checkInterval days *) + checkGitRepo[state]; + (* ReplaceKey[state["config"], "lastCheckForUpgrade" -> DateString[Today]] + // saveConfig *) ]; - - (* check for upgrade if not checked for more than checkInterval days *) - checkGitRepo[state]; - checkDependencies[state]; - - (* ReplaceKey[state["config"], "lastCheckForUpgrade" -> DateString[Today]] - // saveConfig *) - -] + {"Continue", state} +) checkGitRepo[state_WorkState] := ( Check[Needs["GitLink`"], @@ -1587,7 +1761,7 @@ checkGitRepo[state_WorkState] := ( state ]; Return[] - ]; + ] // Quiet; If[!GitLink`GitRepoQ[WolframLanguageServer`RootDirectory], showMessage[ @@ -1619,11 +1793,22 @@ checkGitRepo[state_WorkState] := ( ]; ) +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 + ) +] + checkDependencies[state_WorkState] := With[ { dependencies = { - {"AST", "0.15"}, - {"Lint", "0.15"} + {"CodeParser", "1.0"}, + {"CodeInspector", "1.0"} } }, @@ -1636,24 +1821,19 @@ checkDependencies[state_WorkState] := With[ Return[] ]; - Table[ - PacletManager`PacletInformation[depinfo] - // Replace[{ - {} :> (StringRiffle[depinfo, "-"]), - _ :> (Nothing) - }], - {depinfo, dependencies} - ] // Replace[{depInstalls__} :> ( - showMessage[ - StringJoin["These dependencies with correct versions need to be installed or upgraded: ", - StringRiffle[{depInstalls}, ", "], ", ", + 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 section](https://github.com/kenkangxgwe/lsp-wl/blob/master/README.md#installation) for details." - ], - "Warning", - state - ] - )]; + "Please see the [Installation](https://github.com/kenkangxgwe/lsp-wl/blob/master/README.md#installation) section for details." + ]] + // showMessage[#, "Warning", state]& + ) + ] ] diff --git a/src/WolframLanguageServer/Specification.wl b/src/WolframLanguageServer/Specification.wl index 61cffb2..5e134d0 100644 --- a/src/WolframLanguageServer/Specification.wl +++ b/src/WolframLanguageServer/Specification.wl @@ -7,7 +7,7 @@ BeginPackage["WolframLanguageServer`Specification`"] -Construct[ClearAll, Context[] <> "*"] +ClearAll[Evaluate[Context[] <> "*"]]; LspPosition::usage = "is type of Position interface in LSP." @@ -19,6 +19,9 @@ TextDocumentContentChangeEvent::usage = "is an event describing a change to a te the new text is considered to be the full content of the document." MarkupContent::usage = "is the type of MarkupContent 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." @@ -211,6 +214,23 @@ DeclareType[Hover, <| "range" -> _LspRange |>] +DeclareType[SignatureHelp, <| + "signatures" -> {___SignatureInformation}, + "activeSignature" -> _Integer, + "activeParameter" -> _Integer +|>] + +DeclareType[SignatureInformation, <| + "label" -> _String, + "documentation" -> _String | _MarkupContent, + "parameters" -> {___ParameterInformation} +|>] + +DeclareType[ParameterInformation, <| + "label" -> _String | {_Integer, _Integer}, + "documentation" -> _String | _MarkupContent +|>] + DeclareType[DocumentSymbol, <| "name" -> _String, "detail" -> _String, @@ -241,7 +261,7 @@ DeclareType[CompletionItem, <| "kind" -> _Integer, "detail" -> _String, "documentation" -> _String | _MarkupContent, - "preselect" -> _String, + "preselect" -> _?BooleanQ, "filterText" -> _String, "insertText" -> _String, "insertTextFormat" -> _Integer, @@ -249,20 +269,19 @@ DeclareType[CompletionItem, <| "commitCharacters" -> {___String} |>] - DeclareType[Location, <| "uri" -> DocumentUri, - "range" -> LspRange + "range" -> _LspRange |>] DeclareType[DocumentHighlight, <| - "range" -> LspRange, + "range" -> _LspRange, "kind" -> _Integer |>] DeclareType[ColorInformation, <| - "range" -> LspRange, - "color" -> LspColor + "range" -> _LspRange, + "color" -> _LspColor |>] DeclareType[LspColor, <| diff --git a/src/WolframLanguageServer/TableGenerator.wl b/src/WolframLanguageServer/TableGenerator.wl index e2ffee1..01a087c 100644 --- a/src/WolframLanguageServer/TableGenerator.wl +++ b/src/WolframLanguageServer/TableGenerator.wl @@ -47,19 +47,44 @@ GenerateUnicodeTable[file_] := Module[ ) :> (longName -> FromDigits[unicode, 16])] // Association; - Keys[AliasToLongName] - // Map[StringTake[#, 1]&] - // DeleteDuplicates - // DeleteCases[_?LetterQ] - // Prepend["["] - // DeleteDuplicates + (* aliases without A-Za-z. *) + NonLetterAliases = Keys[AliasToLongName] + // Cases[_?(StringMatchQ[Except[WordCharacter]..])]; + + (* + Non-letters which are only used as prefix of aliases that contains letters. + We know there is only one such leader, i.e. `$`, but we generate it here. + *) + NonLetterLeaders = Complement[ + (* all non-letter prefix *) + Keys[AliasToLongName] + // Map[StringTake[#, 1]&] + // Prepend["["] (* for long names `[` is a prefix *) + // DeleteDuplicates + // DeleteCases[_?LetterQ], + (* non-letter prefix *) + NonLetterAliases + // Map[StringTake[#, 1]&] + // DeleteDuplicates + ]; + + (* Use `Function` to do lexical replacement in `Unevaluated` *) + NonLetterLeaders // (leaders \[Function] ( - Write[file, Unevaluated[WolframLanguageServer`UnicodeTable`UnicodeLeaders = leaders]]; + Write[file, Unevaluated[WolframLanguageServer`UnicodeTable`NonLetterLeaders = leaders]]; leaders )); WriteLine[file, "\n"]; + NonLetterAliases + // (aliases \[Function] ( + Write[file, Unevaluated[WolframLanguageServer`UnicodeTable`NonLetterAliases = aliases]]; + aliases + )); + + WriteLine[file, "\n"]; + AliasToLongName // (assoc \[Function] ( Write[file, Unevaluated[WolframLanguageServer`UnicodeTable`AliasToLongName = assoc]]; @@ -74,62 +99,15 @@ GenerateUnicodeTable[file_] := Module[ assoc )); - (* Join[ - AliasToLongName - // KeyValueMap[{alias, longName} \[Function] ( - <| - "label" -> StringJoin[ - LongNameToUnicode[longName] - // Replace[{ - code_?(LessThan[16^^E000]) :> ( - FromCharacterCode[code] - ), - _ -> " " - }], "\t", - alias, "\t\t", - longName - ], - "kind" -> 1, - (* "detail" -> , *) - "filterText" -> alias, - "insertText" -> StringDrop[longName, 1], - "data" -> <|"type" -> "Alias"|> - |> - )], - LongNameToUnicode - // KeyValueMap[{longName, unicode} \[Function] ( - <| - "label" -> StringJoin[ - unicode - // Replace[{ - code_?(LessThan[16^^E000]) :> ( - FromCharacterCode[code] - ), - _ -> " " - }], "\t", - longName - ], - "kind" -> 1, - (* "detail" -> , *) - "filterText" -> StringDrop[longName, 1], - "insertText" -> StringDrop[longName, 1], - "data" -> <|"type" -> "LongName"|> - |> - )] - ] - // (items \[Function] ( - Write[file, Unevaluated[WolframLanguageServer`UnicodeTable`UnicodeCompletionItems = items]] - ));*) - WriteLine[file, "\n"]; WriteLine[file, "EndPackage[]"]; Close[file]; - + ] End[] -EndPackage[] \ No newline at end of file +EndPackage[] diff --git a/src/WolframLanguageServer/TextDocument.wl b/src/WolframLanguageServer/TextDocument.wl index 6fd0897..0c7b59f 100644 --- a/src/WolframLanguageServer/TextDocument.wl +++ b/src/WolframLanguageServer/TextDocument.wl @@ -1,7 +1,7 @@ (* ::Package:: *) (* Wolfram Language Server TextDocument *) -(* Author: kenkangxgwe , +(* Author: kenkangxgwe , huxianglong *) @@ -15,12 +15,14 @@ CreateTextDocument::usage = "CreateTextDocument[textDocumentItem_TextDocumentIte ChangeTextDocument::usage = "ChangeTextDocument[doc_TextDocument, change_TextDocumentContentChangeEvent] returns the changed doc from the input." HoverInfo::usage = "HoverInfo[hoverKind, {literal, docTag}] Basic information to generate a hover message." GetHoverInfo::usage = "GetHoverInfo[doc_TextDocument, pos_LspPosition] gives the HoverInfo and range at the given position." +GetFunctionName::usage = "GetFunctionName[doc_TextDocument, pos_LspPosition] gives the function being called at the position." GetTokenPrefix::usage = "GetTokenPrefix[doc_TextDocument, pos_LspPosition] gives the prefix of the token before the position." DiagnoseDoc::usage = "DiagnoseDoc[doc_TextDocument] gives diagnostic information of the doc." ToDocumentSymbol::usage = "ToDocumentSymbol[doc_TextDocument] gives the DocumentSymbol structure of a document." FindDefinitions::usage = "FindDefinitions[doc_TextDocument, pos_LspPosition] gives the definitions of the symbol at the position in the Top level." 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." 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." @@ -30,23 +32,40 @@ ClearAll[Evaluate[Context[] <> "*"]] Needs["DataType`"] Needs["WolframLanguageServer`Logger`"] Needs["WolframLanguageServer`Specification`"] -Needs["AST`"] -Needs["Lint`"] -(* After AST 0.15: prevent AST parsed into PackageNode and ContextNode *) -AST`Abstract`Private`abstractTopLevel = List /* Append[{}] +Needs["CodeParser`"] +Needs["CodeInspector`"] Needs["WolframLanguageServer`AstPatterns`"] Needs["WolframLanguageServer`ColorTable`"] +(* ::Section:: *) +(*CodeParser Shims*) + + +(* + Prevents AST being parsed into PackageNode and ContextNode. + Only monitors top-level comma issues. +*) +CodeParser`Abstract`Private`abstractTopLevel = ( + Replace[ + #, + AstPattern["Function"][functionName:"CodeParser`Comma", arguments_, data_] :> ( + CodeParser`AbstractSyntaxErrorNode[AbstractSyntaxError`CommaTopLevel, arguments, data] + ), + {1} + ]& + /* List + /* Append[{}] +) + (* ::Section:: *) (*TextDocument*) DeclareType[TextDocument, <| "uri" -> _DocumentUri, - "text" -> {__String}, + "text" -> {___String}, "version" -> _Integer, - "cell" -> _CellNode, "lastUpdate" -> _DateObject |>] @@ -140,160 +159,165 @@ DeclareType[CellNode, <| "level" -> _Integer | Infinity, "style" -> _String | _AdditionalStyle, "name" -> _String, - "range" -> {_Integer, _Integer | Infinity}, + "range" -> {_Integer, _Integer}, "selectionRange" -> _LspRange, - "codeRange" -> {{_Integer, _Integer | Infinity}...}, + "codeRange" -> {{_Integer, _Integer}...}, "children" -> {___CellNode} |>] - divideCells[doc_TextDocument] := ( - - Table[ - Part[doc@"text", line] - // StringCases["(* "~~"::"~~Shortest[style___]~~"::"~~" *)" :> style] - // Replace[ - {"Package", ___} :> ( - {AdditionalStyle["Package"]} - (* If[If[ScriptFileQ[doc["uri"]], line == 2, line == 1], - {"Package"}, - {AdditionalStyle["Ignored"]} - ] *) - ) - ] - // Replace[{ - {style_, ___} :> ( - style - // Replace[{ - (* "Package" :> {URLParse[doc["uri"], "Path"] // Last, (* name line: *) False}, *) - _String :> ( - Part[doc@"text", line + 1] - // Replace[( - (* if the line contains style definitions, discard it *) - _?(StringContainsQ["(* "~~"::"~~___~~"::"~~" *)"]) | - (* if the line contains strings outside the comment, discard it *) - _?(Not@*StringMatchQ[WhitespaceCharacter___~~"(*"~~___~~"*)"~~WhitespaceCharacter___]) - ) -> ""] - // StringCases["(*"~~Longest[name___]~~"*)" :> name] - // Replace[{ - {name_String, ___} :> {name, (* name line: *) True}, - {} -> {"", (* name line: *) False} - }] - ), - _AdditionalStyle -> {"", (* name line: *) False} - }] - // Apply[{name, nameLineQ} \[Function] CellNode[<| - "style" -> style, - "name" -> (name // Replace[ - "" -> "[unnamed]" - ]), - "range" -> {line, Infinity}, - style - // Replace[{ - _String :> ( - "selectionRange" -> ( - If[nameLineQ, - Part[doc@"text", line + 1] - // StringPosition["(*"<>name<>"*)"] - // First, - Part[doc@"text", line] - // StringPosition["(* "~~"::"<>style<>"::"~~" *)"] - // First - ] // Apply[{start, end} \[Function] LspRange[<| - "start" -> LspPosition[<| - "line" -> line, - "character" -> start - |>], - "end" -> LspPosition[<| - "line" -> line, - "character" -> end - |>] - |>]] - ) - ), - _AdditionalStyle -> ( - Nothing - ) - }], - "codeRange" -> {{ - If[nameLineQ, line + 2, line + 1], - Infinity - }} - |>]] - ), - {(* current line does not contain styles *)} -> Nothing - }], - {line, Length@doc@"text"} + Position[ + doc["text"], + (* matches style line *) + _?(StringContainsQ["(* " ~~ "::" ~~ Shortest[style___] ~~ "::" ~~ " *)"]), + {1}, Heads -> False ] - // Prepend[CellNode[<| - "style" -> AdditionalStyle["File"], - "name" -> "", - "range" -> {1, Infinity}, - "codeRange" -> {{If[doc["text"] // First // StringStartsQ["#!"], 2, 1], Infinity}}, - "children" -> {} - |>]] + // Flatten + // Append[Length[doc["text"]] + 1] + // Prepend[0] + // BlockMap[Apply[constructCellNode[doc, #1, #2]&], #, 2, 1]& // Fold[InsertCell] - // Curry[TerminateCell][Length[doc["text"]]] - + // TerminateCell + // Replace[err:Except[_CellNode] :> ( + LogError["The result of devideCells is not a CellNode " <> ToString[err]] + )] ) -InsertCell[rootCell_CellNode, newCell_CellNode] := ( +constructCellNode[doc_TextDocument, styleLine_Integer, endLine_Integer] := Block[ + { + style, title = Missing["Untitled"], codeStart + }, - rootCell["children"] - // Replace[{ - _?MissingQ|{} :> ( - rootCell - // ReplaceKey[{"codeRange", -1 ,-1} -> (First[newCell["range"]] - 1)] - // ReplaceKeyBy[{"codeRange", -1} -> Replace[{s_, e_} /; (s > e) -> Nothing]] - // If[HeadingQ[newCell["style"]], - ReplaceKey["children" -> {newCell}], - (* not a heading style, append its code range to its parent *) - ReplaceKeyBy["codeRange" -> (Join[#, newCell["codeRange"]]&)] - ] - ), - {preCells___, lastCell_CellNode} :> ( - If[HeadingQ[newCell["style"]] \[Implies] (HeadingLevel[lastCell["style"]] < HeadingLevel[newCell["style"]]), - (* includes the new cell in the last child *) - rootCell - // ReplaceKey["children" -> {preCells, InsertCell[lastCell, newCell]}], - (* append the new cell after the last child *) - rootCell - // ReplaceKey["children" -> { - preCells, - TerminateCell[lastCell, First[newCell["range"]] - 1], - newCell - }] - ] - ) - }] + style = If[styleLine == 0, + AdditionalStyle["File"], + Part[doc["text"], styleLine] + // StringCases["(* "~~"::"~~Shortest[style___]~~"::"~~" *)" :> style] + // First + // Replace["" -> "[empty]"] + ]; -) + 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] + ]; + + CellNode[<| + "level" -> If[HeadingQ[style], HeadingLevel[style], Infinity], + "style" -> style, + "name" -> (title // Replace[(_?MissingQ|"") :> ""]), + "range" -> {styleLine, endLine - 1}, + "selectionRange" -> If[!MissingQ[title], + Part[doc["text"], styleLine + 1] + // StringPosition[title] + // First + // Apply[{startPos, endPos} \[Function] ( + LspRange[<| + "start" -> LspPosition[<| + "line" -> styleLine, + "character" -> startPos - 1 + |>], + "end" -> LspPosition[<| + "line" -> styleLine, + "character" -> endPos + |>] + |>] + )], + LspRange[<| + "start" -> LspPosition[<| + "line" -> styleLine - 1, + "character" -> 0 + |>], + "end" -> LspPosition[<| + "line" -> styleLine, + "character" -> 0 + |>] + |>] + ], + "codeRange" -> If[codeStart < endLine, {{codeStart, endLine - 1}}, {}], + "children" -> {} + |>] +] -TerminateCell[cell_CellNode, endLine_Integer] := ( - cell - // ReplaceKey[{"range", -1} -> endLine] - // ReplaceKeyBy[{"codeRange", -1, -1} -> Replace[{ - Infinity -> endLine - }]] - // ReplaceKeyBy[{"codeRange", -1} -> Replace[{s_, e_} /; (s > e) -> Nothing]] - // ReplaceKeyBy[{"children"} -> Replace[{ - _?MissingQ :> {}, - {children___, lastChild_CellNode} :> { - children, - TerminateCell[lastChild, endLine] - } - }]] +findCodeLine[doc_TextDocument, currentLine_Integer] := ( + If[currentLine <= Length[doc["text"]] && + Part[doc["text"], currentLine] === "", + findCodeLine[doc, currentLine + 1], + currentLine + ] ) -CellContainsLine[indexLine_Integer][cell_CellNode] := ( - indexLine // Between[cell["range"]] +InsertCell[rootCell_CellNode, nextCell_CellNode] := ( + If[Length[rootCell["children"]] > 0 && + Last[rootCell["children"]]["level"] < nextCell["level"], + (* includes the new cell in the last child *) + rootCell + // ReplaceKeyBy[{"children", -1} -> (InsertCell[#, nextCell]&)], + rootCell + // 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, + (* removes codeRange *) + ReplaceKey[{"range", -1} -> ( + First[First[nextCell["codeRange"]]] - 1 + )] + /* ReplaceKey["codeRange" -> {}], + Identity + ] + ]] + ] +) + +TerminateCell[rootcell_CellNode] := ( + rootcell + // If[Length[rootcell["children"]] > 0, + ReplaceKeyBy[{"children", -1} -> TerminateCell], + Identity + ] + // (newRootCell \[Function] ( + newRootCell + // ReplaceKey[{"range", -1} -> ( + Max[ + Last[newRootCell["range"]], + newRootCell["children"] + // Replace[{ + {___, lastChild_} :> Last[lastChild["range"]], + _ -> -Infinity + }], + newRootCell["codeRange"] + // Replace[{ + {___, {_, last_}} :> last, + _ -> -Infinity + }] + ] + )] + )) ) -HeadingCellQ[cellNode_CellNode] := HeadingQ[cellNode["style"]] +AnonymousStyleQ[style:(_String|_AdditionalStyle)] := MatchQ[style, "Package" | _AdditionalStyle] HeadingQ[style:(_String|_AdditionalStyle)] := KeyMemberQ[HeadingLevel, style] HeadingLevel = <| "Title" -> 1, @@ -325,11 +349,15 @@ CellToAST[doc_TextDocument, {startLine_, endLine_}] := ( {StringRepeat::intp (* before 12.0 *)} ] // Quiet, #]& - // AST`ParseString + // CodeParser`CodeParse // Part[#, 2]& ) +CellContainsLine[indexLine_Integer][cell_CellNode] := ( + indexLine // Between[cell["range"]] +) + GetCodeRangeAtPosition[doc_TextDocument, pos_LspPosition] := With[ { @@ -341,15 +369,26 @@ GetCodeRangeAtPosition[doc_TextDocument, pos_LspPosition] := With[ cell_CellNode?(CellContainsLine[line]) :> cell["codeRange"], {}, {0, Infinity} ] - // SelectFirst[Curry[Between, 2][line]] + // SelectFirst[Between[line, #]&] ] +FindAllCodeRanges[doc_TextDocument] := ( + + Cases[ + divideCells[doc], + node_CellNode :> node["codeRange"], + {0, Infinity} + ] + // Catenate + // Map[ToLspRange[doc, #]&] +) + + (* ::Section:: *) (*AST utils*) - NodeDataContainsPosition[pos:{_Integer, _Integer}][data_] := NodeDataContainsPosition[data, pos] NodeDataContainsPosition[data_Association, pos:{_Integer, _Integer}] := ( CompareNodePosition[{data}, pos] === 0 @@ -366,11 +405,11 @@ NodeContainsPosition[node_, pos:{_Integer, _Integer}] := ( -1, if node is before pos; 0, if node contains pos; +1, if node is after pos; - default, node does not have AST`Source information. + default, node does not have CodeParser`Source information. *) CompareNodePosition[node_, {line_Integer, col_Integer}, default_:Missing["NotFound"]] := With[ { - source = node // Last // Key[AST`Source] + source = node // Last // Key[CodeParser`Source] }, Which[ @@ -405,7 +444,7 @@ SourceToRange[{{startLine_, startCol_}, {endLine_, endCol_}}] := ( ToDocumentSymbol[doc_TextDocument] := ( doc // divideCells - // Curry[ToDocumentSymbolImpl, 2][doc] + // ToDocumentSymbolImpl[doc, #]& // Flatten ) @@ -413,44 +452,46 @@ ToDocumentSymbol[doc_TextDocument] := ( ToDocumentSymbolImpl[doc_TextDocument, node_] := ( node // Replace[{ - _CellNode?HeadingCellQ :> (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[Curry[CellToAST, 2][doc]] - // Flatten - // Map[ToDocumentSymbolImpl], - {} - ], - If[!MissingQ[node["children"]], - node["children"] - // Map[Curry[ToDocumentSymbolImpl, 2][doc]], - {} - ] - ] // Flatten - ) - |>]), + _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[Curry[CellToAST, 2][doc]] + // Map[CellToAST[doc, #]&] // Flatten // Map[ToDocumentSymbolImpl], {} ], If[!MissingQ[node["children"]], node["children"] - // Map[Curry[ToDocumentSymbolImpl, 2][doc]], + // Map[ToDocumentSymbolImpl[doc, #]&], {} ] ] // Flatten @@ -461,7 +502,7 @@ ToDocumentSymbolImpl[doc_TextDocument, node_] := ( ToDocumentSymbolImpl[node_] := ( node // Replace[{ - AstPattern["Definable"][{head_, func_, key_, data_}] :> ( + AstPattern["Definable"][head_, func_, key_, data_] :> ( DocumentSymbol[<| "name" -> ( key @@ -477,69 +518,63 @@ ToDocumentSymbolImpl[node_] := ( ), "range" -> ( data - // Key[AST`Source] + // Key[CodeParser`Source] // SourceToRange ), "selectionRange" -> ( head // Last - // Key[AST`Source] + // Key[CodeParser`Source] // SourceToRange ), "children" -> ({}) |>] ), - AstPattern["Set"][{head_, op_, data_}] :> Block[ + AstPattern["Set"][head_, op:"Set", data_] :> Block[ { symbolList }, - ( - symbolList - // Map[Replace[{ - AstPattern["Symbol"][<|"symbolName" -> symbolName_, "data" -> symbolData_|>] :> ( - DocumentSymbol[<| - "name" -> ( - symbolName - ), - "kind" -> SymbolKind["Variable"], - "range" -> ( - data - // Key[AST`Source] - // SourceToRange - ), - "selectionRange" -> ( - symbolData - // Key[AST`Source] - // SourceToRange - ), - "children" -> ({}) - |>] - ), - _ -> Nothing - }]] - ) + Replace[symbolList, { + AstPattern["Symbol"][<|"symbolName" -> symbolName_, "data" -> symbolData_|>] :> ( + DocumentSymbol[<| + "name" -> ( + symbolName + ), + "kind" -> SymbolKind["Variable"], + "range" -> ( + data + // Key[CodeParser`Source] + // SourceToRange + ), + "selectionRange" -> ( + symbolData + // Key[CodeParser`Source] + // SourceToRange + ), + "children" -> ({}) + |>] + ), + _ -> Nothing + }, {1}] /; ( - op == "Set" - && ( - head - // GetSymbolList - // ((symbolList = #)&) - // MissingQ - // Not - ) + head + // GetSymbolList + // ((symbolList = #)&) + // MissingQ + // Not ) ], - AstPattern["Set"][{head_, op_, tag_, data_}] :> ( + AstPattern["Set"][head_, op_, tag_, data_] :> ( DocumentSymbol[<| "name" -> ( FirstCase[ head, AstPattern["Symbol"][<|"symbolName" -> rootSymbol_|>] :> rootSymbol, - "[unnamed]", + "", AstLevelspec["LeafNodeWithSource"] ] ), @@ -559,32 +594,32 @@ ToDocumentSymbolImpl[node_] := ( ), "range" -> ( data - // Key[AST`Source] + // Key[CodeParser`Source] // SourceToRange ), "selectionRange" -> ( head // Last - // Key[AST`Source] + // Key[CodeParser`Source] // SourceToRange ), "children" -> ({}) |>] ), - AstPattern["CompoundExpression"][{exprs_}] :> ( + AstPattern["CompoundExpression"][exprs_] :> ( exprs // Map[ToDocumentSymbolImpl] ), - (* lhsNode[AST`CallNode[caller_, {callees__}, _]] :> ({}), - lhsNode[AST`LeafNode[Symbol, symbolName_String, _]] :> ({}), *) + (* lhsNode[CodeParser`CallNode[caller_, {callees__}, _]] :> ({}), + lhsNode[CodeParser`LeafNode[Symbol, symbolName_String, _]] :> ({}), *) _ -> Nothing }] ) -ToLspRange[doc_TextDocument, {startLine_Integer, endLine_Integer}] := <| +ToLspRange[doc_TextDocument, {startLine_Integer, endLine_Integer}] := LspRange[<| "start" -> LspPosition[<| "line" -> startLine - 1, "character" -> 0 @@ -601,20 +636,19 @@ ToLspRange[doc_TextDocument, {startLine_Integer, endLine_Integer}] := <| |> ] ] -|> +|>] GetSymbolList[node_] := ( node // Replace[{ - AstPattern["Function"][{functionName_, arguments_}] - /; (functionName == "List") :> ( + AstPattern["Function"][functionName:"List", arguments_] :> ( arguments // Map[GetSymbolList] // Catenate // Replace[_?(MemberQ[_?MissingQ]) -> Missing["NotSymbolList"]] ), - symbolNode:AstPattern["Symbol"][{}] :> ( + symbolNode:AstPattern["Symbol"][] :> ( {symbolNode} ), _ -> {Missing["NotSymbolList"]} @@ -630,7 +664,7 @@ GetHoverInfo[doc_TextDocument, pos_LspPosition] := With[ { line = pos["line"] + 1, character = pos["character"] + 1 }, - + GetCodeRangeAtPosition[doc, pos] // Replace[lineRange:{_Integer, _Integer} :> ( CellToAST[doc, lineRange] @@ -644,13 +678,15 @@ GetHoverInfo[doc_TextDocument, pos_LspPosition] := With[ ] // Most // Replace[indices_List :> { - getHoverInfoImpl[ast, indices, {}] + getHoverInfoImpl[ast, indices] + // Reap + // Last // Flatten // DeleteDuplicates, (* get range *) ast // Extract[indices] // Last - // Key[AST`Source] + // Key[CodeParser`Source] // Replace[{ _?MissingQ -> Nothing, source_ :> SourceToRange[source] @@ -666,32 +702,88 @@ GetHoverInfo[doc_TextDocument, pos_LspPosition] := With[ ] -getHoverInfoImpl[ast_, {}, res_List] := res -getHoverInfoImpl[ast_, {index_Integer, restIndices___}, res_] := ( +getHoverInfoImpl[ast_, {}] := Null +getHoverInfoImpl[ast_, {index_Integer, restIndices___}] := ( Part[ast, index] - // (node \[Function] getHoverInfoImpl[ - node, - {restIndices}, - Append[res, node // Replace[{ - AstPattern["Symbol"][{symbolName_}] :> ( - HoverInfo["Message", {symbolName, "usage"}] - ), - integer:AstPattern["Integer"][{integerLiteral_}] :> ( - HoverInfo["Number", {integerLiteral, AST`FromNode[integer]}] - ), - real:AstPattern["Real"][{realLiteral_}] :> ( - HoverInfo["Number", {realLiteral, AST`FromNode[real]}] - ), - AstPattern["Function"][{functionName_}] /; Length[{restIndices}] == 0 :> ( - HoverInfo["Operator", {functionName}] - (* TODO(kenkangxgwe): to know whether the cursor is hovering on the operator *) - ), - AstPattern["MessageName"][{symbolName_, message_}] :> ( - HoverInfo["Message", {symbolName, AST`FromNode[message]}] - ), - _ :> Nothing - }]] - ]) + // (node \[Function] ( + node + // { + If[Length[{restIndices}] == 0, + Replace[{ + AstPattern["Function"][functionName_] :> ( + HoverInfo["Operator", {functionName}] + (* TODO(kenkangxgwe): to know whether the cursor is hovering on the operator *) + ), + _ -> Nothing + }], + Nothing + ], + Replace[{ + AstPattern["Symbol"][symbolName_] :> ( + HoverInfo["Message", {symbolName, "usage"}] + ), + integer:AstPattern["Integer"][integerLiteral_] :> ( + HoverInfo["Number", {integerLiteral, CodeParser`FromNode[integer]}] + ), + real:AstPattern["Real"][realLiteral_] :> ( + HoverInfo["Number", {realLiteral, CodeParser`FromNode[real]}] + ), + AstPattern["MessageName"][symbolName_, message_] :> ( + HoverInfo["Message", {symbolName, CodeParser`FromNode[message]}] + ), + _ -> Nothing + }] + } // Through // Map[Sow]; + getHoverInfoImpl[node, {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] + ) + }] + ) + }] ) @@ -708,17 +800,18 @@ GetTokenPrefix[doc_TextDocument, pos_LspPosition] := With[ // Replace[lineRange:{rangeStartLine_Integer, _Integer} :> ( (* get token list *) Take[doc["text"], lineRange] - // Curry[StringRiffle]["\n"] - // AST`TokenizeString + // StringRiffle[#, "\n"]& + // CodeParser`CodeTokenize // SelectFirst[NodeContainsPosition[{ line - rangeStartLine + 1, pos["character"] }]] - // Replace[ - AstPattern["Token"][{tokenString_, data_}] :> ( - StringTake[tokenString, pos["character"] - Part[data[AST`Source], 1, 2] + 1] - ) - ] + // 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 -> "" @@ -734,21 +827,25 @@ DiagnoseDoc[doc_TextDocument] := ( doc["text"] // Replace[{_String?(StringStartsQ["#!"]), restLines___} :> ({"", restLines})] - // Curry[StringRiffle]["\n"] + // StringRiffle[#, "\n"]& // Replace[err:Except[_String] :> (LogError[doc]; "")] - // Lint`LintString + // CodeInspector`CodeInspect // Replace[_?FailureQ -> {}] - // ReplaceAll[Lint`Lint[tag_, description_, severity_, data_] :> Diagnostic[<| + // ReplaceAll[CodeInspector`InspectionObject[tag_, description_, severity_, data_] :> Diagnostic[<| "range" -> ( data - // Key[AST`Source] + // Key[CodeParser`Source] // SourceToRange + // If[tag == "GroupMissingCloser", + ReplaceKey[#, "end" -> #["start"]]&, + Identity + ] ), "severity" -> ( severity // Replace[{ "Fatal" -> "Error", - "ImplicitTimes"|"Formatting"|"Remark" -> "Hint" + "Formatting"|"Remark" -> "Hint" }] // Replace[{ "Warning" :> ( @@ -766,9 +863,9 @@ DiagnoseDoc[doc_TextDocument] := ( StringJoin[ "[", tag, "] ", description - // ReplaceAll[{Lint`Format`LintMarkup[content_, ___] :> ( + (* // ReplaceAll[{CodeInspector`Format`LintMarkup[content_, ___] :> ( ToString[content] - )}] + )}] *) // StringReplace["``" -> "\""] ] ) @@ -845,7 +942,7 @@ FindDocumentHighlight[doc_TextDocument, pos_LspPosition] := ( Options[FindScopeOccurence] = { - (* only document-wide search, not pooject-wide currently *) + (* only document-wide search, not project-wide currently *) "GlobalSearch" -> True, "BodySearch" -> True } @@ -864,22 +961,23 @@ FindScopeOccurence[doc_TextDocument, pos_LspPosition, o:OptionsPattern[]] := Blo name = FirstCase[ ast, - AstPattern["Symbol"][{symbolName_}] + AstPattern["Symbol"][symbolName_] ?(NodeContainsPosition[{line, character}]) :> ( symbolName ), Missing["NotFound"], AstLevelspec["LeafNodeWithSource"] - ] // Replace[_?MissingQ :> Return[{{}, {}}]]; + ] + // Replace[_?MissingQ :> Return[{{}, {}}]]; LogDebug["Searching for " <> name]; FirstCase[ ast, ( - AstPattern["Scope"][{head_, body_, op_}] + AstPattern["Scope"][head_, body_, op_] ?(NodeContainsPosition[{line, character}]) | - AstPattern["Delayed"][{head_, body_, op_}] + AstPattern["Delayed"][head_, body_, op_] ?(NodeContainsPosition[{line, character}]) ) :> Block[ { @@ -921,7 +1019,7 @@ FindScopeOccurence[doc_TextDocument, pos_LspPosition, o:OptionsPattern[]] := Blo ], "TopLevelOnly" :> ( CellToAST[doc, {1, doc["text"] // Length}] - // Map[Curry[FindTopLevelSymbols][name]] + // Map[FindTopLevelSymbols[#, name]&] // Catenate ), _ -> {} @@ -936,9 +1034,8 @@ ScopeHeadSymbolSource["With", head_, name_String] := ( FirstCase[ (* elements in the list *) Part[head, 2], - AstPattern["InscopeSet"][{symbolName_, symbolData_}] - /; (symbolName == name) :> ( - symbolData[AST`Source] + AstPattern["InscopeSet"][symbolName:name, symbolData_] :> ( + symbolData[CodeParser`Source] // Replace[ _?MissingQ :> ( LogDebug["With"]; @@ -953,9 +1050,8 @@ ScopeHeadSymbolSource["With", head_, name_String] := ( ScopeHeadSymbolSource["Function", head_, name_String] := ( Replace[head, { - AstPattern["Symbol"][{symbolName_, data_}] - /; (symbolName == name) :> ( - data[AST`Source] + AstPattern["Symbol"][symbolName:name, data_] :> ( + data[CodeParser`Source] // Replace[ _?MissingQ :> ( LogDebug["Function"]; @@ -964,13 +1060,11 @@ ScopeHeadSymbolSource["Function", head_, name_String] := ( ) ] ), - AstPattern["Function"][{functionName_, arguments_}] - /; (functionName == "List") :> ( + AstPattern["Function"][functionName:"List", arguments_] :> ( FirstCase[ arguments, - AstPattern["Symbol"][{symbolName_, data_}] - /; (symbolName == name) :> ( - data[AST`Source] + AstPattern["Symbol"][symbolName:name, data_] :> ( + data[CodeParser`Source] // Replace[ _?MissingQ :> ( LogDebug["Function"]; @@ -989,14 +1083,12 @@ ScopeHeadSymbolSource["Function", head_, name_String] := ( ScopeHeadSymbolSource["Block"|"Module"|"DynamicModule", head_, name_String] :=( Replace[head, { - AstPattern["Function"][{functionName_, arguments_}] - /; (functionName == "List") :> ( + AstPattern["Function"][functionName:"List", arguments_] :> ( FirstCase[ arguments, - AstPattern["InscopeSet"][{symbolName_, symbolData_}] | - AstPattern["Symbol"][<|"symbolName" -> symbolName_, "data" -> symbolData_|>] - /; (symbolName == name) :> ( - symbolData[AST`Source] + AstPattern["InscopeSet"][symbolName:name, symbolData_] | + AstPattern["Symbol"][<|"symbolName" -> symbolName:name, "data" -> symbolData_|>] :> ( + symbolData[CodeParser`Source] // Replace[ _?MissingQ :> ( LogDebug["Block"]; @@ -1016,10 +1108,9 @@ ScopeHeadSymbolSource["Block"|"Module"|"DynamicModule", head_, name_String] :=( DelayedHeadPatternNameSource[head_, name_String] := ( Join[ Cases[ - Part[head, 2], - AstPattern["DelayedPattern"][{patternName_, patternData_}] - /; (patternName == name) :> ( - patternData[AST`Source] + head, + AstPattern["DelayedPattern"][patternName:name, patternData_] :> ( + patternData[CodeParser`Source] // Replace[ _?MissingQ :> ( LogDebug["Delayed"]; @@ -1031,11 +1122,10 @@ DelayedHeadPatternNameSource[head_, name_String] := ( {0, Infinity} ], Replace[head, { - AstPattern["Function"][{functionName_, arguments_}] - /; (functionName == "Condition") :> ( + AstPattern["Function"][functionName:"Condition", arguments_] :> ( arguments // Last - // Curry[StaticLocalSource][name] + // StaticLocalSource[#, name]& ), _ :> {} }] @@ -1046,9 +1136,8 @@ DelayedHeadPatternNameSource[head_, name_String] := ( StaticLocalSource[node_, name_String] := ( Cases[ node, - AstPattern["Symbol"][{symbolName_, data_}] - /; (symbolName == name) :> ( - data[AST`Source] + AstPattern["Symbol"][symbolName:name, data_] :> ( + data[CodeParser`Source] (* happens when an operator is parsed as a symbol *) // Replace[_?MissingQ -> Nothing] ), @@ -1063,8 +1152,8 @@ DynamicLocalSource[node_, name_String] := ( StaticLocalSource[node, name], Cases[ node, - AstPattern["Scope"][{head_, body_, op_}] | - AstPattern["Delayed"][{head_, body_, op_}] :> Block[ + AstPattern["Scope"][head_, body_, op_] | + AstPattern["Delayed"][head_, body_, op_] :> Block[ { headSource }, @@ -1095,31 +1184,27 @@ DynamicLocalSource[node_, name_String] := ( FindTopLevelSymbols[node_, name_String] := ( node // Replace[{ - AstPattern["Set"][{head_, op_}] :> Block[ + AstPattern["Set"][head_, op:"Set"] :> Block[ { symbolSource }, {symbolSource} /; ( - op == "Set" - && ( - head - // GetSymbolList - // FirstCase[ - AstPattern["Symbol"][{symbolName_, data_}] - /; (functionName == name) :> ( - data[AST`Source] - ) - ] - // ((symbolSource = #)&) - // MissingQ - // Not - ) + head + // GetSymbolList + // FirstCase[ + AstPattern["Symbol"][symbolName:name, data_] :> ( + data[CodeParser`Source] + ) + ] + // ((symbolSource = #)&) + // MissingQ + // Not ) ], - AstPattern["Set"][{head_}] :> Block[ + AstPattern["Set"][head_] :> Block[ { symbolSource }, @@ -1128,14 +1213,13 @@ FindTopLevelSymbols[node_, name_String] := ( /; ( FirstCase[ head, - AstPattern["Symbol"][{}], + AstPattern["Symbol"][], Missing["NotFound"], AstLevelspec["LeafNodeWithSource"] ] // Replace[{ - AstPattern["Symbol"][{symbolName_, data_}] - /; (symbolName == name) :> ( - data[AST`Source] + AstPattern["Symbol"][symbolName:name, data_] :> ( + data[CodeParser`Source] // Replace[{ source_ :> ( LogDebug[symbolName]; @@ -1152,9 +1236,9 @@ FindTopLevelSymbols[node_, name_String] := ( ) ], - AstPattern["CompoundExpression"][{exprs_}] :> ( + AstPattern["CompoundExpression"][exprs_] :> ( exprs - // Map[Curry[FindTopLevelSymbols][name]] + // Map[FindTopLevelSymbols[#, name]&] // Catenate ), @@ -1175,11 +1259,11 @@ FindDocumentColor[doc_TextDocument] := With[ Join[ Cases[ ast, - AstPattern["NamedColor"][{color_, data_}] :> ( + AstPattern["NamedColor"][color_, data_] :> ( ColorInformation[<| "range" -> ( data - // Key[AST`Source] + // Key[CodeParser`Source] // SourceToRange ), "color" -> ( @@ -1193,11 +1277,11 @@ FindDocumentColor[doc_TextDocument] := With[ ], Cases[ ast, - AstPattern["ColorModel"][{model_, params_, data_}] :> With[ + AstPattern["ColorModel"][model_, params_, data_] :> With[ { color = ( params - // Map[AST`FromNode] + // Map[CodeParser`FromNode] // Apply[ToExpression[model]] ) }, @@ -1206,7 +1290,7 @@ FindDocumentColor[doc_TextDocument] := With[ ColorInformation[<| "range" -> ( data - // Key[AST`Source] + // Key[CodeParser`Source] // SourceToRange ), "color" -> ( @@ -1241,7 +1325,7 @@ GetColorPresentation[doc_TextDocument, color_LspColor, range_LspRange] := With[ ColorPresentation[<| "label" -> ( rgbColor - // Curry[ColorConvert][colorSpace] + // ColorConvert[#, colorSpace]& // InputForm // ToString ) diff --git a/src/WolframLanguageServer/Token.wl b/src/WolframLanguageServer/Token.wl index e87544f..3b2e153 100644 --- a/src/WolframLanguageServer/Token.wl +++ b/src/WolframLanguageServer/Token.wl @@ -1,6 +1,6 @@ (* ::Package:: *) -(* Wolfram Language Server Documentation *) +(* Wolfram Language Server Token *) (* Author: kenkangxgwe , huxianglong *) @@ -15,8 +15,8 @@ TokenDocumentation::usage = "TokenDocumentation[token_String, tag_String, o] ret \"Format\" -> \"plaintext\" | \"markdown\" " GetHoverAtPosition::usage = "GetHoverAtPosition[doc_TextDocument, pos_LspPosition] gives the text to be shown when hover at the given position." +GetSignatureHelp::usage = "GetSignatureHelp[doc_TextDocument, pos_LspPosition] gives the signature help at the position." GetTokenCompletionAtPostion::usage = "GetTokenCompletionAtPostion[doc_TextDocument, pos_LspPosition] gives a list of suggestions for completion." -GetTriggerKeys::usage = "GetTriggerKeys[] returns a list of characters that trigger a completion request when input." GetTriggerKeyCompletion::usage = "GetTriggerKeyCompletion[] returns a list of available leader keys." GetIncompleteCompletionAtPosition::usage = "GetIncompleteCompletionAtPosition[doc_TextDocument, pos_LspPosition, leader_String] gives a list of completion items according to the leader key." @@ -35,7 +35,8 @@ Needs["WolframLanguageServer`TextDocument`"] Options[TokenDocumentation] = { - "Format" -> MarkupKind["Markdown"] (* | MarkupKind["Plaintext"] *) + "Format" -> MarkupKind["Markdown"] (* | MarkupKind["Plaintext"] *), + "Header" -> True } TokenDocumentation[token_String, tag_String, o: OptionsPattern[]] := ( @@ -49,7 +50,10 @@ TokenDocumentation[token_String, tag_String, o: OptionsPattern[]] := ( tag // Replace[{ "usage" :> ( { - GenHeader[token, tag], + If[OptionValue["Header"], + GenHeader[token, tag, "Format" -> OptionValue["Format"]], + Nothing + ], boxText // If[(OptionValue["Format"]) === MarkupKind["Markdown"], splitUsage @@ -58,20 +62,23 @@ TokenDocumentation[token_String, tag_String, o: OptionsPattern[]] := ( /* Flatten /* DeleteCases[""], GenPlainText ], - GenFooter[token] + GenOptions[token, "Format" -> OptionValue["Format"]] } // Flatten - // Curry[StringRiffle]["\n\n"] + // StringRiffle[#, "\n\n"]& ), _(* other messages *) :> ( { - GenHeader[token, tag], + If[OptionValue["Header"], + GenHeader[token, tag, "Format" -> OptionValue["Format"]], + Nothing + ], boxText // If[OptionValue["Format"] === MarkupKind["Markdown"], GenMarkdownText, GenPlainText ] } // Flatten - // Curry[StringRiffle]["\n"] + // StringRiffle[#, "\n"]& ) }] ) @@ -91,12 +98,23 @@ splitUsage[usageText_String] := ( ))] (* split header and content *) // Map[{ - StringCases[StartOfString ~~ (header:Shortest["\!\(\*"~~box__~~"\)"]) ~~ content__ ~~ EndOfString :> {header, content}], + StringCases[StartOfString ~~ + (header:( + Shortest[(* box: *) "\!\(\*"~~__~~"\)"] ~~ (( + (Whitespace|"") ~~ + (","|"or"|("," ~~ Whitespace ~~ "or")) ~~ + Whitespace ~~ + Shortest[(* box: *) "\!\(\*"~~__~~"\)"] + )...)) + ) ~~ + content__ ~~ + EndOfString :> {header, content} + ], Identity } /* Through /* Replace[{ {{{header_, content_}}, _} :> ( - {header, content} + {header, content} ), {{(* no matches *)}, origin_} :> ( (* use fallback method *) @@ -130,23 +148,37 @@ groupBalanceQ[text_String] := And[ -GenHeader[token_String, tag_String] := ( + +Options[GenHeader] = { + "Format" -> MarkupKind["Markdown"] (* | MarkupKind["Plaintext"] *) +} +GenHeader[token_String, tag_String, o: OptionsPattern[]] := ( tag // Replace[{ "usage" :> ( token // { Identity, - Curry[GetUri][tag], + GetUri[#, tag]&, GenAttributes } // Through - // Apply[StringTemplate["**`1`** `2` (_`3`_)\n"]] + // Apply[ + If[OptionValue["Format"] == MarkupKind["Markdown"], + StringTemplate["**`1`** `2` (`3`)\n"], + StringTemplate["`1`\t(`3`)\n"] + ] + ] ), _ :> ( - StringJoin[ - "```mathematica\n", - token, "::", tag, "\n", - "```" + If[OptionValue["Format"] == MarkupKind["Markdown"], + StringJoin[ + "```mathematica\n", + token, "::", tag, "\n", + "```" + ], + StringJoin[ + token, "::", tag, "\n" + ] ] ) }] @@ -166,28 +198,40 @@ GetUri[token_String, tag_String] := ( }] ) - -GenAttributes[token_String] := ( +Options[GenAttributes] = { + "Format" -> MarkupKind["Markdown"] (* | MarkupKind["Plaintext"] *) +} +GenAttributes[token_String, o:OptionsPattern[]] := ( Attributes[token] // Replace[_Attributes -> {}] - // Curry[StringRiffle][", "] + // StringRiffle[#, ", "]& ) -GenFooter[token_String] := ({ +Options[GenOptions] = { + "Format" -> MarkupKind["Markdown"] (* | MarkupKind["Plaintext"] *) +} +GenOptions[token_String, o:OptionsPattern[]] := ( token // StringTemplate["Options[``]"] // ToExpression // Replace[_Options -> {}] - // Map[Curry[ToString][InputForm]] + // Map[ToString[#, InputForm]&] // Replace[{options__} :> ( - { - "__Options:__", - "``` mathematica", - options, - "```" - } // Curry[StringRiffle]["\n"] + If[OptionValue["Format"] == MarkupKind["Markdown"], + { + "__Options:__", + "``` mathematica", + options, + "```" + }, + { + "Options:", + options + } + ] )] -}) + // StringRiffle[#, "\n"]& +) GenPlainText[boxText_String] := ( @@ -278,7 +322,8 @@ BoxToText[input_, o:OptionsPattern[]] := Block[ StringReplace[{ "~" -> "\\~", "`" -> "\\`", - "*" -> "\\*" + "*" -> "\\*", + "\\" -> "\\\\" }], Identity ] @@ -303,9 +348,11 @@ PUACharactersReplaceRule = { "\[LeftAssociation]" -> "<|", "\[RightAssociation]" -> "|>", "\[InvisibleSpace]" -> " ", - "\[Null]" -> "" + "\[Null]" -> "", + "\[ExponentialE]" -> "\[ScriptE]" } + (* ::Section:: *) (*Hover*) @@ -313,15 +360,14 @@ PUACharactersReplaceRule = { GetHoverAtPosition[doc_TextDocument, pos_LspPosition] := ( GetHoverInfo[doc, pos] // Apply[printHoverText] - ) printHoverText[hoverInfo_List, range_LspRange:Automatic] := ( hoverInfo - // Map[printHoverTextImpl] - // Curry[StringRiffle]["\n\n---\n\n"] + // printHoverTextImpl + // StringRiffle[#, "\n\n---\n\n"]& // Replace[{ "" -> Null, text_String :> ( @@ -338,9 +384,10 @@ printHoverText[hoverInfo_List, range_LspRange:Automatic] := ( ) }] ) -printHoverTextImpl[hoverInfo_HoverInfo] := ( - hoverInfo - // Replace[{ + + +printHoverTextImpl[hoverInfo_List] := ( + Replace[hoverInfo, { HoverInfo["Operator", {symbolName_String}] :> ( TokenDocumentation[symbolName, "usage"] ), @@ -358,10 +405,53 @@ printHoverTextImpl[hoverInfo_HoverInfo] := ( ] }] ) + }, {1}] +) + + +(* ::Section:: *) +(*SignatureHelp*) + + +GetSignatureHelp[doc_TextDocument, pos_LspPosition] := ( + GetFunctionName[doc, pos] + // Replace[{ + _?MissingQ -> Null, + functionName_ :> ( + printSignatureHelp[functionName] + ) }] ) +printSignatureHelp[functionName_String] := ( + + If[Names["System`"<>functionName] === {}, Return[Null]]; + + ToExpression[functionName<>"::"<>"usage"] + // Replace[{ + _MessageName -> {}, + usageText_String :> ( + splitUsage[usageText] + // MapAt[GenPlainText, {All, 1}] + // MapAt[GenMarkdownText, {All, 2}] + ) + }] + // Map[Apply[{label, documentation} \[Function] ( + SignatureInformation[<| + "label" -> label, + "documentation" -> MarkupContent[<| + "kind" -> MarkupKind["Markdown"], + "value" -> documentation + |>], + "parameters" -> {} + |>] + )]] + // SignatureHelp[<|"signatures" -> #|>]& + +) + + (* ::Section:: *) (*Completion*) @@ -419,36 +509,58 @@ GetTokenCompletionAtPostion[doc_TextDocument, pos_LspPosition] := With[ ] -GetTriggerKeys[] := UnicodeLeaders +(* SetDelayed is not needed. Cache it when define it. *) +GetTriggerKeyCompletion[doc_TextDocument, pos_LspPosition] := ( + If[GetTokenPrefix[doc, pos] == "\\\\", + (* double-triggered *) + GetAliasCompletion["\\", pos], + NonLetterAliasCompletionItems + ] +) -GetTriggerKeyCompletion[] := ( - AliasToLongName - // KeySelect[StringStartsQ[UnicodeLeaders]] - // KeyValueMap[{alias, longName} \[Function] With[ - { - unicode = LongNameToUnicode[longName] - }, - - CompletionItem[<| - "label" -> StringJoin[ - If[unicode < 16^^E000, - FromCharacterCode[unicode], - "" - ], "\t", - alias , "\t\t", - "\\[", longName, "]" - ], - "kind" -> CompletionItemKind["Text"], - "detail" -> StringJoin["0x", StringPadLeft[IntegerString[unicode, 16] // ToUpperCase, 4, "0"]], - "filterText" -> alias, - "sortText" -> alias, - "insertText" -> "[" <> longName <> "]", - "data" -> <| - "type" -> "Alias" - |> - |>] - ]] +NonLetterAliasCompletionItems = ( + Join[ + AliasToLongName + // KeyTake[NonLetterAliases] + // KeyValueMap[{alias, longName} \[Function] With[ + { + unicode = LongNameToUnicode[longName] + }, + + CompletionItem[<| + "label" -> StringJoin[ + If[unicode < 16^^E000, + FromCharacterCode[unicode], + "" + ], "\t", + alias // StringReplace[" " -> "\[SpaceIndicator]"] , "\t\t", + "\\[", longName, "]" + ], + "kind" -> CompletionItemKind["Text"], + "detail" -> StringJoin["0x", StringPadLeft[IntegerString[unicode, 16] // ToUpperCase, 4, "0"]], + "filterText" -> alias, + "sortText" -> alias, + "insertText" -> "[" <> longName <> "]", + "data" -> <| + "type" -> "Alias" + |> + |>] + ]], + Table[ + CompletionItem[<| + "label" -> leader <> "...", + "kind" -> CompletionItemKind["Text"], + "detail" -> "More input needed to show the completion.", + "filterText" -> leader, + "sortText" -> leader <> "...", + "insertText" -> leader, + "data" -> <| + "type" -> "Alias" + |> + |>], {leader, NonLetterLeaders} + ] + ] ) @@ -472,8 +584,8 @@ GetAliasCompletion[prefix_String, pos_LspPosition] := ( "kind" -> CompletionItemKind["Text"], "detail" -> StringJoin["0x", StringPadLeft[IntegerString[unicode, 16] // ToUpperCase, 4, "0"]], (* label has some extra information, thus cannot be used to sort, filter or insert *) - "sortText" -> StringDrop[alias, StringLength[prefix] - 1], - "filterText" -> StringDrop[alias, StringLength[prefix] - 1], + "sortText" -> alias (*StringDrop[alias, StringLength[prefix] - 1]*), + "filterText" -> alias (*StringDrop[alias, StringLength[prefix] - 1]*), "textEdit" -> TextEdit[<| "range" -> LspRange[<| "start" -> LspPosition[<| @@ -535,8 +647,7 @@ GetIncompleteCompletionAtPosition[doc_TextDocument, pos_LspPosition] := ( ), (* other aliases *) prefix_ :> ( - prefix - // Curry[GetAliasCompletion][pos] + GetAliasCompletion[prefix, pos] ) }] diff --git a/src/WolframLanguageServer/UnicodeTable.wl b/src/WolframLanguageServer/UnicodeTable.wl index 2fdd2c2..110c063 100644 --- a/src/WolframLanguageServer/UnicodeTable.wl +++ b/src/WolframLanguageServer/UnicodeTable.wl @@ -7,9 +7,20 @@ BeginPackage["WolframLanguageServer`UnicodeTable`"] ClearAll[Evaluate[Context[] <> "*"]] -WolframLanguageServer`UnicodeTable`UnicodeLeaders = - {"[", "&", "\\", "$", "-", ".", "'", "`", "!", "|", "=", "~", "<", ">", ":", - " ", "*", "+", "^", "@", "]", "_", "#", ","} +WolframLanguageServer`UnicodeTable`NonLetterLeaders = {"$"} + + +WolframLanguageServer`UnicodeTable`NonLetterAliases = + {"\\%", "\\&", "\\_", "\\-", "-", "--", "...", "'", "''", "`", "``", "|", + "=>", "~", "!~", "=~", "~=", "!~=", "~==", "~~", "!~~", "!=~", "!~==", + "<~", ">~", "!<~", "!>~", "!=", "!<", "!>", "<=", "!<=", ">=", "!>=", "/", "!>/", "===", "!===", ".=", ":", "==", " ||", "\\|", "!||", + "!", "*", "+-", "-+", ".", "\\", "^", "&&", "!&&", "||", " ->", "<-", + "<->", "@>", ":>", "->", " =>", " <=", "<=>", "-->", "<--", "<==", "==>", + "<-->", "<==>", "[[", "]]", "<", ">", "<|", "|>", "_", ":-(", ":-@", ":-|", + "#", "+", " ", " ", "\\,", " ", "\\>", "\\:", " ", "\\;", "- ", + "- ", "\\!", "- ", "- ", "['", "]'", "[\"", "]\"", "[", "]", "@", + ",", " |", "!|", "<-> "} WolframLanguageServer`UnicodeTable`AliasToLongName = @@ -334,62 +345,116 @@ WolframLanguageServer`UnicodeTable`AliasToLongName = "𝕎" -> "DoubleStruckCapitalW", "dsX" -> "DoubleStruckCapitalX", "𝕏" -> "DoubleStruckCapitalX", "dsY" -> "DoubleStruckCapitalY", "𝕐" -> "DoubleStruckCapitalY", "dsZ" -> "DoubleStruckCapitalZ", - "ℤ" -> "DoubleStruckCapitalZ", "$a" -> "FormalA", "$b" -> "FormalB", - "$c" -> "FormalC", "$d" -> "FormalD", "$e" -> "FormalE", "$f" -> "FormalF", - "$g" -> "FormalG", "$h" -> "FormalH", "$i" -> "FormalI", "$j" -> "FormalJ", - "$k" -> "FormalK", "$l" -> "FormalL", "$m" -> "FormalM", "$n" -> "FormalN", - "$o" -> "FormalO", "$p" -> "FormalP", "$q" -> "FormalQ", "$r" -> "FormalR", - "$s" -> "FormalS", "$t" -> "FormalT", "$u" -> "FormalU", "$v" -> "FormalV", - "$w" -> "FormalW", "$x" -> "FormalX", "$y" -> "FormalY", "$z" -> "FormalZ", - "$A" -> "FormalCapitalA", "$B" -> "FormalCapitalB", - "$C" -> "FormalCapitalC", "$D" -> "FormalCapitalD", - "$E" -> "FormalCapitalE", "$F" -> "FormalCapitalF", - "$G" -> "FormalCapitalG", "$H" -> "FormalCapitalH", - "$I" -> "FormalCapitalI", "$J" -> "FormalCapitalJ", - "$K" -> "FormalCapitalK", "$L" -> "FormalCapitalL", - "$M" -> "FormalCapitalM", "$N" -> "FormalCapitalN", - "$O" -> "FormalCapitalO", "$P" -> "FormalCapitalP", - "$Q" -> "FormalCapitalQ", "$R" -> "FormalCapitalR", - "$S" -> "FormalCapitalS", "$T" -> "FormalCapitalT", - "$U" -> "FormalCapitalU", "$V" -> "FormalCapitalV", - "$W" -> "FormalCapitalW", "$X" -> "FormalCapitalX", - "$Y" -> "FormalCapitalY", "$Z" -> "FormalCapitalZ", - "$CapitalAlpha" -> "FormalCapitalAlpha", "$CapitalBeta" -> - "FormalCapitalBeta", "$CapitalGamma" -> "FormalCapitalGamma", - "$CapitalDelta" -> "FormalCapitalDelta", "$CapitalEpsilon" -> - "FormalCapitalEpsilon", "$CapitalZeta" -> "FormalCapitalZeta", - "$CapitalEta" -> "FormalCapitalEta", "$CapitalTheta" -> - "FormalCapitalTheta", "$CapitalIota" -> "FormalCapitalIota", - "$CapitalKappa" -> "FormalCapitalKappa", "$CapitalLambda" -> - "FormalCapitalLambda", "$CapitalMu" -> "FormalCapitalMu", - "$CapitalNu" -> "FormalCapitalNu", "$CapitalXi" -> "FormalCapitalXi", + "ℤ" -> "DoubleStruckCapitalZ", "$a" -> "FormalA", ".a" -> "FormalA", + "$b" -> "FormalB", ".b" -> "FormalB", "$c" -> "FormalC", ".c" -> "FormalC", + "$d" -> "FormalD", ".d" -> "FormalD", "$e" -> "FormalE", ".e" -> "FormalE", + "$f" -> "FormalF", ".f" -> "FormalF", "$g" -> "FormalG", ".g" -> "FormalG", + "$h" -> "FormalH", ".h" -> "FormalH", "$i" -> "FormalI", ".i" -> "FormalI", + "$j" -> "FormalJ", ".j" -> "FormalJ", "$k" -> "FormalK", ".k" -> "FormalK", + "$l" -> "FormalL", ".l" -> "FormalL", "$m" -> "FormalM", ".m" -> "FormalM", + "$n" -> "FormalN", ".n" -> "FormalN", "$o" -> "FormalO", ".o" -> "FormalO", + "$p" -> "FormalP", ".p" -> "FormalP", "$q" -> "FormalQ", ".q" -> "FormalQ", + "$r" -> "FormalR", ".r" -> "FormalR", "$s" -> "FormalS", ".s" -> "FormalS", + "$t" -> "FormalT", ".t" -> "FormalT", "$u" -> "FormalU", ".u" -> "FormalU", + "$v" -> "FormalV", ".v" -> "FormalV", "$w" -> "FormalW", ".w" -> "FormalW", + "$x" -> "FormalX", ".x" -> "FormalX", "$y" -> "FormalY", ".y" -> "FormalY", + "$z" -> "FormalZ", ".z" -> "FormalZ", "$A" -> "FormalCapitalA", + ".A" -> "FormalCapitalA", "$B" -> "FormalCapitalB", + ".B" -> "FormalCapitalB", "$C" -> "FormalCapitalC", + ".C" -> "FormalCapitalC", "$D" -> "FormalCapitalD", + ".D" -> "FormalCapitalD", "$E" -> "FormalCapitalE", + ".E" -> "FormalCapitalE", "$F" -> "FormalCapitalF", + ".F" -> "FormalCapitalF", "$G" -> "FormalCapitalG", + ".G" -> "FormalCapitalG", "$H" -> "FormalCapitalH", + ".H" -> "FormalCapitalH", "$I" -> "FormalCapitalI", + ".I" -> "FormalCapitalI", "$J" -> "FormalCapitalJ", + ".J" -> "FormalCapitalJ", "$K" -> "FormalCapitalK", + ".K" -> "FormalCapitalK", "$L" -> "FormalCapitalL", + ".L" -> "FormalCapitalL", "$M" -> "FormalCapitalM", + ".M" -> "FormalCapitalM", "$N" -> "FormalCapitalN", + ".N" -> "FormalCapitalN", "$O" -> "FormalCapitalO", + ".O" -> "FormalCapitalO", "$P" -> "FormalCapitalP", + ".P" -> "FormalCapitalP", "$Q" -> "FormalCapitalQ", + ".Q" -> "FormalCapitalQ", "$R" -> "FormalCapitalR", + ".R" -> "FormalCapitalR", "$S" -> "FormalCapitalS", + ".S" -> "FormalCapitalS", "$T" -> "FormalCapitalT", + ".T" -> "FormalCapitalT", "$U" -> "FormalCapitalU", + ".U" -> "FormalCapitalU", "$V" -> "FormalCapitalV", + ".V" -> "FormalCapitalV", "$W" -> "FormalCapitalW", + ".W" -> "FormalCapitalW", "$X" -> "FormalCapitalX", + ".X" -> "FormalCapitalX", "$Y" -> "FormalCapitalY", + ".Y" -> "FormalCapitalY", "$Z" -> "FormalCapitalZ", + ".Z" -> "FormalCapitalZ", "$CapitalAlpha" -> "FormalCapitalAlpha", + ".CapitalAlpha" -> "FormalCapitalAlpha", "$CapitalBeta" -> + "FormalCapitalBeta", ".CapitalBeta" -> "FormalCapitalBeta", + "$CapitalGamma" -> "FormalCapitalGamma", ".CapitalGamma" -> + "FormalCapitalGamma", "$CapitalDelta" -> "FormalCapitalDelta", + ".CapitalDelta" -> "FormalCapitalDelta", "$CapitalEpsilon" -> + "FormalCapitalEpsilon", ".CapitalEpsilon" -> "FormalCapitalEpsilon", + "$CapitalZeta" -> "FormalCapitalZeta", ".CapitalZeta" -> + "FormalCapitalZeta", "$CapitalEta" -> "FormalCapitalEta", + ".CapitalEta" -> "FormalCapitalEta", "$CapitalTheta" -> + "FormalCapitalTheta", ".CapitalTheta" -> "FormalCapitalTheta", + "$CapitalIota" -> "FormalCapitalIota", ".CapitalIota" -> + "FormalCapitalIota", "$CapitalKappa" -> "FormalCapitalKappa", + ".CapitalKappa" -> "FormalCapitalKappa", "$CapitalLambda" -> + "FormalCapitalLambda", ".CapitalLambda" -> "FormalCapitalLambda", + "$CapitalMu" -> "FormalCapitalMu", ".CapitalMu" -> "FormalCapitalMu", + "$CapitalNu" -> "FormalCapitalNu", ".CapitalNu" -> "FormalCapitalNu", + "$CapitalXi" -> "FormalCapitalXi", ".CapitalXi" -> "FormalCapitalXi", "$CapitalOmicron" -> "FormalCapitalOmicron", - "$CapitalPi" -> "FormalCapitalPi", "$CapitalRho" -> "FormalCapitalRho", - "$CapitalSigma" -> "FormalCapitalSigma", - "$CapitalTau" -> "FormalCapitalTau", "$CapitalUpsilon" -> - "FormalCapitalUpsilon", "$CapitalPhi" -> "FormalCapitalPhi", - "$CapitalChi" -> "FormalCapitalChi", "$CapitalPsi" -> "FormalCapitalPsi", - "$CapitalOmega" -> "FormalCapitalOmega", "$Alpha" -> "FormalAlpha", - "$Beta" -> "FormalBeta", "$Gamma" -> "FormalGamma", - "$Delta" -> "FormalDelta", "$CurlyEpsilon" -> "FormalCurlyEpsilon", - "$Zeta" -> "FormalZeta", "$Eta" -> "FormalEta", "$Theta" -> "FormalTheta", - "$Iota" -> "FormalIota", "$Kappa" -> "FormalKappa", - "$Lambda" -> "FormalLambda", "$Mu" -> "FormalMu", "$Nu" -> "FormalNu", - "$Xi" -> "FormalXi", "$Omicron" -> "FormalOmicron", "$Pi" -> "FormalPi", - "$Rho" -> "FormalRho", "$FinalSigma" -> "FormalFinalSigma", - "$Sigma" -> "FormalSigma", "$Tau" -> "FormalTau", - "$Upsilon" -> "FormalUpsilon", "$CurlyPhi" -> "FormalCurlyPhi", - "$Chi" -> "FormalChi", "$Psi" -> "FormalPsi", "$Omega" -> "FormalOmega", - "$CurlyTheta" -> "FormalCurlyTheta", "$CurlyCapitalUpsilon" -> - "FormalCurlyCapitalUpsilon", "$Phi" -> "FormalPhi", - "$CurlyPi" -> "FormalCurlyPi", "$CapitalStigma" -> "FormalCapitalStigma", - "$Stigma" -> "FormalStigma", "$CapitalDigamma" -> "FormalCapitalDigamma", - "$Digamma" -> "FormalDigamma", "$CapitalKoppa" -> "FormalCapitalKoppa", - "$Koppa" -> "FormalKoppa", "$CapitalSampi" -> "FormalCapitalSampi", - "$Sampi" -> "FormalSampi", "$CurlyKappa" -> "FormalCurlyKappa", - "$CurlyRho" -> "FormalCurlyRho", "$Epsilon" -> "FormalEpsilon", - "wp" -> "WeierstrassP", "℘" -> "WeierstrassP", "\\wp" -> "WeierstrassP", - "al" -> "Aleph", "ℵ" -> "Aleph", "\\aleph" -> "Aleph", "be" -> "Bet", + ".CapitalOmicron" -> "FormalCapitalOmicron", + "$CapitalPi" -> "FormalCapitalPi", ".CapitalPi" -> "FormalCapitalPi", + "$CapitalRho" -> "FormalCapitalRho", ".CapitalRho" -> "FormalCapitalRho", + "$CapitalSigma" -> "FormalCapitalSigma", ".CapitalSigma" -> + "FormalCapitalSigma", "$CapitalTau" -> "FormalCapitalTau", + ".CapitalTau" -> "FormalCapitalTau", "$CapitalUpsilon" -> + "FormalCapitalUpsilon", ".CapitalUpsilon" -> "FormalCapitalUpsilon", + "$CapitalPhi" -> "FormalCapitalPhi", ".CapitalPhi" -> "FormalCapitalPhi", + "$CapitalChi" -> "FormalCapitalChi", ".CapitalChi" -> "FormalCapitalChi", + "$CapitalPsi" -> "FormalCapitalPsi", ".CapitalPsi" -> "FormalCapitalPsi", + "$CapitalOmega" -> "FormalCapitalOmega", ".CapitalOmega" -> + "FormalCapitalOmega", "$Alpha" -> "FormalAlpha", + ".Alpha" -> "FormalAlpha", "$Beta" -> "FormalBeta", + ".Beta" -> "FormalBeta", "$Gamma" -> "FormalGamma", + ".Gamma" -> "FormalGamma", "$Delta" -> "FormalDelta", + ".Delta" -> "FormalDelta", "$CurlyEpsilon" -> "FormalCurlyEpsilon", + ".CurlyEpsilon" -> "FormalCurlyEpsilon", "$Zeta" -> "FormalZeta", + ".Zeta" -> "FormalZeta", "$Eta" -> "FormalEta", ".Eta" -> "FormalEta", + "$Theta" -> "FormalTheta", ".Theta" -> "FormalTheta", + "$Iota" -> "FormalIota", ".Iota" -> "FormalIota", + "$Kappa" -> "FormalKappa", ".Kappa" -> "FormalKappa", + "$Lambda" -> "FormalLambda", ".Lambda" -> "FormalLambda", + "$Mu" -> "FormalMu", ".Mu" -> "FormalMu", "$Nu" -> "FormalNu", + ".Nu" -> "FormalNu", "$Xi" -> "FormalXi", ".Xi" -> "FormalXi", + "$Omicron" -> "FormalOmicron", ".Omicron" -> "FormalOmicron", + "$Pi" -> "FormalPi", ".Pi" -> "FormalPi", "$Rho" -> "FormalRho", + ".Rho" -> "FormalRho", "$FinalSigma" -> "FormalFinalSigma", + ".FinalSigma" -> "FormalFinalSigma", "$Sigma" -> "FormalSigma", + ".Sigma" -> "FormalSigma", "$Tau" -> "FormalTau", ".Tau" -> "FormalTau", + "$Upsilon" -> "FormalUpsilon", ".Upsilon" -> "FormalUpsilon", + "$CurlyPhi" -> "FormalCurlyPhi", ".CurlyPhi" -> "FormalCurlyPhi", + "$Chi" -> "FormalChi", ".Chi" -> "FormalChi", "$Psi" -> "FormalPsi", + ".Psi" -> "FormalPsi", "$Omega" -> "FormalOmega", + ".Omega" -> "FormalOmega", "$CurlyTheta" -> "FormalCurlyTheta", + ".CurlyTheta" -> "FormalCurlyTheta", "$CurlyCapitalUpsilon" -> + "FormalCurlyCapitalUpsilon", ".CurlyCapitalUpsilon" -> + "FormalCurlyCapitalUpsilon", "$Phi" -> "FormalPhi", ".Phi" -> "FormalPhi", + "$CurlyPi" -> "FormalCurlyPi", ".CurlyPi" -> "FormalCurlyPi", + "$CapitalStigma" -> "FormalCapitalStigma", ".CapitalStigma" -> + "FormalCapitalStigma", "$Stigma" -> "FormalStigma", + ".Stigma" -> "FormalStigma", "$CapitalDigamma" -> "FormalCapitalDigamma", + ".CapitalDigamma" -> "FormalCapitalDigamma", "$Digamma" -> "FormalDigamma", + ".Digamma" -> "FormalDigamma", "$CapitalKoppa" -> "FormalCapitalKoppa", + ".CapitalKoppa" -> "FormalCapitalKoppa", "$Koppa" -> "FormalKoppa", + ".Koppa" -> "FormalKoppa", "$CapitalSampi" -> "FormalCapitalSampi", + ".CapitalSampi" -> "FormalCapitalSampi", "$Sampi" -> "FormalSampi", + ".Sampi" -> "FormalSampi", "$CurlyKappa" -> "FormalCurlyKappa", + ".CurlyKappa" -> "FormalCurlyKappa", "$CurlyRho" -> "FormalCurlyRho", + ".CurlyRho" -> "FormalCurlyRho", "$Epsilon" -> "FormalEpsilon", + ".Epsilon" -> "FormalEpsilon", "wp" -> "WeierstrassP", + "℘" -> "WeierstrassP", "\\wp" -> "WeierstrassP", "al" -> "Aleph", + "ℵ" -> "Aleph", "\\aleph" -> "Aleph", "be" -> "Bet", "ℶ" -> "Bet", "\\beth" -> "Bet", "gi" -> "Gimel", "ℷ" -> "Gimel", "\\gimel" -> "Gimel", "da" -> "Dalet", "ℸ" -> "Dalet", "\\daleth" -> "Dalet", "d!" -> "DownExclamation", @@ -493,19 +558,20 @@ WolframLanguageServer`UnicodeTable`AliasToLongName = "≶" -> "LessGreater", "≷" -> "GreaterLess", "≸" -> "NotLessGreater", "≹" -> "NotGreaterLess", "⋚" -> "LessEqualGreater", - "⋛" -> "GreaterEqualLess", "lim" -> "Limit", - "\\lim" -> "Limit", "Mlim" -> "MaxLimit", "\\limsup" -> "MaxLimit", - "mlim" -> "MinLimit", "\\liminf" -> "MinLimit", "sub" -> "Subset", - "⊂" -> "Subset", "\\subset" -> "Subset", "sup" -> "Superset", - "⊃" -> "Superset", "\\supset" -> "Superset", - "!sub" -> "NotSubset", "⊂⃒" -> "NotSubset", - "!sup" -> "NotSuperset", "⊃⃒" -> "NotSuperset", - "sub=" -> "SubsetEqual", "⊆" -> "SubsetEqual", - "\\subseteq" -> "SubsetEqual", "sup=" -> "SupersetEqual", - "⊇" -> "SupersetEqual", "\\supseteq" -> "SupersetEqual", - "!sub=" -> "NotSubsetEqual", "⊈" -> "NotSubsetEqual", - "\\nsubseteq" -> "NotSubsetEqual", "!sup=" -> "NotSupersetEqual", - "⊉" -> "NotSupersetEqual", + "⋛" -> "GreaterEqualLess", "v>" -> "VectorGreater", + "v>=" -> "VectorGreaterEqual", "v<" -> "VectorLess", + "v<=" -> "VectorLessEqual", "lim" -> "Limit", "\\lim" -> "Limit", + "Mlim" -> "MaxLimit", "\\limsup" -> "MaxLimit", "mlim" -> "MinLimit", + "\\liminf" -> "MinLimit", "sub" -> "Subset", "⊂" -> "Subset", + "\\subset" -> "Subset", "sup" -> "Superset", "⊃" -> "Superset", + "\\supset" -> "Superset", "!sub" -> "NotSubset", + "⊂⃒" -> "NotSubset", "!sup" -> "NotSuperset", + "⊃⃒" -> "NotSuperset", "sub=" -> "SubsetEqual", + "⊆" -> "SubsetEqual", "\\subseteq" -> "SubsetEqual", + "sup=" -> "SupersetEqual", "⊇" -> "SupersetEqual", + "\\supseteq" -> "SupersetEqual", "!sub=" -> "NotSubsetEqual", + "⊈" -> "NotSubsetEqual", "\\nsubseteq" -> "NotSubsetEqual", + "!sup=" -> "NotSupersetEqual", "⊉" -> "NotSupersetEqual", "\\nsupseteq" -> "NotSupersetEqual", "⊏" -> "SquareSubset", "\\sqsubset" -> "SquareSubset", "⊐" -> "SquareSuperset", "\\sqsupset" -> "SquareSuperset", "⊑" -> @@ -529,30 +595,30 @@ WolframLanguageServer`UnicodeTable`AliasToLongName = "≽" -> "SucceedsSlantEqual", "⪰̸" -> "NotSucceedsEqual", "⋡" -> "NotSucceedsSlantEqual", "≿̸" -> "NotSucceedsTilde", - "uT" -> "UpTee", "⊥" -> "UpTee", "\\perp" -> "UpTee", - "\\bot" -> "UpTee", "perp" -> "Perpendicular", - "\\perpendicular" -> "Perpendicular", "prop" -> "Proportional", - "∝" -> "Proportional", "\\propto" -> "Proportional", - "∷" -> "Proportion", "===" -> "Congruent", - "≡" -> "Congruent", "\\equiv" -> "Congruent", - "!===" -> "NotCongruent", "≢" -> "NotCongruent", - "≍" -> "CupCap", "\\asymp" -> "CupCap", - "≭" -> "NotCupCap", "equi" -> "Equilibrium", - "⇌" -> "Equilibrium", "⇋" -> - "ReverseEquilibrium", "⥮" -> "UpEquilibrium", - "⥯" -> "ReverseUpEquilibrium", "rT" -> "RightTee", - "⊢" -> "RightTee", "\\vdash" -> "RightTee", "lT" -> "LeftTee", - "⊣" -> "LeftTee", "\\dashv" -> "LeftTee", "dT" -> "DownTee", - "⊤" -> "DownTee", "⊨" -> "DoubleRightTee", - "\\models" -> "DoubleRightTee", "⫤" -> "DoubleLeftTee", - "h=" -> "HumpEqual", "≏" -> "HumpEqual", - "\\bumpeq" -> "HumpEqual", "!h=" -> "NotHumpEqual", - "≏̸" -> "NotHumpEqual", "≎" -> "HumpDownHump", - "≎̸" -> "NotHumpDownHump", ".=" -> "DotEqual", - "≐" -> "DotEqual", "\\doteq" -> "DotEqual", ":" -> "Colon", - ":" -> "Colon", "==" -> "Equal", "⩵" -> "Equal", - "l=" -> "LongEqual", "sqrt" -> "Sqrt", "√" -> "Sqrt", - "\\surd" -> "Sqrt", "divides" -> "Divides", "&Divides;" -> "Divides", + "uT" -> "UpTee", "⊥" -> "UpTee", "\\bot" -> "UpTee", + "perp" -> "Perpendicular", "\\perp" -> "Perpendicular", + "prop" -> "Proportional", "∝" -> "Proportional", + "\\propto" -> "Proportional", "∷" -> "Proportion", + "===" -> "Congruent", "≡" -> "Congruent", + "\\equiv" -> "Congruent", "!===" -> "NotCongruent", + "≢" -> "NotCongruent", "≍" -> "CupCap", + "\\asymp" -> "CupCap", "≭" -> "NotCupCap", + "equi" -> "Equilibrium", "⇌" -> "Equilibrium", + "⇋" -> "ReverseEquilibrium", + "⥮" -> "UpEquilibrium", "⥯" -> + "ReverseUpEquilibrium", "rT" -> "RightTee", "⊢" -> "RightTee", + "\\vdash" -> "RightTee", "lT" -> "LeftTee", "⊣" -> "LeftTee", + "\\dashv" -> "LeftTee", "dT" -> "DownTee", "⊤" -> "DownTee", + "⊨" -> "DoubleRightTee", "\\models" -> "DoubleRightTee", + "⫤" -> "DoubleLeftTee", "h=" -> "HumpEqual", + "≏" -> "HumpEqual", "\\bumpeq" -> "HumpEqual", + "!h=" -> "NotHumpEqual", "≏̸" -> "NotHumpEqual", + "≎" -> "HumpDownHump", "≎̸" -> + "NotHumpDownHump", ".=" -> "DotEqual", "≐" -> "DotEqual", + "\\doteq" -> "DotEqual", ":" -> "Colon", ":" -> "Colon", + "==" -> "Equal", "⩵" -> "Equal", "l=" -> "LongEqual", + "sqrt" -> "Sqrt", "√" -> "Sqrt", "\\surd" -> "Sqrt", + "cbrti" -> "CubeRoot", "divides" -> "Divides", "&Divides;" -> "Divides", " ||" -> "DoubleVerticalBar", "∥" -> "DoubleVerticalBar", "\\Vert" -> "DoubleVerticalBar", "\\parallel" -> "DoubleVerticalBar", "\\|" -> "DoubleVerticalBar", "!||" -> "NotDoubleVerticalBar", @@ -575,18 +641,16 @@ WolframLanguageServer`UnicodeTable`AliasToLongName = "p*" -> "PermutationProduct", "t*" -> "TensorProduct", "t^" -> "TensorWedge", "star" -> "Star", "⋆" -> "Star", "sq" -> "Square", "□" -> "Square", "dia" -> "Diamond", - "⋄" -> "Diamond", "clap" -> "Laplacian", "del" -> "Del", - "∇" -> "Del", "\\nabla" -> "Del", "cdive" -> "Divergence", - "ccurl" -> "Curl", "prob" -> "ProbabilityPr", "expect" -> "ExpectationE", - "sha" -> "Shah", "\\" -> "Backslash", "∖" -> "Backslash", - "\\setminus" -> "Backslash", "⌢" -> "Cap", "\\frown" -> "Cap", - "⌣" -> "Cup", "\\smile" -> "Cup", "coprod" -> "Coproduct", - "∐" -> "Coproduct", "\\amalg" -> "Coproduct", - "int" -> "Integral", "∫" -> "Integral", "\\int" -> "Integral", - "\\intop" -> "Integral", "cint" -> "ContourIntegral", - "∮" -> "ContourIntegral", "\\oint" -> "ContourIntegral", - "∯" -> "DoubleContourIntegral", - "cccint" -> "CounterClockwiseContourIntegral", + "⋄" -> "Diamond", "del" -> "Del", "∇" -> "Del", + "\\nabla" -> "Del", "sha" -> "Shah", "\\" -> "Backslash", + "∖" -> "Backslash", "\\setminus" -> "Backslash", + "⌢" -> "Cap", "\\frown" -> "Cap", "⌣" -> "Cup", + "\\smile" -> "Cup", "coprod" -> "Coproduct", "∐" -> "Coproduct", + "\\amalg" -> "Coproduct", "int" -> "Integral", "∫" -> "Integral", + "\\int" -> "Integral", "\\intop" -> "Integral", + "cint" -> "ContourIntegral", "∮" -> "ContourIntegral", + "\\oint" -> "ContourIntegral", "∯" -> + "DoubleContourIntegral", "cccint" -> "CounterClockwiseContourIntegral", "∳" -> "CounterClockwiseContourIntegral", "ccint" -> "ClockwiseContourIntegral", "∲" -> "ClockwiseContourIntegral", "sum" -> "Sum", "∑" -> "Sum", @@ -609,7 +673,7 @@ WolframLanguageServer`UnicodeTable`AliasToLongName = "∂" -> "PartialD", "\\partial" -> "PartialD", "dd" -> "DifferentialD", "ⅆ" -> "DifferentialD", "DD" -> "CapitalDifferentialD", "ⅅ" -> - "CapitalDifferentialD", "cgrad" -> "Gradient", " ->" -> "RightArrow", + "CapitalDifferentialD", " ->" -> "RightArrow", "→" -> "RightArrow", "\\rightarrow" -> "RightArrow", "\\to" -> "RightArrow", "<-" -> "LeftArrow", "←" -> "LeftArrow", "\\leftarrow" -> "LeftArrow", "\\gets" -> "LeftArrow", @@ -1036,7 +1100,9 @@ WolframLanguageServer`UnicodeTable`LongNameToUnicode = "NotNestedGreaterGreater" -> 62504, "NotGreaterSlantEqual" -> 62505, "NotGreaterFullEqual" -> 8809, "LessGreater" -> 8822, "GreaterLess" -> 8823, "NotLessGreater" -> 8824, "NotGreaterLess" -> 8825, - "LessEqualGreater" -> 8922, "GreaterEqualLess" -> 8923, "Limit" -> 62520, + "LessEqualGreater" -> 8922, "GreaterEqualLess" -> 8923, + "VectorGreater" -> 62516, "VectorGreaterEqual" -> 62517, + "VectorLess" -> 62518, "VectorLessEqual" -> 62519, "Limit" -> 62520, "MaxLimit" -> 62521, "MinLimit" -> 62522, "Subset" -> 8834, "Superset" -> 8835, "NotSubset" -> 8836, "NotSuperset" -> 8837, "SubsetEqual" -> 8838, "SupersetEqual" -> 8839, "NotSubsetEqual" -> 8840, @@ -1060,16 +1126,16 @@ WolframLanguageServer`UnicodeTable`LongNameToUnicode = "DoubleRightTee" -> 8872, "DoubleLeftTee" -> 10980, "HumpEqual" -> 8783, "NotHumpEqual" -> 62465, "HumpDownHump" -> 8782, "NotHumpDownHump" -> 62466, "DotEqual" -> 8784, "Colon" -> 8758, - "Equal" -> 62513, "LongEqual" -> 63449, "Sqrt" -> 8730, "Divides" -> 8739, - "DoubleVerticalBar" -> 8741, "NotDoubleVerticalBar" -> 8742, "Not" -> 172, - "VerticalTilde" -> 8768, "Times" -> 215, "InvisibleTimes" -> 8290, - "" -> 8213, "PlusMinus" -> 177, "MinusPlus" -> 8723, "Minus" -> 8722, - "DivisionSlash" -> 8725, "Divide" -> 247, "CenterDot" -> 183, - "SmallCircle" -> 8728, "Cross" -> 62624, "CirclePlus" -> 8853, - "CircleMinus" -> 8854, "CircleTimes" -> 8855, "CircleDot" -> 8857, - "PermutationProduct" -> 62430, "TensorProduct" -> 62426, - "TensorWedge" -> 62427, "Star" -> 8902, "Square" -> 62752, - "Diamond" -> 8900, "Laplacian" -> 8710, "Del" -> 8711, + "Equal" -> 62513, "LongEqual" -> 63449, "Sqrt" -> 8730, "CubeRoot" -> 8731, + "Divides" -> 8739, "DoubleVerticalBar" -> 8741, + "NotDoubleVerticalBar" -> 8742, "Not" -> 172, "VerticalTilde" -> 8768, + "Times" -> 215, "InvisibleTimes" -> 8290, "" -> 62708, "PlusMinus" -> 177, + "MinusPlus" -> 8723, "Minus" -> 8722, "DivisionSlash" -> 8725, + "Divide" -> 247, "CenterDot" -> 183, "SmallCircle" -> 8728, + "Cross" -> 62624, "CirclePlus" -> 8853, "CircleMinus" -> 8854, + "CircleTimes" -> 8855, "CircleDot" -> 8857, "PermutationProduct" -> 62430, + "TensorProduct" -> 62426, "TensorWedge" -> 62427, "Star" -> 8902, + "Square" -> 62752, "Diamond" -> 8900, "Laplacian" -> 8710, "Del" -> 8711, "Divergence" -> 62423, "Curl" -> 62424, "ProbabilityPr" -> 62428, "ExpectationE" -> 62429, "Shah" -> 61725, "Backslash" -> 8726, "Cap" -> 8994, "Cup" -> 8995, "Coproduct" -> 8720, "Integral" -> 8747, diff --git a/test/MyCurryTest.wl b/test/MyCurryTest.wl deleted file mode 100644 index 21b310f..0000000 --- a/test/MyCurryTest.wl +++ /dev/null @@ -1,90 +0,0 @@ -(* ::Package:: *) - -BeginPackage["MyCurryTest`"] -ClearAll[Evaluate[Context[] <> "*"]] - - -Begin["`Private`"]; -ClearAll[Evaluate[Context[] <> "*"]] - - -TestingContext = "MyCurry`" -CurrentContext = "MyCurryTest`" -Needs[TestingContext] - - -{ - -VerificationTest[ - MyCurry[f][y][x], - f[x, y], - TestID -> "Currying a binary function 1" -], - -VerificationTest[ - MyCurry[f][y, x], - f[x, y], - TestID -> "Currying a binary function 2" -], - -VerificationTest[ - MyCurry[f, 2][x, y], - f[x, y], - TestID -> "Currying a binary function 3" -], - -VerificationTest[ - MyCurry[f, 2][x][y], - f[x, y], - TestID -> "Currying a binary function 4" -], - -VerificationTest[ - MyCurry[f, 0], - f[], - TestID -> "Currying a nullary function 1" -], - -VerificationTest[ - MyCurry[f, 0][x], - f[][x], - TestID -> "Currying a nullary function 2" -], - -VerificationTest[ - MyCurry[f, {}][x], - f[][x], - TestID -> "Currying a nullary function 3" -], - -VerificationTest[ - MyCurry[f, 2 -> {}][x][y], - f[], - TestID -> "Currying a nullary function using a binary operator" -], - -VerificationTest[ - MyCurry[f, 3][x][][y][z, a], - f[x, y, z][a], - TestID -> "Currying with multiple applications" -], - -VerificationTest[ - MyCurry[f, {5,4,2,3,1}][e, c, d, b, a], - f[a, b, c, d, e], - TestID -> "Currying with a slot map" -], - -VerificationTest[ - MyCurry[f, 6 -> {5,4,6,3,1}][e, f, d, b, a, c], - f[a, b, c, d, e], - TestID -> "Currying with a slot map and extended arity" -] - -} // Map@Curry[Sow]@CurrentContext - - -End[] - - -EndPackage[] diff --git a/test/PatternTemplateTest.wl b/test/PatternTemplateTest.wl new file mode 100644 index 0000000..9518659 --- /dev/null +++ b/test/PatternTemplateTest.wl @@ -0,0 +1,108 @@ +(* ::Package:: *) + +BeginPackage["PatternTemplateTest`"] +ClearAll[Evaluate[Context[] <> "*"]] + + +Begin["`Private`"]; +ClearAll[Evaluate[Context[] <> "*"]] + + +TestingContext = "PatternTemplate`" +CurrentContext = "PatternTemplateTest`" +Needs[TestingContext] + + +{ + +VerificationTest[ + PatternTemplate[_][], + _, + TestID -> "Blank" +], + +VerificationTest[ + PatternTemplate[oldName_][<||>], + _, + TestID -> "BlankWithOldName" +], + +VerificationTest[ + PatternTemplate[oldName_][<|"oldName" -> newName_|>], + newName_, + TestID -> "BlankWithNewName" +], + +VerificationTest[ + PatternTemplate[oldName_][<|"oldName" -> _List|>], + _List, + TestID -> "BlankWithNewPattern" +], + +VerificationTest[ + PatternTemplate[oldName_][<|"oldName" -> newName_List|>], + newName_List, + TestID -> "BlankWithNewNameAndPattern" +], + +VerificationTest[ + PatternTemplate[{{oldName_List}}][oldName_], + {{oldName_List}}, + TestID -> "PatternWithList" +], + +VerificationTest[ + PatternTemplate[{{oldName:_List|_Association}}][oldName:_?((Length[#] == 3)&)], + {{oldName:Except[Except[_?((Length[#] == 3)&)], _List|_Association]}}, + TestID -> "PatternWithListNewPattern 1" +], + +VerificationTest[ + PatternTemplate[{{oldName_?((Length[#] == 3)&)}}][oldName:_List|_Association], + {{(oldName:_List|_Association)?((Length[#] == 3)&)}}, + TestID -> "PatternWithListNewPattern 2" +], + +VerificationTest[ + PatternTemplate[{{oldName_:0}}][oldName:_List], + {{oldName:_List:0}}, + TestID -> "PatternWithOptional" +], + +VerificationTest[ + PatternTemplate[outer:{inner:{oldName:{___}}}][oldName_, inner_, outer_], + outer:{inner:{oldName:{___}}}, + TestID -> "NestedPatterns" +], + +VerificationTest[ + PatternTemplate[outer:{inner:{oldName:{other___}}}][<|"oldName" -> newName_, "inner" -> newInner_, "outer" -> newOuter_|>], + newOuter:{newInner:{newName:{___}}}, + TestID -> "NestedPatternWithNewNames" +], + +VerificationTest[ + PatternTemplate[outer:{inner:{oldName:{___}}}][oldName:PatternTemplate[symbol_List][{}], inner_, outer_], + outer:{inner:{oldName:Except[Except[_List], {___}]}}, + TestID -> "NestedPatternTemplatesApply" +], + +VerificationTest[ + PatternTemplate[outer:{inner:{PatternTemplate[oldName:{___}][<|"oldName" -> nestedOldName_|>]}}][nestedOldName_, inner_, outer_], + outer:{inner:{nestedOldName:{___}}}, + TestID -> "NestedPatternTemplates" +], + +VerificationTest[ + PatternTemplate[outer:{inner:{PatternTemplate[oldName:{___}]}}][inner_, outer_], + outer:{inner:{PatternTemplateObject[PatternTemplate`Private`PatternTemplateSlot[oldName, {___}]]}}, + TestID -> "NestedPatternTemplateObjects" +] + +} // Map[Sow[#, CurrentContext]&] + + +End[] + + +EndPackage[] diff --git a/test/RunTest.wl b/test/RunTest.wl index 74468b4..1de0b08 100644 --- a/test/RunTest.wl +++ b/test/RunTest.wl @@ -5,9 +5,10 @@ ClearAll[Evaluate[Context[] <> "*"]] TestContexts = { - "MyCurryTest`", + "PatternTemplateTest`", "DataTypeTest`", - "WolframLanguageServer`TextDocumentTest`" + "WolframLanguageServer`TextDocumentTest`", + "WolframLanguageServer`TokenTest`" } TestRunContext::usage = "Run tests for given context." TestRunAll::usage = "Run tests for all the contexts below:\n\t" <> StringRiffle[TestContexts, "\n\t"] @@ -17,7 +18,14 @@ Begin["`Private`"] ClearAll[Evaluate[Context[] <> "*"]] -TestRunAll[] := Column[TestRunContext /@ TestContexts] +TestRunAll[] := ( + TestContexts + // Map[TestRunContext] + // Transpose + // MapAt[Apply[And], 1] + // MapAt[Column, 2] +) + TestRunContext[context_String] := ( @@ -26,15 +34,24 @@ TestRunContext[context_String] := ( ) -ShowTestReport[report_TestReportObject, context_String] := -Column[{ - TableForm[{ - {"Test: ", context}, - {"Test passed: ", {{report["TestsSucceededCount"], "/", report["TestsSucceededCount"] + report["TestsFailedCount"]}}}, - {"Time Elapsed: ", report["TimeElapsed"]} - }], - Column[ShowTestResult /@ Cases[report["TestsFailed"], _TestResultObject, Infinity]] -}] +ShowTestReport[report_TestReportObject, context_String] := { + report["AllTestsSucceeded"], + Column[{ + Grid[{ + {"Test Context: ", context}, + { + "Tests Passed: ", + { + report["TestsSucceededCount"], + " / ", + report["TestsSucceededCount"] + report["TestsFailedCount"] + } // Map[ToString] // StringJoin + }, + {"Time Elapsed: ", report["TimeElapsed"]} + }], + Column[ShowTestResult /@ Cases[report["TestsFailed"], _TestResultObject, Infinity]] + }] +} ShowTestResult[result_TestResultObject] := ( diff --git a/test/WolframLanguageServer/TextDocumentTest.wl b/test/WolframLanguageServer/TextDocumentTest.wl index 61b3f9b..a6fc74c 100644 --- a/test/WolframLanguageServer/TextDocumentTest.wl +++ b/test/WolframLanguageServer/TextDocumentTest.wl @@ -102,9 +102,378 @@ VerificationTest[ "" }, TestID -> "ChangeTextDocument2" +], + +VerificationTest[ + FindAllCodeRanges[TextDocument[<| + "text" -> { + "(* " ~~ "::Package::" ~~ " *)", + "(* code range with one line *)" + } + |>]], + {LspRange[<| + "start" -> LspPosition[<| + "line" -> 1, + "character" -> 0 + |>], + "end" -> LspPosition[<| + "line" -> 1, + "character" -> 30 + |>] + |>]}, + TestID -> "FindAllCodeRangePackage1" +], + +VerificationTest[ + FindAllCodeRanges[TextDocument[<| + "text" -> { + "(* " ~~ "::Package::" ~~ " *)", + "", + "(* code range with one line *)" + } + |>]], + {LspRange[<| + "start" -> LspPosition[<| + "line" -> 2, + "character" -> 0 + |>], + "end" -> LspPosition[<| + "line" -> 2, + "character" -> 30 + |>] + |>]}, + TestID -> "FindAllCodeRangePackage2" +], + +VerificationTest[ + FindAllCodeRanges[TextDocument[<| + "text" -> { + "(* " ~~ "::Section::" ~~ " *)", + "(*section name*)", + "", + "", + "(* code range with four lines *)", + "", + "(* code range with four lines *)", + "(* code range with four lines *)", + "", + "" + } + |>]], + { + LspRange[<| + "start" -> LspPosition[<| + "line" -> 4, + "character" -> 0 + |>], + "end" -> LspPosition[<| + "line" -> 9, + "character" -> 0 + |>] + |>] + }, + TestID -> "FindAllCodeRangeSection1" +], + +VerificationTest[ + FindAllCodeRanges[TextDocument[<| + "text" -> { + "(* " ~~ "::Section::" ~~ " *)", + "(*section name*)", + "", + "(* code range with four lines *)", + "", + "(* code range with four lines *)", + "(* code range with four lines *)", + "" + } + |>]], + { + LspRange[<| + "start" -> LspPosition[<| + "line" -> 3, + "character" -> 0 + |>], + "end" -> LspPosition[<| + "line" -> 7, + "character" -> 0 + |>] + |>] + }, + TestID -> "FindAllCodeRangeSection2" +], + +VerificationTest[ + FindAllCodeRanges[TextDocument[<| + "text" -> { + "(* " ~~ "::Section::" ~~ " *)", + "(*section name*)", + "(* code range with four lines *)", + "", + "(* code range with four lines *)", + "(* code range with four lines *)" + } + |>]], + { + LspRange[<| + "start" -> LspPosition[<| + "line" -> 2, + "character" -> 0 + |>], + "end" -> LspPosition[<| + "line" -> 5, + "character" -> 32 + |>] + |>] + }, + TestID -> "FindAllCodeRangeSection3" +], + +VerificationTest[ + FindAllCodeRanges[TextDocument[<| + "text" -> { + "(* " ~~ "::Section::" ~~ " *)", + "(*section name*)", + "", + "", + "(* code range with one line *)", + "(* " ~~ "::Section::" ~~ " *)", + "(*section name*)", + "", + "" + } + |>]], + { + LspRange[<| + "start" -> LspPosition[<| + "line" -> 4, + "character" -> 0 + |>], + "end" -> LspPosition[<| + "line" -> 5, + "character" -> 0 + |>] + |>] + }, + TestID -> "FindAllCodeRangeTwoSection1" +], + +VerificationTest[ + FindAllCodeRanges[TextDocument[<|"text" -> {}|>]], + {}, + TestID -> "FindAllCodeRangeEmptyDoc" +], + +VerificationTest[ + GetHoverInfo[ + TextDocument[<| + "text" -> { + "Replace[a, b]" + } + |>], + LspPosition[<| + "line" -> 0, + "character" -> 3 + |>] + ], + { + {HoverInfo["Message", {"Replace", "usage"}]}, + LspRange[<| + "start" -> LspPosition[<| + "line" -> 0, + "character" -> 0 + |>], + "end" -> LspPosition[<| + "line" -> 0, + "character" -> 7 + |>] + |>] + }, + TestID -> "HoverSymbol" +], + +VerificationTest[ + GetHoverInfo[ + TextDocument[<| + "text" -> { + "2^^110" + } + |>], + LspPosition[<| + "line" -> 0, + "character" -> 3 + |>] + ], + { + {HoverInfo["Number", {"2^^110", 6}]}, + LspRange[<| + "start" -> LspPosition[<| + "line" -> 0, + "character" -> 0 + |>], + "end" -> LspPosition[<| + "line" -> 0, + "character" -> 6 + |>] + |>] + }, + TestID -> "HoverNumericLiteral" +], + +VerificationTest[ + GetHoverInfo[ + TextDocument[<| + "text" -> { + "General::obspkg" + } + |>], + LspPosition[<| + "line" -> 0, + "character" -> 3 + |>] + ], + { + { + HoverInfo["Message", {"General", "obspkg"}], + HoverInfo["Message", {"General", "usage"}] + }, + LspRange[<| + "start" -> LspPosition[<| + "line" -> 0, + "character" -> 0 + |>], + "end" -> LspPosition[<| + "line" -> 0, + "character" -> 7 + |>] + |>] + }, + TestID -> "HoverMessageName 1" +], + +VerificationTest[ + GetHoverInfo[ + TextDocument[<| + "text" -> { + "General::obspkg" + } + |>], + LspPosition[<| + "line" -> 0, + "character" -> 8 + |>] + ], + { + { + HoverInfo["Operator", {"MessageName"}], + HoverInfo["Message", {"General", "obspkg"}] + }, + LspRange[<| + "start" -> LspPosition[<| + "line" -> 0, + "character" -> 0 + |>], + "end" -> LspPosition[<| + "line" -> 0, + "character" -> 15 + |>] + |>] + }, + TestID -> "HoverMessageName 2" +], + +(* + TODO: There's an unreleased commit on CodeParser's master to reveal the + source of the message name in Symbol::name form. Enable this test after + that is release. +*) +(* VerificationTest[ + GetHoverInfo[ + TextDocument[<| + "text" -> { + "General::obspkg" + } + |>], + LspPosition[<| + "line" -> 0, + "character" -> 12 + |>] + ], + { + { + HoverInfo["Message", {"General", "obspkg"}] + }, + LspRange[<| + "start" -> LspPosition[<| + "line" -> 0, + "character" -> 9 + |>], + "end" -> LspPosition[<| + "line" -> 0, + "character" -> 15 + |>] + |>] + }, + TestID -> "HoverMessageName 3" +], *) + +VerificationTest[ + GetHoverInfo[ + TextDocument[<| + "text" -> { + "f @@ a" + } + |>], + LspPosition[<| + "line" -> 0, + "character" -> 3 + |>] + ], + { + {HoverInfo["Operator", {"Apply"}]}, + LspRange[<| + "start" -> LspPosition[<| + "line" -> 0, + "character" -> 0 + |>], + "end" -> LspPosition[<| + "line" -> 0, + "character" -> 6 + |>] + |>] + }, + TestID -> "HoverOperator 1" +], + +VerificationTest[ + GetHoverInfo[ + TextDocument[<| + "text" -> { + "{##&@@#}&" + } + |>], + LspPosition[<| + "line" -> 0, + "character" -> 2 + |>] + ], + { + {HoverInfo["Operator", {"SlotSequence"}]}, + LspRange[<| + "start" -> LspPosition[<| + "line" -> 0, + "character" -> 1 + |>], + "end" -> LspPosition[<| + "line" -> 0, + "character" -> 3 + |>] + |>] + }, + TestID -> "HoverOperator 2" ] -} // Map@Curry[Sow]@CurrentContext +} // Map[Sow[#, CurrentContext]&] End[] diff --git a/test/WolframLanguageServer/TokenTest.wl b/test/WolframLanguageServer/TokenTest.wl new file mode 100644 index 0000000..6c4f350 --- /dev/null +++ b/test/WolframLanguageServer/TokenTest.wl @@ -0,0 +1,102 @@ +(* ::Package:: *) + +BeginPackage["WolframLanguageServer`TokenTest`"] +ClearAll[Evaluate[Context[] <> "*"]] + + +Begin["`Private`"] +ClearAll[Evaluate[Context[] <> "*"]] + + +TestingContext = "WolframLanguageServer`Token`" +CurrentContext = "WolframLanguageServer`TokenTest`" +Needs[TestingContext] +Needs["DataType`"] +Needs["WolframLanguageServer`Specification`"] +Needs["WolframLanguageServer`TextDocument`"] + + +{ + +VerificationTest[ + TokenDocumentation["BeginPackage", "usage"], + StringJoin[ + "**BeginPackage** [*reference*](https://reference.wolfram.com/language/ref/BeginPackage.html) (Protected)\n\n\n", + "```mathematica\n", + "BeginPackage[\"context`\"]\n", + "```\n\n", + " makes *context*\\` and System\\` the only active contexts. \n\n", + "```mathematica\n", + "BeginPackage[\"context`\",{\"need_1`\",\"need_2`\",\[Ellipsis]}]\n", + "```\n\n", + " calls Needs on the *need*\\_*i*. \n\n" + ], + TestID -> "KnownSymbolUsage 1" +], + +VerificationTest[ + TokenDocumentation["Replace", "usage"], + StringJoin[ + "**Replace** [*reference*](https://reference.wolfram.com/language/ref/Replace.html) (Protected)\n\n\n", + "```mathematica\n", + "Replace[expr,rules]\n", + "```\n\n", + " applies a rule or list of rules in an attempt to transform the entire expression *expr*. \n\n", + "```mathematica\n", + "Replace[expr,rules,levelspec]\n", + "```\n\n", + " applies rules to parts of *expr* specified by *levelspec*. \n\n", + "```mathematica\n", + "Replace[rules]\n", + "```\n\n", + " represents an operator form of Replace that can be applied to an expression.\n\n", + "__Options:__\n", + "``` mathematica\n", + "Heads -> False\n", + "```" + ], + TestID -> "KnownSymbolUsage 2" +], + +If[$VersionNumber >= 12.0, + VerificationTest[ + TokenDocumentation["SlotSequence", "usage"], + StringJoin[ + "**SlotSequence** [*reference*](https://reference.wolfram.com/language/ref/SlotSequence.html) (NHoldAll, Protected)\n\n\n", + "```mathematica\n", + "## \n", + "```\n\n", + "represents the sequence of arguments supplied to a pure function. \n\n", + "```mathematica\n", + "##n \n", + "```\n\n", + "represents the sequence of arguments supplied to a pure function, starting with the *n*-th argument. \n\n" + ], + TestID -> "KnownSymbolUsage 3" + ], + (* before 12.0, SlotSequence::usage is empty *) + VerificationTest[ + TokenDocumentation["SlotSequence", "usage"], + "", + TestID -> "KnownSymbolUsage 3" + ] +], + +VerificationTest[ + TokenDocumentation["Syntax", "stresc"], + StringJoin[ + "```mathematica\n", + "Syntax::stresc\n", + "```\n", + "Unknown string escape \\\\\\`1\\`." + ], + TestID -> "KnownMessageName" +] + +} // Map[Sow[#, CurrentContext]&] + + +End[] + + +EndPackage[]