├── .github
└── workflows
│ ├── formatting.yml
│ └── main.yml
├── .gitignore
├── .haxerc
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── LICENSE.md
├── README.md
├── cases
├── documentSymbols
│ ├── Expected.json
│ └── Input.hx
├── foldingRange
│ ├── Expected.json
│ └── Input.hx
└── hxformat.json
├── haxe_libraries
├── formatter.hxml
├── haxe-hxparser.hxml
├── haxeparser.hxml
├── hxjsonast.hxml
├── hxnodejs.hxml
├── hxparse.hxml
├── json2object.hxml
├── language-server-protocol.hxml
├── rename.hxml
├── safety.hxml
├── test-adapter.hxml
├── tokentree.hxml
├── uglifyjs.hxml
├── utest.hxml
├── vscode-json-rpc.hxml
└── vshaxe-build.hxml
├── package-lock.json
├── package.json
├── shared
└── haxeLanguageServer
│ ├── DisplayServerConfig.hx
│ ├── LanguageServerMethods.hx
│ └── ServerRecordingEntryKind.hx
├── src
└── haxeLanguageServer
│ ├── Configuration.hx
│ ├── Context.hx
│ ├── Init.hx
│ ├── Main.hx
│ ├── documents
│ ├── HaxeDocument.hx
│ ├── HxTextDocument.hx
│ ├── HxmlDocument.hx
│ └── TextDocuments.hx
│ ├── extensions
│ ├── ArrayExtensions.hx
│ ├── DocumentUriExtensions.hx
│ ├── FsPathExtensions.hx
│ ├── FunctionFormattingConfigExtensions.hx
│ ├── PositionExtensions.hx
│ ├── RangeExtensions.hx
│ ├── ResponseErrorExtensions.hx
│ └── StringExtensions.hx
│ ├── features
│ ├── CompletionFeature.hx
│ ├── HoverFeature.hx
│ ├── haxe
│ │ ├── CodeLensFeature.hx
│ │ ├── ColorProviderFeature.hx
│ │ ├── DeterminePackageFeature.hx
│ │ ├── DiagnosticsFeature.hx
│ │ ├── DocumentFormattingFeature.hx
│ │ ├── FindReferencesFeature.hx
│ │ ├── GotoDefinitionFeature.hx
│ │ ├── GotoImplementationFeature.hx
│ │ ├── GotoTypeDefinitionFeature.hx
│ │ ├── HoverFeature.hx
│ │ ├── InlayHintFeature.hx
│ │ ├── RenameFeature.hx
│ │ ├── SignatureHelpFeature.hx
│ │ ├── WorkspaceSymbolsFeature.hx
│ │ ├── codeAction
│ │ │ ├── CodeActionFeature.hx
│ │ │ ├── DiagnosticsCodeActionFeature.hx
│ │ │ ├── ExtractConstantFeature.hx
│ │ │ ├── ExtractFunctionFeature.hx
│ │ │ ├── ExtractTypeFeature.hx
│ │ │ ├── OrganizeImportsFeature.hx
│ │ │ └── diagnostics
│ │ │ │ ├── CompilerErrorActions.hx
│ │ │ │ ├── MissingArgumentsAction.hx
│ │ │ │ ├── MissingFieldsActions.hx
│ │ │ │ ├── OrganizeImportActions.hx
│ │ │ │ ├── ParserErrorActions.hx
│ │ │ │ ├── RemovableCodeActions.hx
│ │ │ │ ├── UnresolvedIdentifierActions.hx
│ │ │ │ ├── UnusedImportActions.hx
│ │ │ │ └── import.hx
│ │ ├── completion
│ │ │ ├── CompletionContextData.hx
│ │ │ ├── CompletionFeature.hx
│ │ │ ├── CompletionFeatureLegacy.hx
│ │ │ ├── ExpectedTypeCompletion.hx
│ │ │ ├── PostfixCompletion.hx
│ │ │ └── SnippetCompletion.hx
│ │ ├── documentSymbols
│ │ │ ├── DocumentSymbolsFeature.hx
│ │ │ ├── DocumentSymbolsResolver.hx
│ │ │ └── SymbolStack.hx
│ │ └── foldingRange
│ │ │ ├── FoldingRangeFeature.hx
│ │ │ └── FoldingRangeResolver.hx
│ └── hxml
│ │ ├── CompletionFeature.hx
│ │ ├── HoverFeature.hx
│ │ ├── HxmlContextAnalyzer.hx
│ │ └── data
│ │ ├── Defines.hx
│ │ ├── Flags.hx
│ │ └── Shared.hx
│ ├── helper
│ ├── DisplayOffsetConverter.hx
│ ├── DocHelper.hx
│ ├── FormatterHelper.hx
│ ├── FsHelper.hx
│ ├── HaxePosition.hx
│ ├── IdentifierHelper.hx
│ ├── ImportHelper.hx
│ ├── JavadocHelper.hx
│ ├── PathHelper.hx
│ ├── SemVer.hx
│ ├── Set.hx
│ ├── SnippetHelper.hx
│ ├── StructDefaultsMacro.hx
│ ├── TypeHelper.hx
│ ├── VscodeCommands.hx
│ └── WorkspaceEditHelper.hx
│ ├── hxParser
│ ├── PositionAwareWalker.hx
│ └── RenameResolver.hx
│ ├── import.hx
│ ├── protocol
│ ├── CompilerMetadata.hx
│ ├── DisplayPrinter.hx
│ ├── DotPath.hx
│ └── Extensions.hx
│ ├── server
│ ├── DisplayRequest.hx
│ ├── DisplayResult.hx
│ ├── HaxeConnection.hx
│ ├── HaxeServer.hx
│ ├── MessageBuffer.hx
│ ├── ResultHandler.hx
│ ├── ServerRecording.hx
│ └── ServerRecordingTools.hx
│ └── tokentree
│ ├── PositionAnalyzer.hx
│ ├── TokenContext.hx
│ └── TokenTreeManager.hx
├── test
├── TestMain.hx
├── haxeLanguageServer
│ ├── features
│ │ └── haxe
│ │ │ └── codeAction
│ │ │ ├── ExtractConstantFeatureTest.hx
│ │ │ └── OrganizeImportsFeatureTest.hx
│ ├── helper
│ │ ├── ArrayHelperTest.hx
│ │ ├── IdentifierHelperTest.hx
│ │ ├── ImportHelperTest.hx
│ │ ├── PathHelperTest.hx
│ │ ├── PositionHelperTest.hx
│ │ ├── RangeHelperTest.hx
│ │ ├── SemVerTest.hx
│ │ └── TypeHelperTest.hx
│ ├── hxParser
│ │ └── RenameResolverTest.hx
│ ├── protocol
│ │ └── ExtensionsTest.hx
│ └── tokentree
│ │ └── TokenTreeTest.hx
├── import.hx
└── testcases
│ ├── EditTestCaseMacro.hx
│ ├── TestTextEditHelper.hx
│ ├── extractConstant
│ ├── ExtractConstant_FILE.edittest
│ ├── ExtractConstant_HAXE.edittest
│ ├── ExtractConstant_HAXE_singlequote.edittest
│ ├── ExtractConstant_multiple.edittest
│ ├── ExtractConstant_umlaut_begin.edittest
│ └── ExtractConstant_umlaut_end.edittest
│ └── organizeImports
│ ├── ConditionalImportsWithPackage.edittest
│ ├── ConditionalImportsWithPackage_AA.edittest
│ ├── ConditionalImportsWithPackage_NPP.edittest
│ ├── ConditionalImportsWithPackage_SLP.edittest
│ ├── ImportsWithClass.edittest
│ ├── ImportsWithClassWithCommentedOutImport.edittest
│ ├── ImportsWithClassWithSpaces.edittest
│ ├── ImportsWithClassWithSpacesAndUsing.edittest
│ ├── ImportsWithClass_conditional.edittest
│ ├── ImportsWithClass_conditional_first.edittest
│ ├── ImportsWithPackage.edittest
│ ├── ImportsWithPackage_AA.edittest
│ ├── ImportsWithPackage_NPP.edittest
│ ├── ImportsWithPackage_SLP.edittest
│ ├── ImportsWithoutPackage.edittest
│ ├── ImportsWithoutPackage_AA.edittest
│ ├── ImportsWithoutPackage_NPP.edittest
│ └── ImportsWithoutPackage_SLP.edittest
└── vshaxe-build.json
/.github/workflows/formatting.yml:
--------------------------------------------------------------------------------
1 | name: Formatting
2 |
3 | on:
4 | push:
5 | pull_request:
6 |
7 | jobs:
8 | check:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 | with:
13 | submodules: true
14 | - run: |
15 | npm ci
16 | npx lix run formatter -s . --check
17 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | - run: npm ci
11 | - run: npx lix run vshaxe-build -t language-server -t language-server-tests
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
2 | dump/
3 | node_modules/
4 | Actual.json
5 | .unittest
6 |
--------------------------------------------------------------------------------
/.haxerc:
--------------------------------------------------------------------------------
1 | {
2 | "version": "dd0d6a6",
3 | "resolveLibs": "scoped"
4 | }
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Attach",
6 | "type": "node",
7 | "request": "attach",
8 | "port": 6004,
9 | "sourceMaps": true,
10 | "outFiles": [
11 | "${workspaceRoot}/bin/*.js"
12 | ]
13 | },
14 | {
15 | "type": "node",
16 | "request": "launch",
17 | "name": "Tests",
18 | "program": "${workspaceRoot}/bin/test.js",
19 | "sourceMaps": true,
20 | "outFiles": [
21 | "${workspaceRoot}/bin/*.js"
22 | ]
23 | }
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "[haxe]": {
3 | "editor.formatOnSave": true,
4 | "editor.formatOnPaste": true,
5 | "editor.codeActionsOnSave": {
6 | "source.sortImports": true
7 | }
8 | },
9 | "haxe.executable": "auto",
10 | "haxe.importsSortOrder": "all-alphabetical",
11 | "haxeTestExplorer.testCommand": [
12 | "npx",
13 | "lix",
14 | "run",
15 | "vshaxe-build",
16 | "--target",
17 | "language-server-tests",
18 | "--debug",
19 | "--",
20 | "-lib",
21 | "test-adapter"
22 | ]
23 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "type": "vshaxe-build",
6 | "target": "language-server (debug)",
7 | "group": {
8 | "kind": "build",
9 | "isDefault": true
10 | }
11 | },
12 | {
13 | "type": "vshaxe-build",
14 | "target": "language-server-tests",
15 | "group": {
16 | "kind": "test",
17 | "isDefault": true
18 | }
19 | }
20 | ]
21 | }
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 vshaxe contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Haxe Language Server
2 |
3 | [](https://github.com/vshaxe/haxe-language-server/actions/workflows/main.yml)
4 |
5 | This is a language server implementing [Language Server Protocol](https://github.com/Microsoft/language-server-protocol) for the [Haxe](http://haxe.org/) language.
6 |
7 | The goal of this project is to encapsulate haxe's completion API with all its quirks behind a solid and easy-to-use protocol that can be used by any editor/IDE.
8 |
9 | Used by the [Visual Studio Code Haxe Extension](https://github.com/vshaxe/vshaxe). It has also successfully been used in Neovim and Sublime Text[[1]](https://github.com/vshaxe/vshaxe/issues/171)[[2]](https://github.com/vshaxe/vshaxe/issues/328), but no official extensions exist at this time.
10 |
11 | Note that any issues should be reported to [vshaxe](https://github.com/vshaxe/vshaxe) directly (this is also the reason why the issue tracker is disabled). Pull requests are welcome however!
12 |
13 | **IMPORTANT**: This requires Haxe 3.4.0 or newer due to usage of [`-D display-stdin`](https://github.com/HaxeFoundation/haxe/pull/5120),
14 | [`--wait stdio`](https://github.com/HaxeFoundation/haxe/pull/5188) and tons of other fixes and additions related to IDE support.
15 |
16 | ### Building From Source
17 |
18 | The easiest way to work on the language server is probably to build it as part of the vshaxe VSCode extension as instructed [here](https://github.com/vshaxe/vshaxe/wiki/Installation#from-source) (even if you ultimately want to use it outside of VSCode), which allows for easy debugging.
19 |
20 | However, you can also build it as a standalone project like so:
21 |
22 | ```
23 | git clone https://github.com/vshaxe/haxe-language-server
24 | cd haxe-language-server
25 | npm ci
26 | npx lix run vshaxe-build -t language-server
27 | ```
28 |
29 | This creates a `bin/server.js` that can be started with `node server.js`.
30 |
31 | ### Usage with (Neo)vim
32 |
33 | There's a large amount of language client plugins for (Neo)vim, but the best choice currently seems to be [coc.nvim](https://github.com/neoclide/coc.nvim). A `coc-settings.json` that is known to work with haxe-language-server looks like this:
34 |
35 | ```haxe
36 | {
37 | "languageserver": {
38 | "haxe": {
39 | "command": "node",
40 | "args": [""],
41 | "filetypes": ["haxe"],
42 | "trace.server": "verbose",
43 | "initializationOptions": {
44 | "displayArguments": ["build.hxml"]
45 | },
46 | "settings": {
47 | "haxe.executable": "haxe"
48 | }
49 | }
50 | }
51 | }
52 | ```
53 |
54 | ### Usage with Kate
55 |
56 | Go to configure Kate (`Ctrl+Shift+,`) » `LSP Client` » `User Server Settings` » Add the following snippet to the JSON config within the `servers` object. Don't forget to change the path to the LSP server.
57 |
58 | ```json
59 | "haxe": {
60 | "command": ["node", ""],
61 | "rootIndicationFileNames": ["*.hx", "*.hxml"],
62 | "url": "https://github.com/vshaxe/haxe-language-server",
63 | "initializationOptions": {"displayArguments": ["build.hxml"]},
64 | "settings": {"haxe": {"buildCompletionCache": true}},
65 | "highlightingModeRegex": "^Haxe$"
66 | },
67 | ```
68 |
69 | Click `Apply`, you can then close the window. Use `File` » `Reload` or `F5` to reload the project. Accept when it asks you whether you want to start the LSP server.
70 |
71 | Where `` can either be a `server.js` you built from source or simply downloaded as part of the Haxe Visual Studio Code extension (`"//.vscode/extensions/nadako.vshaxe-/bin/server.js"`).
72 |
--------------------------------------------------------------------------------
/cases/documentSymbols/Input.hx:
--------------------------------------------------------------------------------
1 | class BreakPositions {
2 | // °𐐀
3 | /* °𐐀 */
4 | var _ = "°𐐀";
5 | var _ = ~/°𐐀/;
6 | }
7 |
8 | abstract Abstract(Int) {
9 | inline static var CONSTANT = 5;
10 |
11 | public var abstractPropery(get,never):Int;
12 |
13 | @:op(A * B)
14 | public function repeat(rhs:Int):Abstract {
15 | return this * rhs;
16 | }
17 |
18 | @:op(A + B) function add(rhs:Int):Abstract;
19 |
20 | @:arrayAccess
21 | public inline function get(key:Int) {
22 | return 0;
23 | }
24 |
25 | @:resolve
26 | function resolve(name:String) {
27 | return null;
28 | }
29 |
30 | public function new() {}
31 |
32 | function foo() {}
33 | }
34 |
35 | @:deprecated
36 | class Class {
37 | @:deprecated
38 | inline static var CONSTANT = 5;
39 |
40 | var variable:Int;
41 |
42 | var variableWithBlockInit:Int = {
43 | function foo():Int return 0;
44 | var bar = foo();
45 | bar;
46 | };
47 |
48 | var property(default,null):Int;
49 |
50 | final finaleVariable:Int;
51 |
52 | final function finalMethod():Void {}
53 |
54 | @:op(A + B) @:deprecated
55 | public function fakeAdd(rhs:Int):Int {
56 | return 0;
57 | }
58 |
59 | function foo(param1:Int, param2:Int) {
60 | function foo2() {
61 | function foo3() {
62 | var foo4:Int;
63 | }
64 | }
65 |
66 | inline function innerFoo() {}
67 |
68 | var f = function() {}
69 |
70 | var a, b, c = {
71 | var f:Int = 100;
72 | f;
73 | };
74 |
75 | var array = [];
76 | for (element in array) {
77 | var varInFor;
78 | }
79 |
80 | try {}
81 | catch (exception:Any) {
82 | var varInCatch;
83 | }
84 |
85 | for (_ in 0...100) {}
86 | try {} catch (_:Any) {}
87 |
88 | var _:Int;
89 |
90 | macro class MacroClass {
91 | var macroField:Int;
92 | }
93 |
94 | macro class {
95 | var macroField:Int;
96 | }
97 |
98 | // inserted _ name shouldn't appear
99 | var
100 | // and also shouldn't affect positions
101 | var var maybeIncorrectPos:Int;
102 | }
103 |
104 | function new() {}
105 | }
106 |
107 | interface Interface {
108 | var variable:Int;
109 | function foo():Void;
110 | }
111 |
112 | @:enum abstract EnumAbstract(Int) {
113 | function foo() {
114 | macro class MacroClass {
115 | var macroField:Int;
116 |
117 | function macroFunction() {
118 | var macroVar;
119 | }
120 | }
121 | }
122 |
123 | inline static var CONSTANT = 5;
124 |
125 | var Value1 = 0;
126 | var Value2 = 1;
127 |
128 | @:op(A + B) function add(rhs:Int):Abstract;
129 | }
130 |
131 | enum abstract EnumAbstractHaxe4(Int) {
132 | inline static var CONSTANT = 5;
133 |
134 | var Value1 = 0;
135 | var Value2 = 1;
136 | }
137 |
138 | enum Enum {
139 | Simple;
140 | Complex(i:Int, b:Bool);
141 | }
142 |
143 | typedef TypeAlias = Int;
144 |
145 | typedef TypedefShortFields = {
146 | ?a:Int,
147 | b:Bool
148 | }
149 |
150 | typedef TypedefComplexFields = {
151 | @:optional var a:Int;
152 | var b:Bool;
153 | var ?c:Bool;
154 | function foo(bar:Int):Void;
155 | }
156 |
157 | typedef TypedefExtension = {
158 | >Foo,
159 | ?a:Int,
160 | b:Bool
161 | }
162 |
163 | typedef TypedefIntersectionTypes = A & B & {
164 | a:Bool
165 | } & C & {
166 | b:Int
167 | }
168 |
169 | typedef TypedefToArray = Array<{i:Int}>;
170 |
171 | /**
172 | * Type doc comment
173 | */
174 | // other comments
175 | // in the way
176 | class Type {
177 | /** var doc comment */
178 | var variable:Int;
179 |
180 | /**
181 | function doc comment
182 | **/
183 | function func() {
184 | /**
185 | * local function doc comment (not a thing)
186 | */
187 | function localFunction() {}
188 |
189 | /**
190 | * local var doc comment (not a thing)
191 | */
192 | var localVar:Int;
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/cases/foldingRange/Expected.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "startLine": 9,
4 | "endLine": 11,
5 | "startCharacter": 0,
6 | "endCharacter": 3,
7 | "kind": "comment"
8 | },
9 | {
10 | "startLine": 12,
11 | "endLine": 80,
12 | "startCharacter": 10,
13 | "endCharacter": 2
14 | },
15 | {
16 | "startLine": 13,
17 | "endLine": 15,
18 | "startCharacter": 1,
19 | "endCharacter": 4,
20 | "kind": "comment"
21 | },
22 | {
23 | "startLine": 16,
24 | "endLine": 79,
25 | "startCharacter": 16,
26 | "endCharacter": 3
27 | },
28 | {
29 | "startLine": 17,
30 | "endLine": 19,
31 | "startCharacter": 19,
32 | "endCharacter": 9
33 | },
34 | {
35 | "startLine": 22,
36 | "endLine": 24,
37 | "startCharacter": 20,
38 | "endCharacter": 0
39 | },
40 | {
41 | "startLine": 30,
42 | "endLine": 31,
43 | "startCharacter": 6,
44 | "endCharacter": 0
45 | },
46 | {
47 | "startLine": 34,
48 | "endLine": 35,
49 | "startCharacter": 28,
50 | "endCharacter": 18
51 | },
52 | {
53 | "startLine": 39,
54 | "endLine": 40,
55 | "startCharacter": 7,
56 | "endCharacter": 10
57 | },
58 | {
59 | "startLine": 41,
60 | "endLine": 42,
61 | "startCharacter": 33,
62 | "endCharacter": 10
63 | },
64 | {
65 | "startLine": 43,
66 | "endLine": 44,
67 | "startCharacter": 9,
68 | "endCharacter": 10
69 | },
70 | {
71 | "startLine": 47,
72 | "endLine": 48,
73 | "startCharacter": 7,
74 | "endCharacter": 10
75 | },
76 | {
77 | "startLine": 49,
78 | "endLine": 54,
79 | "startCharacter": 11,
80 | "endCharacter": 10
81 | },
82 | {
83 | "startLine": 55,
84 | "endLine": 56,
85 | "startCharacter": 9,
86 | "endCharacter": 10
87 | },
88 | {
89 | "startLine": 38,
90 | "endLine": 57,
91 | "startCharacter": 6,
92 | "endCharacter": 7
93 | },
94 | {
95 | "startLine": 60,
96 | "endLine": 62,
97 | "startCharacter": 24,
98 | "endCharacter": 8
99 | },
100 | {
101 | "startLine": 65,
102 | "endLine": 68,
103 | "startCharacter": 24,
104 | "endCharacter": 19
105 | },
106 | {
107 | "startLine": 74,
108 | "endLine": 78,
109 | "startCharacter": 13,
110 | "endCharacter": 21
111 | },
112 | {
113 | "startLine": 75,
114 | "endLine": 76,
115 | "startCharacter": 3,
116 | "endCharacter": 15
117 | },
118 | {
119 | "startLine": 77,
120 | "endLine": 78,
121 | "startCharacter": 3,
122 | "endCharacter": 21
123 | },
124 | {
125 | "startLine": 84,
126 | "endLine": 85,
127 | "startCharacter": 17,
128 | "endCharacter": 15,
129 | "kind": "region"
130 | },
131 | {
132 | "startLine": 87,
133 | "endLine": 88,
134 | "startCharacter": 16,
135 | "endCharacter": 14,
136 | "kind": "region"
137 | },
138 | {
139 | "startLine": 83,
140 | "endLine": 89,
141 | "startCharacter": 16,
142 | "endCharacter": 14,
143 | "kind": "region"
144 | },
145 | {
146 | "startLine": 92,
147 | "endLine": 93,
148 | "startCharacter": 14,
149 | "endCharacter": 13,
150 | "kind": "region"
151 | },
152 | {
153 | "startLine": 96,
154 | "endLine": 97,
155 | "startCharacter": 16,
156 | "endCharacter": 14,
157 | "kind": "region"
158 | },
159 | {
160 | "startLine": 0,
161 | "endLine": 7,
162 | "startCharacter": 0,
163 | "endCharacter": 13,
164 | "kind": "imports"
165 | }
166 | ]
--------------------------------------------------------------------------------
/cases/foldingRange/Input.hx:
--------------------------------------------------------------------------------
1 | import haxeLanguageServer.tokentree.FoldingRangeResolver;
2 | import languageServerProtocol.protocol.FoldingRange;
3 | import jsonrpc.ResponseError;
4 | import jsonrpc.Types.NoData;
5 | import jsonrpc.CancellationToken;
6 |
7 | using StringTools;
8 | using Lambda;
9 |
10 | /**
11 | Doc comment
12 | **/
13 | class Foo {
14 | /**
15 | * JavaDoc-style doc comment
16 | */
17 | function bar() {
18 | var someStruct = {
19 | foo: 0,
20 | bar: 1
21 | }
22 |
23 | var emptyStruct = {
24 |
25 |
26 | }
27 |
28 | #if foo
29 | #end
30 |
31 | #if foo
32 |
33 | #end
34 |
35 | #if (haxe_ver >= "4.0.0")
36 | trace("Haxe 4");
37 | #end
38 |
39 | #if outer
40 | #if inner1
41 | call();
42 | #elseif (haxe_ver >= "4.0.0")
43 | call();
44 | #else
45 | call();
46 | #end
47 |
48 | #if inner1
49 | call();
50 | #elseif foo
51 | call();
52 | call();
53 | call();
54 | #error "foo"
55 | call();
56 | #else
57 | call();
58 | #end
59 | #end
60 |
61 | var mulitlineString = "
62 | lorem
63 | ipsum
64 | ";
65 |
66 | var data:Array = [
67 | 0, 1, 2, 3, 4, 5,
68 | 6, 7, 8, 9, 0, 1,
69 | 2, 3, 4, 5, 6, 7
70 | ];
71 |
72 | "";
73 | [];
74 |
75 | switch foo {
76 | case bar:
77 | trace(bar);
78 | default:
79 | trace("default");
80 | }
81 | }
82 | }
83 |
84 | // # region name
85 | // # region name
86 | // # endregion
87 |
88 | //# region name
89 | //# endregion
90 | // # endregion
91 |
92 |
93 | // region name
94 | // end region
95 |
96 |
97 | // { region name
98 | // } endregion
--------------------------------------------------------------------------------
/cases/hxformat.json:
--------------------------------------------------------------------------------
1 | {
2 | "disableFormatting": true
3 | }
--------------------------------------------------------------------------------
/haxe_libraries/formatter.hxml:
--------------------------------------------------------------------------------
1 | # @install: lix --silent download "haxelib:/formatter#1.14.6" into formatter/1.14.6/haxelib
2 | # @run: haxelib run-dir formatter "${HAXE_LIBCACHE}/formatter/1.14.6/haxelib"
3 | -cp ${HAXE_LIBCACHE}/formatter/1.14.6/haxelib/src
4 | -D formatter=1.14.6
--------------------------------------------------------------------------------
/haxe_libraries/haxe-hxparser.hxml:
--------------------------------------------------------------------------------
1 | # @install: lix --silent download "gh://github.com/vshaxe/haxe-hxparser#de1042397c85ea18440d2d5c3a1c5d47ba2204d3" into haxe-hxparser/0.0.1/github/de1042397c85ea18440d2d5c3a1c5d47ba2204d3
2 | -cp ${HAXE_LIBCACHE}/haxe-hxparser/0.0.1/github/de1042397c85ea18440d2d5c3a1c5d47ba2204d3/src
3 | -D haxe-hxparser=0.0.1
4 |
--------------------------------------------------------------------------------
/haxe_libraries/haxeparser.hxml:
--------------------------------------------------------------------------------
1 | # @install: lix --silent download "gh://github.com/HaxeCheckstyle/haxeparser#f0a7f07101c14dc32b0964dd52af8dcaa322e178" into haxeparser/4.3.0-rc.1/github/f0a7f07101c14dc32b0964dd52af8dcaa322e178
2 | -lib hxparse
3 | -cp ${HAXE_LIBCACHE}/haxeparser/4.3.0-rc.1/github/f0a7f07101c14dc32b0964dd52af8dcaa322e178/src
4 | -D haxeparser=4.3.0-rc.1
--------------------------------------------------------------------------------
/haxe_libraries/hxjsonast.hxml:
--------------------------------------------------------------------------------
1 | # @install: lix --silent download "haxelib:/hxjsonast#1.1.0" into hxjsonast/1.1.0/haxelib
2 | -cp ${HAXE_LIBCACHE}/hxjsonast/1.1.0/haxelib/src
3 | -D hxjsonast=1.1.0
--------------------------------------------------------------------------------
/haxe_libraries/hxnodejs.hxml:
--------------------------------------------------------------------------------
1 | # @install: lix --silent download "haxelib:/hxnodejs#12.1.0" into hxnodejs/12.1.0/haxelib
2 | -cp ${HAXE_LIBCACHE}/hxnodejs/12.1.0/haxelib/src
3 | -D hxnodejs=12.1.0
4 | --macro allowPackage('sys')
5 | # should behave like other target defines and not be defined in macro context
6 | --macro define('nodejs')
7 | --macro _internal.SuppressDeprecated.run()
8 |
--------------------------------------------------------------------------------
/haxe_libraries/hxparse.hxml:
--------------------------------------------------------------------------------
1 | # @install: lix --silent download "gh://github.com/simn/hxparse#32e376f80c4b0e999e9f3229947d4dac2138382b" into hxparse/4.0.1/github/32e376f80c4b0e999e9f3229947d4dac2138382b
2 | -cp ${HAXE_LIBCACHE}/hxparse/4.0.1/github/32e376f80c4b0e999e9f3229947d4dac2138382b/src
3 | -D hxparse=4.0.1
--------------------------------------------------------------------------------
/haxe_libraries/json2object.hxml:
--------------------------------------------------------------------------------
1 | # @install: lix --silent download "gh://github.com/elnabo/json2object#64b46769a143207f266010997ea24aa9764505ce" into json2object/3.10.0/github/64b46769a143207f266010997ea24aa9764505ce
2 | -lib hxjsonast
3 | -cp ${HAXE_LIBCACHE}/json2object/3.10.0/github/64b46769a143207f266010997ea24aa9764505ce/src
4 | -D json2object=3.10.0
--------------------------------------------------------------------------------
/haxe_libraries/language-server-protocol.hxml:
--------------------------------------------------------------------------------
1 | # @install: lix --silent download "gh://github.com/vshaxe/language-server-protocol-haxe#a6baa2ddcd792e99b19398048ef95aa00f0aa1f6" into language-server-protocol/3.17.1/github/a6baa2ddcd792e99b19398048ef95aa00f0aa1f6
2 | -cp ${HAXE_LIBCACHE}/language-server-protocol/3.17.1/github/a6baa2ddcd792e99b19398048ef95aa00f0aa1f6/src
3 | -D language-server-protocol=3.17.1
--------------------------------------------------------------------------------
/haxe_libraries/rename.hxml:
--------------------------------------------------------------------------------
1 | # @install: lix --silent download "haxelib:/rename#2.2.2" into rename/2.2.2/haxelib
2 | -cp ${HAXE_LIBCACHE}/rename/2.2.2/haxelib/src
3 | -D rename=2.2.2
--------------------------------------------------------------------------------
/haxe_libraries/safety.hxml:
--------------------------------------------------------------------------------
1 | # @install: lix --silent download "haxelib:/safety#1.1.2" into safety/1.1.2/haxelib
2 | -cp ${HAXE_LIBCACHE}/safety/1.1.2/haxelib/src
3 | -D safety=1.1.2
--------------------------------------------------------------------------------
/haxe_libraries/test-adapter.hxml:
--------------------------------------------------------------------------------
1 | # @install: lix --silent download "haxelib:/test-adapter#2.0.4" into test-adapter/2.0.4/haxelib
2 | -lib json2object
3 | -cp ${HAXE_LIBCACHE}/test-adapter/2.0.4/haxelib/
4 | -D test-adapter=2.0.4
5 | --macro _testadapter.Macro.init()
--------------------------------------------------------------------------------
/haxe_libraries/tokentree.hxml:
--------------------------------------------------------------------------------
1 | # @install: lix --silent download "haxelib:/tokentree#1.2.8" into tokentree/1.2.8/haxelib
2 | -cp ${HAXE_LIBCACHE}/tokentree/1.2.8/haxelib/src
3 | -D tokentree=1.2.8
--------------------------------------------------------------------------------
/haxe_libraries/uglifyjs.hxml:
--------------------------------------------------------------------------------
1 | # @install: lix --silent download "haxelib:/uglifyjs#1.0.0" into uglifyjs/1.0.0/haxelib
2 | -cp ${HAXE_LIBCACHE}/uglifyjs/1.0.0/haxelib/src/
3 | -D uglifyjs=1.0.0
4 | --macro UglifyJS.run()
5 |
--------------------------------------------------------------------------------
/haxe_libraries/utest.hxml:
--------------------------------------------------------------------------------
1 | # @install: lix --silent download "gh://github.com/haxe-utest/utest#5de48a964ca75c8e6321ac9706346a24958af2a4" into utest/1.13.2/github/5de48a964ca75c8e6321ac9706346a24958af2a4
2 | -cp ${HAXE_LIBCACHE}/utest/1.13.2/github/5de48a964ca75c8e6321ac9706346a24958af2a4/src
3 | -D utest=1.13.2
4 | --macro utest.utils.Macro.checkHaxe()
5 | --macro utest.utils.Macro.importEnvSettings()
6 |
--------------------------------------------------------------------------------
/haxe_libraries/vscode-json-rpc.hxml:
--------------------------------------------------------------------------------
1 | # @install: lix --silent download "gh://github.com/vshaxe/vscode-json-rpc#0160f06bc9df1dd0547f2edf23753540db74ed5b" into vscode-json-rpc/1.0.0/github/0160f06bc9df1dd0547f2edf23753540db74ed5b
2 | -cp ${HAXE_LIBCACHE}/vscode-json-rpc/1.0.0/github/0160f06bc9df1dd0547f2edf23753540db74ed5b/src
3 | -D vscode-json-rpc=1.0.0
--------------------------------------------------------------------------------
/haxe_libraries/vshaxe-build.hxml:
--------------------------------------------------------------------------------
1 | -D vshaxe-build=0.0.1
2 | # @install: lix --silent download "gh://github.com/vshaxe/vshaxe-build#39ab9c6315ae76080e5399391c8fa561daec6d55" into vshaxe-build/0.0.1/github/39ab9c6315ae76080e5399391c8fa561daec6d55
3 | # @run: haxelib run-dir vshaxe-build "${HAXE_LIBCACHE}/vshaxe-build/0.0.1/github/39ab9c6315ae76080e5399391c8fa561daec6d55"
4 | -cp ${HAXE_LIBCACHE}/vshaxe-build/0.0.1/github/39ab9c6315ae76080e5399391c8fa561daec6d55/
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "haxe-language-server",
3 | "version": "0.0.0",
4 | "devDependencies": {
5 | "lix": "^15.12.0",
6 | "terser": "^5.15.0"
7 | },
8 | "scripts": {
9 | "postinstall": "npx lix download"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/shared/haxeLanguageServer/DisplayServerConfig.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer;
2 |
3 | import haxe.DynamicAccess;
4 | import haxe.display.Server.ConfigurePrintParams;
5 |
6 | typedef DisplayServerConfig = {
7 | var path:String;
8 | var env:DynamicAccess;
9 | var arguments:Array;
10 | var useSocket:Bool;
11 | var print:ConfigurePrintParams;
12 | }
13 |
--------------------------------------------------------------------------------
/shared/haxeLanguageServer/ServerRecordingEntryKind.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer;
2 |
3 | enum abstract ServerRecordingEntryKind(String) to String {
4 | var In = "<";
5 | var Out = ">";
6 | var Local = "-";
7 | var Comment = "#";
8 | }
9 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/Init.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer;
2 |
3 | import haxe.macro.Compiler;
4 |
5 | function run() {
6 | #if debug
7 | Compiler.define("uglifyjs_disabled");
8 | #end
9 | Compiler.define("uglifyjs_bin", (if (Sys.systemName() == "Windows") "node_modules\\.bin\\terser.cmd" else "./node_modules/.bin/terser"));
10 | }
11 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/Main.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer;
2 |
3 | import js.Node.process;
4 | import jsonrpc.Protocol;
5 | import jsonrpc.node.MessageReader;
6 | import jsonrpc.node.MessageWriter;
7 |
8 | function main() {
9 | final reader = new MessageReader(process.stdin);
10 | final writer = new MessageWriter(process.stdout);
11 | final languageServerProtocol = new Protocol(writer.write);
12 | languageServerProtocol.logError = message -> languageServerProtocol.sendNotification(LogMessageNotification.type, {type: Warning, message: message});
13 | setupTrace(languageServerProtocol);
14 | final context = new Context(languageServerProtocol);
15 | reader.listen(languageServerProtocol.handleMessage);
16 |
17 | function log(method:String, data:Dynamic) {
18 | if (context.config.sendMethodResults) {
19 | languageServerProtocol.sendNotification(LanguageServerMethods.DidRunMethod, {
20 | kind: Lsp,
21 | method: method,
22 | debugInfo: null,
23 | response: {
24 | result: data
25 | }
26 | });
27 | }
28 | }
29 | languageServerProtocol.didRespondToRequest = function(request, response) {
30 | log(request.method, {
31 | request: request,
32 | response: response
33 | });
34 | }
35 | languageServerProtocol.didSendNotification = function(notification) {
36 | if (notification.method != LogMessageNotification.type && !notification.method.startsWith("haxe/")) {
37 | log(notification.method, notification);
38 | }
39 | }
40 | }
41 |
42 | private function setupTrace(languageServerProtocol:Protocol) {
43 | haxe.Log.trace = function(v, ?i) {
44 | final r = [Std.string(v)];
45 | if (i != null && i.customParams != null) {
46 | for (v in i.customParams)
47 | r.push(Std.string(v));
48 | }
49 | languageServerProtocol.sendNotification(LogMessageNotification.type, {type: Log, message: r.join(" ")});
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/documents/HaxeDocument.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.documents;
2 |
3 | import haxeLanguageServer.tokentree.TokenTreeManager;
4 | import hxParser.ParseTree;
5 |
6 | class HaxeDocument extends HxTextDocument {
7 | public var parseTree(get, never):Null;
8 | public var tokens(get, never):Null;
9 |
10 | var _parseTree:Null;
11 | var _tokens:Null;
12 |
13 | override function update(events:Array, version:Int) {
14 | super.update(events, version);
15 | _parseTree = null;
16 | _tokens = null;
17 | }
18 |
19 | public inline function byteRangeToRange(byteRange:Range, offsetConverter:DisplayOffsetConverter):Range {
20 | return {
21 | start: bytePositionToPosition(byteRange.start, offsetConverter),
22 | end: bytePositionToPosition(byteRange.end, offsetConverter),
23 | };
24 | }
25 |
26 | inline function bytePositionToPosition(bytePosition:Position, offsetConverter:DisplayOffsetConverter):Position {
27 | final line = lineAt(bytePosition.line);
28 | return {
29 | line: bytePosition.line,
30 | character: offsetConverter.byteOffsetToCharacterOffset(line, bytePosition.character)
31 | };
32 | }
33 |
34 | function createParseTree() {
35 | return try switch hxParser.HxParser.parse(content) {
36 | case Success(tree):
37 | new hxParser.Converter(tree).convertResultToFile();
38 | case Failure(error):
39 | trace('hxparser failed to parse $uri with: \'$error\'');
40 | null;
41 | } catch (e) {
42 | trace('hxParser.Converter failed on $uri with: \'$e\'');
43 | null;
44 | }
45 | }
46 |
47 | function get_parseTree() {
48 | if (_parseTree == null) {
49 | _parseTree = createParseTree();
50 | }
51 | return _parseTree;
52 | }
53 |
54 | function get_tokens() {
55 | if (_tokens == null) {
56 | try {
57 | _tokens = TokenTreeManager.create(content);
58 | } catch (e) {
59 | // trace('$uri: $e');
60 | }
61 | }
62 | return _tokens;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/documents/HxmlDocument.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.documents;
2 |
3 | class HxmlDocument extends HxTextDocument {}
4 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/documents/TextDocuments.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.documents;
2 |
3 | @:allow(haxeLanguageServer.Context)
4 | class TextDocuments {
5 | public static inline final syncKind = TextDocumentSyncKind.Incremental;
6 |
7 | final documents = new Map();
8 |
9 | public function new() {}
10 |
11 | public inline function iterator():Iterator {
12 | return documents.iterator();
13 | }
14 |
15 | public inline function getHaxe(uri:DocumentUri):Null {
16 | var doc:Null = Std.downcast(documents[uri], HaxeDocument);
17 | if (doc == null && uri.isHaxeFile()) {
18 | // document not opened via client, load it directly from disk
19 | // see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_didOpen
20 | doc = new HaxeDocument(uri, "hx", 1, sys.io.File.getContent(uri.toFsPath().toString()));
21 | }
22 | return doc;
23 | }
24 |
25 | public inline function getHxml(uri:DocumentUri):Null {
26 | var doc:Null = Std.downcast(documents[uri], HxmlDocument);
27 | if (doc == null && uri.isHxmlFile()) {
28 | // document not (yet) loaded via client, load it directly from disk
29 | // see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_didOpen
30 | doc = new HxmlDocument(uri, "hxml", 1, sys.io.File.getContent(uri.toFsPath().toString()));
31 | }
32 | return doc;
33 | }
34 |
35 | function onDidOpenTextDocument(event:DidOpenTextDocumentParams) {
36 | final td = event.textDocument;
37 | final uri = td.uri;
38 | if (uri.isHaxeFile()) {
39 | documents[td.uri] = new HaxeDocument(td.uri, td.languageId, td.version, td.text);
40 | } else if (uri.isHxmlFile()) {
41 | documents[td.uri] = new HxmlDocument(td.uri, td.languageId, td.version, td.text);
42 | } else {
43 | throw uri + " has unsupported file type (must be .hx or .hxml)";
44 | }
45 | }
46 |
47 | function onDidChangeTextDocument(event:DidChangeTextDocumentParams) {
48 | final td = event.textDocument;
49 | final changes = event.contentChanges;
50 | if (changes.length == 0)
51 | return;
52 | final document = documents[td.uri];
53 | if (document != null) {
54 | document.update(changes, td.version);
55 | }
56 | }
57 |
58 | function onDidCloseTextDocument(event:DidCloseTextDocumentParams) {
59 | documents.remove(event.textDocument.uri);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/extensions/ArrayExtensions.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.extensions;
2 |
3 | using Lambda;
4 |
5 | inline function occurrences(a:Array, element:T):Int {
6 | return a.count(e -> e == element);
7 | }
8 |
9 | function equals(a1:Array, a2:Array):Bool {
10 | if (a1 == null && a2 == null)
11 | return true;
12 | if (a1 == null && a2 != null)
13 | return false;
14 | if (a1 != null && a2 == null)
15 | return false;
16 | if (a1.length != a2.length)
17 | return false;
18 | for (i in 0...a1.length)
19 | if (a1[i] != a2[i])
20 | return false;
21 | return true;
22 | }
23 |
24 | function filterDuplicates(array:Array, filter:(a:T, b:T) -> Bool):Array {
25 | final unique:Array = [];
26 | for (element in array) {
27 | var present = false;
28 | for (unique in unique)
29 | if (filter(unique, element))
30 | present = true;
31 | if (!present)
32 | unique.push(element);
33 | }
34 | return unique;
35 | }
36 |
37 | inline function unique(array:Array):Array {
38 | return filterDuplicates(array, (e1, e2) -> e1 == e2);
39 | }
40 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/extensions/DocumentUriExtensions.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.extensions;
2 |
3 | private final driveLetterPathRe = ~/^\/[a-zA-Z]:/;
4 | private final uriRe = ~/^(([^:\/?#]+?):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
5 |
6 | /** ported from VSCode sources **/
7 | function toFsPath(uri:DocumentUri):FsPath {
8 | if (!uriRe.match(uri.toString()) || uriRe.matched(2) != "file")
9 | throw 'Invalid uri: $uri';
10 |
11 | final path = uriRe.matched(5).urlDecode();
12 | if (driveLetterPathRe.match(path))
13 | return new FsPath(path.charAt(1).toLowerCase() + path.substr(2));
14 | else
15 | return new FsPath(path);
16 | }
17 |
18 | function isFile(uri:DocumentUri):Bool {
19 | return uri.toString().startsWith("file://");
20 | }
21 |
22 | function isUntitled(uri:DocumentUri):Bool {
23 | return uri.toString().startsWith("untitled:");
24 | }
25 |
26 | function isHaxeFile(uri:DocumentUri):Bool {
27 | return uri.toString().endsWith(".hx");
28 | }
29 |
30 | function isHxmlFile(uri:DocumentUri):Bool {
31 | return uri.toString().endsWith(".hxml");
32 | }
33 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/extensions/FsPathExtensions.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.extensions;
2 |
3 | private final upperCaseDriveRe = ~/^(\/)?([A-Z]:)/;
4 |
5 | /** ported from VSCode sources **/
6 | function toUri(path:FsPath):DocumentUri {
7 | var path = path.toString();
8 | path = path.replace("\\", "/");
9 | if (path.fastCodeAt(0) != "/".code)
10 | path = "/" + path;
11 |
12 | final parts = ["file://"];
13 |
14 | if (upperCaseDriveRe.match(path))
15 | path = upperCaseDriveRe.matched(1) + upperCaseDriveRe.matched(2).toLowerCase() + upperCaseDriveRe.matchedRight();
16 |
17 | var lastIdx = 0;
18 | while (true) {
19 | final idx = path.indexOf("/", lastIdx);
20 | if (idx == -1) {
21 | parts.push(urlEncode2(path.substring(lastIdx)));
22 | break;
23 | }
24 | parts.push(urlEncode2(path.substring(lastIdx, idx)));
25 | parts.push("/");
26 | lastIdx = idx + 1;
27 | }
28 | return new DocumentUri(parts.join(""));
29 | }
30 |
31 | private function urlEncode2(s:String):String {
32 | return ~/[!'()*]/g.map(s.urlEncode(), function(re) {
33 | return "%" + re.matched(0).fastCodeAt(0).hex();
34 | });
35 | }
36 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/extensions/FunctionFormattingConfigExtensions.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.extensions;
2 |
3 | import haxe.display.JsonModuleTypes;
4 | import haxeLanguageServer.Configuration.FunctionFormattingConfig;
5 |
6 | function shouldPrintReturn(config:FunctionFormattingConfig, signature:JsonFunctionSignature) {
7 | if (config.useArrowSyntax == true) {
8 | return false;
9 | }
10 | final returnStyle = config.returnTypeHint;
11 | return returnStyle == Always || (returnStyle == NonVoid && !signature.ret.isVoid());
12 | }
13 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/extensions/RangeExtensions.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.extensions;
2 |
3 | /**
4 | * Extends `languageServerProtocol.Types.Range` with the
5 | * same utility methods that `vscode.Range` provides
6 | * (`vscode\src\vs\workbench\api\node\extHostTypes.ts`).
7 | */
8 | /**
9 | * `true` if `start` and `end` are equal.
10 | */
11 | function isEmpty(range:Range):Bool {
12 | return range.end.isEqual(range.start);
13 | }
14 |
15 | /**
16 | * `true` if `start.line` and `end.line` are equal.
17 | */
18 | function isSingleLine(range:Range):Bool {
19 | return range.start.line == range.end.line;
20 | }
21 |
22 | /**
23 | * Check if a range is contained in this range.
24 | *
25 | * @param other A range.
26 | * @return `true` if the range is inside or equal
27 | * to this range.
28 | */
29 | inline function contains(range:Range, other:Range):Bool {
30 | return range.containsPos(other.start) && range.containsPos(other.end);
31 | }
32 |
33 | /**
34 | * Check if a position is contained in this range.
35 | *
36 | * @param pos A position.
37 | * @return `true` if the position is inside or equal
38 | * to this range.
39 | */
40 | function containsPos(range:Range, pos:Position):Bool {
41 | if (pos.isBefore(range.start)) {
42 | return false;
43 | }
44 | if (range.end.isBefore(pos)) {
45 | return false;
46 | }
47 | return true;
48 | }
49 |
50 | /**
51 | * Intersect `range` with this range and returns a new range or `undefined`
52 | * if the ranges have no overlap.
53 | *
54 | * @param range A range.
55 | * @return A range of the greater start and smaller end positions. Will
56 | * return undefined when there is no overlap.
57 | */
58 | function intersection(range:Range, other:Range):Null {
59 | final start = PositionStatics.Max(other.start, range.start);
60 | final end = PositionStatics.Min(other.end, range.end);
61 | if (start.isAfter(end)) {
62 | // this happens when there is no overlap:
63 | // |-----|
64 | // |----|
65 | return null;
66 | }
67 | return {start: start, end: end};
68 | }
69 |
70 | /**
71 | * Compute the union of `other` with this range.
72 | *
73 | * @param other A range.
74 | * @return A range of smaller start position and the greater end position.
75 | */
76 | function union(range:Range, other:Range):Range {
77 | if (range.contains(other)) {
78 | return range;
79 | } else if (other.contains(range)) {
80 | return other;
81 | }
82 | final start = PositionStatics.Min(other.start, range.start);
83 | final end = PositionStatics.Max(other.end, range.end);
84 | return {start: start, end: end};
85 | }
86 |
87 | /**
88 | * Derived a new range from this range.
89 | *
90 | * @param start A position that should be used as start. The default value is the [current start](#Range.start).
91 | * @param end A position that should be used as end. The default value is the [current end](#Range.end).
92 | * @return A range derived from this range with the given start and end position.
93 | * If start and end are not different `this` range will be returned.
94 | */
95 | function with(range:Range, ?start:Position, ?end:Position):Range {
96 | final start:Position = if (start == null) range.start else start;
97 | final end:Position = if (end == null) range.end else end;
98 |
99 | if (start.isEqual(range.start) && end.isEqual(range.end)) {
100 | return range;
101 | }
102 | return {start: start, end: end};
103 | }
104 |
105 | function isEqual(range:Range, other:Range):Bool {
106 | return range.start.isEqual(other.start) && range.end.isEqual(other.end);
107 | }
108 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/extensions/ResponseErrorExtensions.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.extensions;
2 |
3 | import jsonrpc.ResponseError;
4 | import jsonrpc.Types.NoData;
5 |
6 | function handler(reject:ResponseError->Void) {
7 | return function(error:String) reject(ResponseError.internalError(error));
8 | }
9 |
10 | function invalidXml(reject:ResponseError->Void, data:String) {
11 | reject(ResponseError.internalError("Invalid xml data: " + data));
12 | }
13 |
14 | function noTokens(reject:ResponseError->Void) {
15 | reject(ResponseError.internalError("Unable to build token tree"));
16 | }
17 |
18 | function noFittingDocument(reject:ResponseError->Void, uri:DocumentUri) {
19 | reject(ResponseError.internalError('Unable to find document for URI $uri, or feature is not supported for this file type / scheme'));
20 | }
21 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/extensions/StringExtensions.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.extensions;
2 |
3 | inline function occurrences(s:String, of:String) {
4 | return s.length - s.replace(of, "").length;
5 | }
6 |
7 | function untilLastDot(s:String) {
8 | final dotIndex = s.lastIndexOf(".");
9 | if (dotIndex == -1)
10 | return s;
11 | return s.substring(0, dotIndex);
12 | }
13 |
14 | function untilFirstDot(s:String) {
15 | final dotIndex = s.indexOf(".");
16 | if (dotIndex == -1)
17 | return s;
18 | return s.substring(0, dotIndex);
19 | }
20 |
21 | function afterLastDot(s:String) {
22 | final dotIndex = s.lastIndexOf(".");
23 | if (dotIndex == -1)
24 | return s;
25 | return s.substr(dotIndex + 1);
26 | }
27 |
28 | function last(s:String):String {
29 | return s.charAt(s.length - 1);
30 | }
31 |
32 | function capitalize(s:String):String {
33 | return s.charAt(0).toUpperCase() + s.substr(1);
34 | }
35 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/CompletionFeature.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features;
2 |
3 | import haxe.extern.EitherType;
4 | import haxeLanguageServer.features.haxe.completion.CompletionFeature as HaxeCompletionFeature;
5 | import haxeLanguageServer.features.hxml.CompletionFeature as HxmlCompletionFeature;
6 | import jsonrpc.CancellationToken;
7 | import jsonrpc.ResponseError;
8 | import jsonrpc.Types.NoData;
9 | import languageServerProtocol.Types.CompletionItem;
10 | import languageServerProtocol.Types.CompletionList;
11 |
12 | class CompletionFeature {
13 | final haxe:HaxeCompletionFeature;
14 | final hxml:HxmlCompletionFeature;
15 |
16 | public function new(context) {
17 | haxe = new HaxeCompletionFeature(context);
18 | hxml = new HxmlCompletionFeature(context);
19 |
20 | context.languageServerProtocol.onRequest(CompletionRequest.type, onCompletion);
21 | context.languageServerProtocol.onRequest(CompletionResolveRequest.type, onCompletionResolve);
22 | }
23 |
24 | function onCompletion(params:CompletionParams, token:CancellationToken, resolve:Null, CompletionList>>->Void,
25 | reject:ResponseError->Void) {
26 | final uri = params.textDocument.uri;
27 | if (uri.isHaxeFile()) {
28 | haxe.onCompletion(params, token, resolve, reject);
29 | } else if (uri.isHxmlFile()) {
30 | hxml.onCompletion(params, token, resolve, reject);
31 | } else {
32 | reject.noFittingDocument(uri);
33 | }
34 | }
35 |
36 | function onCompletionResolve(item:CompletionItem, token:CancellationToken, resolve:CompletionItem->Void, reject:ResponseError->Void) {
37 | if (item.data != null) {
38 | haxe.onCompletionResolve(item, token, resolve, reject);
39 | } else {
40 | resolve(item);
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/HoverFeature.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features;
2 |
3 | import haxeLanguageServer.features.haxe.HoverFeature as HaxeHoverFeature;
4 | import haxeLanguageServer.features.hxml.HoverFeature as HxmlHoverFeature;
5 | import jsonrpc.CancellationToken;
6 | import jsonrpc.ResponseError;
7 | import jsonrpc.Types.NoData;
8 | import languageServerProtocol.Types.Hover;
9 |
10 | class HoverFeature {
11 | final haxe:HaxeHoverFeature;
12 | final hxml:HxmlHoverFeature;
13 |
14 | public function new(context) {
15 | haxe = new HaxeHoverFeature(context);
16 | hxml = new HxmlHoverFeature(context);
17 |
18 | context.languageServerProtocol.onRequest(HoverRequest.type, onHover);
19 | }
20 |
21 | public function onHover(params:TextDocumentPositionParams, token:CancellationToken, resolve:Null->Void, reject:ResponseError->Void) {
22 | final uri = params.textDocument.uri;
23 | if (uri.isHaxeFile()) {
24 | haxe.onHover(params, token, resolve, reject);
25 | } else if (uri.isHxmlFile()) {
26 | hxml.onHover(params, token, resolve, reject);
27 | } else {
28 | reject.noFittingDocument(uri);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/DeterminePackageFeature.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe;
2 |
3 | import haxe.display.Display;
4 | import jsonrpc.CancellationToken;
5 | import jsonrpc.ResponseError;
6 | import jsonrpc.Types.NoData;
7 |
8 | class DeterminePackageFeature {
9 | final context:Context;
10 |
11 | public function new(context) {
12 | this.context = context;
13 | context.languageServerProtocol.onRequest(LanguageServerMethods.DeterminePackage, onDeterminePackage);
14 | }
15 |
16 | public function onDeterminePackage(params:{fsPath:String}, token:Null, resolve:{pack:String}->Void,
17 | reject:ResponseError->Void) {
18 | final handle = if (context.haxeServer.supports(DisplayMethods.DeterminePackage)) handleJsonRpc else handleLegacy;
19 | handle(new FsPath(params.fsPath), token, resolve, reject);
20 | }
21 |
22 | function handleJsonRpc(path:FsPath, token:Null, resolve:{pack:String}->Void, reject:ResponseError->Void) {
23 | context.callHaxeMethod(DisplayMethods.DeterminePackage, {file: path}, token, function(result) {
24 | resolve({pack: result.join(".")});
25 | return null;
26 | }, reject.handler());
27 | }
28 |
29 | function handleLegacy(path:FsPath, token:Null, resolve:{pack:String}->Void, reject:ResponseError->Void) {
30 | final args = ['$path@0@package'];
31 | context.callDisplay("@package", args, null, token, function(r) {
32 | switch r {
33 | case DCancelled:
34 | resolve({pack: ""});
35 | case DResult(data):
36 | resolve({pack: data});
37 | }
38 | }, reject.handler());
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/DocumentFormattingFeature.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe;
2 |
3 | import formatter.Formatter;
4 | import formatter.codedata.FormatterInputData.FormatterInputRange;
5 | import jsonrpc.CancellationToken;
6 | import jsonrpc.ResponseError;
7 | import jsonrpc.Types.NoData;
8 |
9 | class DocumentFormattingFeature {
10 | final context:Context;
11 |
12 | public function new(context) {
13 | this.context = context;
14 | context.languageServerProtocol.onRequest(DocumentFormattingRequest.type, onDocumentFormatting);
15 | context.languageServerProtocol.onRequest(DocumentRangeFormattingRequest.type, onDocumentRangeFormatting);
16 | }
17 |
18 | function onDocumentFormatting(params:DocumentFormattingParams, token:CancellationToken, resolve:Array->Void,
19 | reject:ResponseError->Void) {
20 | format(params.textDocument.uri, null, resolve, reject);
21 | }
22 |
23 | function onDocumentRangeFormatting(params:DocumentRangeFormattingParams, token:CancellationToken, resolve:Array->Void,
24 | reject:ResponseError->Void) {
25 | format(params.textDocument.uri, params.range, resolve, reject);
26 | }
27 |
28 | function format(uri:DocumentUri, range:Null, resolve:Array->Void, reject:ResponseError->Void) {
29 | final onResolve = context.startTimer("textDocument/formatting");
30 | final doc:Null = context.documents.getHaxe(uri);
31 | if (doc == null) {
32 | return reject.noFittingDocument(uri);
33 | }
34 | final tokens = doc.tokens;
35 | if (tokens == null) {
36 | return reject.noTokens();
37 | }
38 |
39 | var path;
40 | var origin;
41 | if (doc.uri.isFile()) {
42 | path = doc.uri.toFsPath().toString();
43 | origin = SourceFile(path);
44 | } else {
45 | path = context.workspacePath.toString();
46 | origin = Snippet;
47 | }
48 | final config = Formatter.loadConfig(path);
49 | var inputRange:Null = null;
50 | if (range != null) {
51 | range.start.character = 0;
52 | final converter = new Haxe3DisplayOffsetConverter();
53 | function convert(position) {
54 | return converter.characterOffsetToByteOffset(doc.content, doc.offsetAt(position));
55 | }
56 | inputRange = {
57 | startPos: convert(range.start),
58 | endPos: convert(range.end)
59 | }
60 | }
61 | final result = Formatter.format(Tokens(tokens.list, tokens.tree, tokens.bytes, origin), config, inputRange);
62 | switch result {
63 | case Success(formattedCode):
64 | final range:Range = if (range == null) {
65 | {
66 | start: {line: 0, character: 0},
67 | end: {line: doc.lineCount - 1, character: doc.lineAt(doc.lineCount - 1).length}
68 | }
69 | } else {
70 | range;
71 | }
72 | final edits = if (doc.getText(range) != formattedCode) [{range: range, newText: formattedCode}] else [];
73 | resolve(edits);
74 | onResolve(null, edits.length + " changes");
75 | case Failure(errorMessage):
76 | reject(ResponseError.internalError(errorMessage));
77 | case Disabled:
78 | reject(ResponseError.internalError("Formatting is disabled for this file"));
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/FindReferencesFeature.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe;
2 |
3 | import haxe.display.Display.DisplayMethods;
4 | import haxeLanguageServer.helper.HaxePosition;
5 | import jsonrpc.CancellationToken;
6 | import jsonrpc.ResponseError;
7 | import jsonrpc.Types.NoData;
8 | import languageServerProtocol.Types.Location;
9 |
10 | class FindReferencesFeature {
11 | final context:Context;
12 |
13 | public function new(context) {
14 | this.context = context;
15 | context.languageServerProtocol.onRequest(ReferencesRequest.type, onFindReferences);
16 | }
17 |
18 | function onFindReferences(params:TextDocumentPositionParams, token:CancellationToken, resolve:Null>->Void,
19 | reject:ResponseError->Void) {
20 | final uri = params.textDocument.uri;
21 | final doc = context.documents.getHaxe(uri);
22 | if (doc == null || !uri.isFile()) {
23 | return reject.noFittingDocument(uri);
24 | }
25 | final handle = if (context.haxeServer.supports(DisplayMethods.FindReferences)) handleJsonRpc else handleLegacy;
26 | final offset = context.displayOffsetConverter.characterOffsetToByteOffset(doc.content, doc.offsetAt(params.position));
27 | handle(params, token, resolve, reject, doc, offset);
28 | }
29 |
30 | function handleJsonRpc(params:TextDocumentPositionParams, token:CancellationToken, resolve:Null>->Void,
31 | reject:ResponseError->Void, doc:HxTextDocument, offset:Int) {
32 | context.callHaxeMethod(DisplayMethods.FindReferences, {
33 | file: doc.uri.toFsPath(),
34 | contents: doc.content,
35 | offset: offset,
36 | kind: WithBaseAndDescendants
37 | }, token, locations -> {
38 | resolve(locations.filter(location -> location != null).map(location -> {
39 | {
40 | uri: location.file.toUri(),
41 | range: location.range
42 | }
43 | }));
44 | return null;
45 | }, reject.handler());
46 | }
47 |
48 | function handleLegacy(params:TextDocumentPositionParams, token:CancellationToken, resolve:Null>->Void, reject:ResponseError->Void,
49 | doc:HxTextDocument, offset:Int) {
50 | final args = ['${doc.uri.toFsPath()}@$offset@usage'];
51 | context.callDisplay("@usage", args, doc.content, token, function(r) {
52 | switch r {
53 | case DCancelled:
54 | resolve(null);
55 | case DResult(data):
56 | final xml = try Xml.parse(data).firstElement() catch (_:Any) null;
57 | if (xml == null)
58 | return reject.invalidXml(data);
59 |
60 | final positions = [for (el in xml.elements()) el.firstChild().nodeValue];
61 | if (positions.length == 0)
62 | return resolve([]);
63 |
64 | final results = [];
65 | final haxePosCache = new Map();
66 | for (pos in positions) {
67 | final location = HaxePosition.parse(pos, doc, haxePosCache, context.displayOffsetConverter);
68 | if (location == null) {
69 | trace("Got invalid position: " + pos);
70 | continue;
71 | }
72 | results.push(location);
73 | }
74 |
75 | resolve(results);
76 | }
77 | }, reject.handler());
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/GotoDefinitionFeature.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe;
2 |
3 | import haxe.display.Display;
4 | import haxeLanguageServer.helper.HaxePosition;
5 | import jsonrpc.CancellationToken;
6 | import jsonrpc.ResponseError;
7 | import jsonrpc.Types.NoData;
8 | import languageServerProtocol.Types.DefinitionLink;
9 |
10 | class GotoDefinitionFeature {
11 | final context:Context;
12 |
13 | public function new(context) {
14 | this.context = context;
15 | context.languageServerProtocol.onRequest(DefinitionRequest.type, onGotoDefinition);
16 | }
17 |
18 | public function onGotoDefinition(params:TextDocumentPositionParams, token:CancellationToken, resolve:Array->Void,
19 | reject:ResponseError->Void) {
20 | final uri = params.textDocument.uri;
21 | final doc = context.documents.getHaxe(uri);
22 | if (doc == null || !uri.isFile()) {
23 | return reject.noFittingDocument(uri);
24 | }
25 | final handle = if (context.haxeServer.supports(DisplayMethods.GotoDefinition)) handleJsonRpc else handleLegacy;
26 | final offset = context.displayOffsetConverter.characterOffsetToByteOffset(doc.content, doc.offsetAt(params.position));
27 | handle(params, token, resolve, reject, doc, offset);
28 | }
29 |
30 | function handleJsonRpc(params:TextDocumentPositionParams, token:CancellationToken, resolve:Array->Void,
31 | reject:ResponseError->Void, doc:HxTextDocument, offset:Int) {
32 | context.callHaxeMethod(DisplayMethods.GotoDefinition, {
33 | file: doc.uri.toFsPath(),
34 | contents: doc.content,
35 | offset: offset
36 | }, token, function(locations) {
37 | resolve(locations.map(location -> {
38 | final document = getHaxeDocument(location.file.toUri());
39 | final tokens = document!.tokens;
40 | var previewDeclarationRange = location.range;
41 | if (document != null && tokens != null) {
42 | final targetToken = tokens!.getTokenAtOffset(document.offsetAt(location.range.start));
43 | final pos = targetToken!.parent!.getPos();
44 | if (pos != null)
45 | previewDeclarationRange = document.rangeAt(pos.min, pos.max);
46 | }
47 |
48 | final link:DefinitionLink = {
49 | targetUri: location.file.toUri(),
50 | targetRange: previewDeclarationRange,
51 | targetSelectionRange: location.range,
52 | };
53 | link;
54 | }));
55 | return null;
56 | }, reject.handler());
57 | }
58 |
59 | function getHaxeDocument(uri:DocumentUri):Null {
60 | var document = context.documents.getHaxe(uri);
61 | if (document == null) {
62 | final path = uri.toFsPath().toString();
63 | if (!sys.FileSystem.exists(path))
64 | return null;
65 | final content = sys.io.File.getContent(path);
66 | document = new HaxeDocument(uri, "haxe", 0, content);
67 | }
68 | return document;
69 | }
70 |
71 | function handleLegacy(params:TextDocumentPositionParams, token:CancellationToken, resolve:Array->Void, reject:ResponseError->Void,
72 | doc:HxTextDocument, offset:Int) {
73 | final args = ['${doc.uri.toFsPath()}@$offset@position'];
74 | context.callDisplay("@position", args, doc.content, token, function(r) {
75 | switch r {
76 | case DCancelled:
77 | resolve([]);
78 | case DResult(data):
79 | final xml = try Xml.parse(data).firstElement() catch (_:Any) null;
80 | if (xml == null)
81 | return reject.invalidXml(data);
82 |
83 | final positions = [for (el in xml.elements()) el.firstChild().nodeValue];
84 | if (positions.length == 0)
85 | resolve([]);
86 | final results:Array = [];
87 | for (pos in positions) {
88 | // no cache because this right now only returns one position
89 | final location = HaxePosition.parse(pos, doc, null, context.displayOffsetConverter);
90 | if (location == null) {
91 | trace("Got invalid position: " + pos);
92 | continue;
93 | }
94 | results.push({
95 | targetUri: location.uri,
96 | targetRange: location.range,
97 | targetSelectionRange: location.range
98 | });
99 | }
100 | resolve(results);
101 | }
102 | }, reject.handler());
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/GotoImplementationFeature.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe;
2 |
3 | import haxe.display.Display;
4 | import jsonrpc.CancellationToken;
5 | import jsonrpc.ResponseError;
6 | import jsonrpc.Types.NoData;
7 | import languageServerProtocol.Types.Definition;
8 | import languageServerProtocol.protocol.Implementation;
9 |
10 | class GotoImplementationFeature {
11 | final context:Context;
12 |
13 | public function new(context) {
14 | this.context = context;
15 | context.languageServerProtocol.onRequest(ImplementationRequest.type, onGotoImplementation);
16 | }
17 |
18 | public function onGotoImplementation(params:TextDocumentPositionParams, token:CancellationToken, resolve:Definition->Void,
19 | reject:ResponseError->Void) {
20 | final uri = params.textDocument.uri;
21 | final doc = context.documents.getHaxe(uri);
22 | if (doc == null || !uri.isFile()) {
23 | return reject.noFittingDocument(uri);
24 | }
25 | final offset = context.displayOffsetConverter.characterOffsetToByteOffset(doc.content, doc.offsetAt(params.position));
26 | handleJsonRpc(params, token, resolve, reject, doc, offset);
27 | }
28 |
29 | function handleJsonRpc(params:TextDocumentPositionParams, token:CancellationToken, resolve:Definition->Void, reject:ResponseError->Void,
30 | doc:HxTextDocument, offset:Int) {
31 | context.callHaxeMethod(DisplayMethods.GotoImplementation, {file: doc.uri.toFsPath(), contents: doc.content, offset: offset}, token, locations -> {
32 | resolve(locations.map(location -> {
33 | {
34 | uri: location.file.toUri(),
35 | range: location.range
36 | }
37 | }));
38 | return null;
39 | }, reject.handler());
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/GotoTypeDefinitionFeature.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe;
2 |
3 | import haxe.display.Display;
4 | import jsonrpc.CancellationToken;
5 | import jsonrpc.ResponseError;
6 | import jsonrpc.Types.NoData;
7 | import languageServerProtocol.Types.Definition;
8 | import languageServerProtocol.protocol.TypeDefinition;
9 |
10 | class GotoTypeDefinitionFeature {
11 | final context:Context;
12 |
13 | public function new(context) {
14 | this.context = context;
15 | context.languageServerProtocol.onRequest(TypeDefinitionRequest.type, onGotoTypeDefinition);
16 | }
17 |
18 | public function onGotoTypeDefinition(params:TextDocumentPositionParams, token:CancellationToken, resolve:Definition->Void,
19 | reject:ResponseError->Void) {
20 | final uri = params.textDocument.uri;
21 | final doc = context.documents.getHaxe(uri);
22 | if (doc == null || !uri.isFile()) {
23 | return reject.noFittingDocument(uri);
24 | }
25 | final offset = context.displayOffsetConverter.characterOffsetToByteOffset(doc.content, doc.offsetAt(params.position));
26 | context.callHaxeMethod(DisplayMethods.GotoTypeDefinition, {file: uri.toFsPath(), contents: doc.content, offset: offset}, token, locations -> {
27 | resolve(locations.map(location -> {
28 | {
29 | uri: location.file.toUri(),
30 | range: location.range
31 | }
32 | }));
33 | return null;
34 | }, reject.handler());
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/WorkspaceSymbolsFeature.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe;
2 |
3 | import haxeLanguageServer.helper.HaxePosition;
4 | import jsonrpc.CancellationToken;
5 | import jsonrpc.ResponseError;
6 | import jsonrpc.Types.NoData;
7 | import languageServerProtocol.Types.SymbolInformation;
8 | import languageServerProtocol.Types.SymbolKind;
9 |
10 | private enum abstract ModuleSymbolKind(Int) {
11 | final Class = 1;
12 | final Interface;
13 | final Enum;
14 | final TypeAlias;
15 | final Abstract;
16 | final Field;
17 | final Property;
18 | final Method;
19 | final Constructor;
20 | final Function;
21 | final Variable;
22 | final Struct;
23 | final EnumAbstract;
24 | final Operator;
25 | final EnumMember;
26 | final Constant;
27 | final Module;
28 | }
29 |
30 | private typedef ModuleSymbolEntry = {
31 | final name:String;
32 | final kind:ModuleSymbolKind;
33 | final range:Range;
34 | final ?containerName:String;
35 | final ?isDeprecated:Bool;
36 | }
37 |
38 | private typedef SymbolReply = {
39 | final file:FsPath;
40 | final symbols:Array;
41 | }
42 |
43 | class WorkspaceSymbolsFeature {
44 | final context:Context;
45 |
46 | public function new(context) {
47 | this.context = context;
48 | context.languageServerProtocol.onRequest(WorkspaceSymbolRequest.type, onWorkspaceSymbols);
49 | }
50 |
51 | function processSymbolsReply(data:Array, reject:ResponseError->Void) {
52 | final result = [];
53 | for (file in data) {
54 | final uri = HaxePosition.getProperFileNameCase(file.file).toUri();
55 | for (symbol in file.symbols) {
56 | if (symbol.range == null) {
57 | context.sendShowMessage(Error, "Unknown location for " + haxe.Json.stringify(symbol));
58 | continue;
59 | }
60 | result.push(moduleSymbolEntryToSymbolInformation(symbol, uri));
61 | }
62 | }
63 | return result;
64 | }
65 |
66 | function makeRequest(label:String, args:Array, doc:Null, token:CancellationToken, resolve:Array->Void,
67 | reject:ResponseError->Void) {
68 | final onResolve = context.startTimer("@workspace-symbols");
69 | context.callDisplay(label, args, doc == null ? null : doc.content, token, function(r) {
70 | switch r {
71 | case DCancelled:
72 | resolve([]);
73 | case DResult(data):
74 | final data:Array = try {
75 | haxe.Json.parse(data);
76 | } catch (e) {
77 | reject(ResponseError.internalError("Error parsing document symbol response: " + Std.string(e)));
78 | return;
79 | }
80 | final result = processSymbolsReply(data, reject);
81 | resolve(result);
82 | onResolve(data, data.length + " symbols");
83 | }
84 | }, reject.handler());
85 | }
86 |
87 | function onWorkspaceSymbols(params:WorkspaceSymbolParams, token:CancellationToken, resolve:Array->Void,
88 | reject:ResponseError->Void) {
89 | final args = ["?@0@workspace-symbols@" + params.query];
90 | makeRequest("@workspace-symbols", args, null, token, resolve, reject);
91 | }
92 |
93 | function moduleSymbolEntryToSymbolInformation(entry:ModuleSymbolEntry, uri:DocumentUri):SymbolInformation {
94 | final result:SymbolInformation = {
95 | name: entry.name,
96 | kind: switch entry.kind {
97 | case Class | Abstract: SymbolKind.Class;
98 | case Interface | TypeAlias: SymbolKind.Interface;
99 | case Enum: SymbolKind.Enum;
100 | case Constructor: SymbolKind.Constructor;
101 | case Field: SymbolKind.Field;
102 | case Method: SymbolKind.Method;
103 | case Function: SymbolKind.Function;
104 | case Property: SymbolKind.Property;
105 | case Variable: SymbolKind.Variable;
106 | case Struct: SymbolKind.Struct;
107 | case EnumAbstract: SymbolKind.Enum;
108 | case Operator: SymbolKind.Operator;
109 | case EnumMember: SymbolKind.EnumMember;
110 | case Constant: SymbolKind.Constant;
111 | case Module: SymbolKind.Module;
112 | },
113 | location: {
114 | uri: uri,
115 | range: entry.range
116 | }
117 | };
118 | if (entry.containerName != null) {
119 | result.containerName = entry.containerName;
120 | }
121 | if (entry.isDeprecated != null && entry.isDeprecated) {
122 | result.tags = [Deprecated];
123 | }
124 | return result;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/codeAction/CodeActionFeature.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe.codeAction;
2 |
3 | import haxeLanguageServer.features.haxe.codeAction.diagnostics.MissingArgumentsAction;
4 | import jsonrpc.CancellationToken;
5 | import jsonrpc.ResponseError;
6 | import jsonrpc.Types.NoData;
7 | import languageServerProtocol.Types.CodeAction;
8 | import languageServerProtocol.Types.Diagnostic;
9 |
10 | interface CodeActionContributor {
11 | function createCodeActions(params:CodeActionParams):Array;
12 | }
13 |
14 | enum CodeActionResolveType {
15 | MissingArg;
16 | }
17 |
18 | typedef CodeActionResolveData = {
19 | ?type:CodeActionResolveType,
20 | params:CodeActionParams,
21 | diagnostic:Diagnostic
22 | }
23 |
24 | class CodeActionFeature {
25 | public static inline final SourceSortImports = "source.sortImports";
26 |
27 | final context:Context;
28 | final contributors:Array = [];
29 |
30 | public function new(context) {
31 | this.context = context;
32 |
33 | context.registerCapability(CodeActionRequest.type, {
34 | documentSelector: Context.haxeSelector,
35 | codeActionKinds: [
36 | QuickFix,
37 | SourceOrganizeImports,
38 | SourceSortImports,
39 | RefactorExtract,
40 | RefactorRewrite
41 | ],
42 | resolveProvider: true
43 | });
44 | context.languageServerProtocol.onRequest(CodeActionRequest.type, onCodeAction);
45 | context.languageServerProtocol.onRequest(CodeActionResolveRequest.type, onCodeActionResolve);
46 |
47 | registerContributor(new ExtractConstantFeature(context));
48 | registerContributor(new DiagnosticsCodeActionFeature(context));
49 | #if debug
50 | registerContributor(new ExtractTypeFeature(context));
51 | registerContributor(new ExtractFunctionFeature(context));
52 | #end
53 | }
54 |
55 | public function registerContributor(contributor:CodeActionContributor) {
56 | contributors.push(contributor);
57 | }
58 |
59 | function onCodeAction(params:CodeActionParams, token:CancellationToken, resolve:Array->Void, reject:ResponseError->Void) {
60 | var codeActions = [];
61 | for (contributor in contributors) {
62 | codeActions = codeActions.concat(contributor.createCodeActions(params));
63 | }
64 | resolve(codeActions);
65 | }
66 |
67 | function onCodeActionResolve(action:CodeAction, token:CancellationToken, resolve:CodeAction->Void, reject:ResponseError->Void) {
68 | final data:Null = action.data;
69 | final type = data!.type;
70 | final params = data!.params;
71 | final diagnostic = data!.diagnostic;
72 | if (params == null || diagnostic == null) {
73 | resolve(action);
74 | return;
75 | }
76 | switch (type) {
77 | case null:
78 | resolve(action);
79 | case MissingArg:
80 | final promise = MissingArgumentsAction.createMissingArgumentsAction(context, action, params, diagnostic);
81 | if (promise == null) {
82 | reject(ResponseError.internalError("failed to resolve missing arguments action"));
83 | return;
84 | }
85 | promise.then(action -> {
86 | resolve(action);
87 | final command = action.command;
88 | if (command == null)
89 | return;
90 | context.languageServerProtocol.sendNotification(LanguageServerMethods.ExecuteClientCommand, {
91 | command: command.command,
92 | arguments: command.arguments ?? []
93 | });
94 | }).catchError((e) -> reject(e));
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/codeAction/DiagnosticsCodeActionFeature.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe.codeAction;
2 |
3 | import haxeLanguageServer.features.haxe.DiagnosticsFeature;
4 | import haxeLanguageServer.features.haxe.codeAction.CodeActionFeature;
5 | import haxeLanguageServer.features.haxe.codeAction.diagnostics.CompilerErrorActions;
6 | import haxeLanguageServer.features.haxe.codeAction.diagnostics.MissingFieldsActions;
7 | import haxeLanguageServer.features.haxe.codeAction.diagnostics.OrganizeImportActions;
8 | import haxeLanguageServer.features.haxe.codeAction.diagnostics.ParserErrorActions;
9 | import haxeLanguageServer.features.haxe.codeAction.diagnostics.RemovableCodeActions;
10 | import haxeLanguageServer.features.haxe.codeAction.diagnostics.UnresolvedIdentifierActions;
11 | import haxeLanguageServer.features.haxe.codeAction.diagnostics.UnusedImportActions;
12 | import languageServerProtocol.Types.CodeAction;
13 |
14 | using Lambda;
15 | using tokentree.TokenTreeAccessHelper;
16 | using tokentree.utils.TokenTreeCheckUtils;
17 |
18 | private enum FieldInsertionMode {
19 | IntoClass(rangeClass:Range, rangeEnd:Range);
20 | }
21 |
22 | class DiagnosticsCodeActionFeature implements CodeActionContributor {
23 | final context:Context;
24 |
25 | public function new(context) {
26 | this.context = context;
27 | }
28 |
29 | public function createCodeActions(params:CodeActionParams) {
30 | if (!params.textDocument.uri.isFile()) {
31 | return [];
32 | }
33 | var actions:Array = [];
34 | for (diagnostic in params.context.diagnostics) {
35 | if (diagnostic.code == null || !(diagnostic.code is Int)) { // our codes are int, so we don't handle other stuff
36 | continue;
37 | }
38 | final code = new DiagnosticKind(diagnostic.code);
39 | actions = actions.concat(switch code {
40 | case UnusedImport: UnusedImportActions.createUnusedImportActions(context, params, diagnostic);
41 | case UnresolvedIdentifier: UnresolvedIdentifierActions.createUnresolvedIdentifierActions(context, params, diagnostic);
42 | case CompilerError: CompilerErrorActions.createCompilerErrorActions(context, params, diagnostic);
43 | case RemovableCode: RemovableCodeActions.createRemovableCodeActions(context, params, diagnostic);
44 | case ParserError: ParserErrorActions.createParserErrorActions(context, params, diagnostic);
45 | case MissingFields: MissingFieldsActions.createMissingFieldsActions(context, params, diagnostic);
46 | case _: [];
47 | });
48 | }
49 | actions = OrganizeImportActions.createOrganizeImportActions(context, params, actions).concat(actions);
50 | actions = actions.filterDuplicates((a, b) -> a.title == b.title);
51 | return actions;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/CompilerErrorActions.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe.codeAction.diagnostics;
2 |
3 | class CompilerErrorActions {
4 | public static function createCompilerErrorActions(context:Context, params:CodeActionParams, diagnostic:Diagnostic):Array {
5 | if ((params.context.only != null) && (!params.context.only.contains(QuickFix))) {
6 | return [];
7 | }
8 | final actions:Array = [];
9 | final arg = context.diagnostics.getArguments(params.textDocument.uri, CompilerError, diagnostic.range);
10 | if (arg == null) {
11 | return actions;
12 | }
13 | final suggestionsRe = ~/\(Suggestions?: (.*)\)/;
14 | if (suggestionsRe.match(arg)) {
15 | final suggestions = suggestionsRe.matched(1).split(",");
16 | // Haxe reports the entire expression, not just the field position, so we have to be a bit creative here.
17 | final range = diagnostic.range;
18 | final fieldRe = ~/has no field ([^ ]+) /;
19 | if (fieldRe.match(arg)) {
20 | range.start.character = range.end.character - fieldRe.matched(1).length;
21 | }
22 | for (suggestion in suggestions) {
23 | suggestion = suggestion.trim();
24 | actions.push({
25 | title: "Change to " + suggestion,
26 | kind: QuickFix,
27 | edit: WorkspaceEditHelper.create(context, params, [{range: range, newText: suggestion}]),
28 | diagnostics: [diagnostic]
29 | });
30 | }
31 | return actions;
32 | }
33 |
34 | final invalidPackageRe = ~/Invalid package : ([\w.]*) should be ([\w.]*)/;
35 | if (invalidPackageRe.match(arg)) {
36 | final is = invalidPackageRe.matched(1);
37 | final shouldBe = invalidPackageRe.matched(2);
38 | final document = context.documents.getHaxe(params.textDocument.uri);
39 | if (document != null) {
40 | final replacement = document.getText(diagnostic.range).replace(is, shouldBe);
41 | actions.push({
42 | title: "Change to " + replacement,
43 | kind: QuickFix,
44 | edit: WorkspaceEditHelper.create(context, params, [{range: diagnostic.range, newText: replacement}]),
45 | diagnostics: [diagnostic],
46 | isPreferred: true
47 | });
48 | }
49 | }
50 |
51 | if (context.haxeServer.haxeVersion.major >= 4 // unsuitable error range before Haxe 4
52 | && arg.contains("should be declared with 'override' since it is inherited from superclass")) {
53 | var pos = diagnostic.range.start;
54 | final document = context.documents.getHaxe(params.textDocument.uri);
55 | if (document.tokens != null) {
56 | // Resolve parent token to add "override" before "fnunction" instead of function name
57 | final funPos = document.tokens!.getTokenAtOffset(document.offsetAt(diagnostic.range.start))!.parent!.pos!.min;
58 | if (funPos != null) {
59 | pos = document.positionAt(funPos);
60 | }
61 | }
62 | actions.push({
63 | title: "Add override keyword",
64 | kind: QuickFix,
65 | edit: WorkspaceEditHelper.create(context, params, [{range: pos.toRange(), newText: "override "}]),
66 | diagnostics: [diagnostic],
67 | isPreferred: true
68 | });
69 | }
70 |
71 | final tooManyArgsRe = ~/Too many arguments([\w.]*)/;
72 | if (tooManyArgsRe.match(arg)) {
73 | final document = context.documents.getHaxe(params.textDocument.uri);
74 | final replacement = document.getText(diagnostic.range);
75 | final data:CodeActionResolveData = {
76 | type: MissingArg,
77 | params: params,
78 | diagnostic: diagnostic
79 | };
80 | actions.push({
81 | title: "Add argument",
82 | data: data,
83 | kind: RefactorRewrite,
84 | diagnostics: [diagnostic],
85 | isPreferred: false
86 | });
87 | }
88 | return actions;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/OrganizeImportActions.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe.codeAction.diagnostics;
2 |
3 | import haxeLanguageServer.features.haxe.DiagnosticsFeature.DiagnosticKind;
4 | import haxeLanguageServer.features.haxe.codeAction.CodeActionFeature;
5 | import haxeLanguageServer.features.haxe.codeAction.OrganizeImportsFeature;
6 | import haxeLanguageServer.helper.DocHelper;
7 | import languageServerProtocol.Types.CodeAction;
8 |
9 | class OrganizeImportActions {
10 | public static function createOrganizeImportActions(context:Context, params:CodeActionParams, existingActions:Array):Array {
11 | var shouldQuickFix:Bool = true;
12 | var shouldOrganize:Bool = true;
13 | var shouldSort:Bool = true;
14 |
15 | if (params.context.only != null) {
16 | shouldQuickFix = params.context.only.contains(QuickFix);
17 | shouldOrganize = params.context.only.contains(SourceOrganizeImports);
18 | shouldSort = params.context.only.contains(CodeActionFeature.SourceSortImports);
19 | }
20 | if (!shouldQuickFix && !shouldOrganize && !shouldSort) {
21 | return [];
22 | }
23 |
24 | final uri = params.textDocument.uri;
25 | final doc = context.documents.getHaxe(uri);
26 | if (doc == null) {
27 | return [];
28 | }
29 | final map = context.diagnostics.getArgumentsMap(uri);
30 | final removeUnusedFixes = if (map == null) [] else [
31 | for (key in map.keys()) {
32 | if (key.code == DiagnosticKind.UnusedImport) {
33 | WorkspaceEditHelper.removeText(DocHelper.untrimRange(doc, key.range));
34 | }
35 | }
36 | ];
37 |
38 | final sortFixes = OrganizeImportsFeature.organizeImports(doc, context, []);
39 |
40 | final unusedRanges:Array = removeUnusedFixes.map(edit -> edit.range);
41 | final organizeFixes = removeUnusedFixes.concat(OrganizeImportsFeature.organizeImports(doc, context, unusedRanges));
42 |
43 | @:nullSafety(Off) // ?
44 | final diagnostics = existingActions.filter(action -> action.title == DiagnosticsFeature.RemoveUnusedImportUsingTitle)
45 | .map(action -> action.diagnostics)
46 | .flatten()
47 | .array();
48 |
49 | final actions:Array = [];
50 |
51 | if (shouldOrganize) {
52 | actions.push({
53 | title: DiagnosticsFeature.OrganizeImportsUsingsTitle,
54 | kind: SourceOrganizeImports,
55 | edit: WorkspaceEditHelper.create(context, params, organizeFixes),
56 | diagnostics: diagnostics
57 | });
58 | }
59 | if (shouldSort) {
60 | actions.push({
61 | title: DiagnosticsFeature.SortImportsUsingsTitle,
62 | kind: CodeActionFeature.SourceSortImports,
63 | edit: WorkspaceEditHelper.create(context, params, sortFixes)
64 | });
65 | }
66 |
67 | if (shouldQuickFix && diagnostics.length > 0 && removeUnusedFixes.length > 1) {
68 | actions.push({
69 | title: DiagnosticsFeature.RemoveAllUnusedImportsUsingsTitle,
70 | kind: QuickFix,
71 | edit: WorkspaceEditHelper.create(context, params, removeUnusedFixes),
72 | diagnostics: diagnostics
73 | });
74 | }
75 |
76 | return actions;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/RemovableCodeActions.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe.codeAction.diagnostics;
2 |
3 | class RemovableCodeActions {
4 | public static function createRemovableCodeActions(context:Context, params:CodeActionParams, diagnostic:Diagnostic):Array {
5 | if ((params.context.only != null) && (!params.context.only.contains(QuickFix))) {
6 | return [];
7 | }
8 | final range = context.diagnostics.getArguments(params.textDocument.uri, RemovableCode, diagnostic.range)!.range;
9 | if (range == null) {
10 | return [];
11 | }
12 | return [
13 | {
14 | title: "Remove",
15 | kind: QuickFix,
16 | edit: WorkspaceEditHelper.create(context, params, @:nullSafety(Off) [{range: range, newText: ""}]),
17 | diagnostics: [diagnostic],
18 | isPreferred: true
19 | }
20 | ];
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/UnresolvedIdentifierActions.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe.codeAction.diagnostics;
2 |
3 | import haxeLanguageServer.Configuration;
4 | import haxeLanguageServer.helper.ImportHelper;
5 | import haxeLanguageServer.helper.TypeHelper;
6 |
7 | class UnresolvedIdentifierActions {
8 | public static function createUnresolvedIdentifierActions(context:Context, params:CodeActionParams, diagnostic:Diagnostic):Array {
9 | if ((params.context.only != null) && (!params.context.only.contains(QuickFix))) {
10 | return [];
11 | }
12 | final args = context.diagnostics.getArguments(params.textDocument.uri, UnresolvedIdentifier, diagnostic.range);
13 | if (args == null) {
14 | return [];
15 | }
16 | var actions:Array = [];
17 | final importCount = args.count(a -> a.kind == Import);
18 | for (arg in args) {
19 | actions = actions.concat(switch arg.kind {
20 | case Import: createUnresolvedImportActions(context, params, diagnostic, arg, importCount);
21 | case Typo: createTypoActions(context, params, diagnostic, arg);
22 | });
23 | }
24 | return actions;
25 | }
26 |
27 | static function createUnresolvedImportActions(context:Context, params:CodeActionParams, diagnostic:Diagnostic, arg, importCount:Int):Array {
28 | final doc = context.documents.getHaxe(params.textDocument.uri);
29 | if (doc == null) {
30 | return [];
31 | }
32 | final preferredStyle = context.config.user.codeGeneration.imports.style;
33 | final secondaryStyle:ImportStyle = if (preferredStyle == Type) Module else Type;
34 |
35 | final importPosition = determineImportPosition(doc);
36 | function makeImportAction(style:ImportStyle):CodeAction {
37 | final path = if (style == Module) TypeHelper.getModule(arg.name) else arg.name;
38 | return {
39 | title: "Import " + path,
40 | kind: QuickFix,
41 | edit: WorkspaceEditHelper.create(context, params, [createImportsEdit(doc, importPosition, [arg.name], style)]),
42 | diagnostics: [diagnostic]
43 | };
44 | }
45 |
46 | final preferred = makeImportAction(preferredStyle);
47 | final secondary = makeImportAction(secondaryStyle);
48 | if (importCount == 1) {
49 | preferred.isPreferred = true;
50 | }
51 | final actions = [preferred, secondary];
52 |
53 | actions.push({
54 | title: "Change to " + arg.name,
55 | kind: QuickFix,
56 | edit: WorkspaceEditHelper.create(context, params, [
57 | {
58 | range: diagnostic.range,
59 | newText: arg.name
60 | }
61 | ]),
62 | diagnostics: [diagnostic]
63 | });
64 |
65 | return actions;
66 | }
67 |
68 | static function createTypoActions(context:Context, params:CodeActionParams, diagnostic:Diagnostic, arg):Array {
69 | return [
70 | {
71 | title: "Change to " + arg.name,
72 | kind: QuickFix,
73 | edit: WorkspaceEditHelper.create(context, params, [{range: diagnostic.range, newText: arg.name}]),
74 | diagnostics: [diagnostic]
75 | }
76 | ];
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/UnusedImportActions.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe.codeAction.diagnostics;
2 |
3 | import haxeLanguageServer.features.haxe.DiagnosticsFeature.*;
4 | import haxeLanguageServer.helper.DocHelper;
5 |
6 | class UnusedImportActions {
7 | public static function createUnusedImportActions(context:Context, params:CodeActionParams, diagnostic:Diagnostic):Array {
8 | if ((params.context.only != null) && (!params.context.only.contains(QuickFix))) {
9 | return [];
10 | }
11 | final doc = context.documents.getHaxe(params.textDocument.uri);
12 | if (doc == null) {
13 | return [];
14 | }
15 | return [
16 | {
17 | title: DiagnosticsFeature.RemoveUnusedImportUsingTitle,
18 | kind: QuickFix,
19 | edit: WorkspaceEditHelper.create(context, params, [
20 | {
21 | range: DocHelper.untrimRange(doc, diagnostic.range),
22 | newText: ""
23 | }
24 | ]),
25 | diagnostics: [diagnostic],
26 | isPreferred: true
27 | }
28 | ];
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/import.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe.codeAction.diagnostics;
2 |
3 | import haxeLanguageServer.features.haxe.codeAction.CodeActionFeature;
4 | import haxeLanguageServer.helper.WorkspaceEditHelper;
5 | import languageServerProtocol.Types.CodeAction;
6 | import languageServerProtocol.Types.Diagnostic;
7 |
8 | using Lambda;
9 | using tokentree.TokenTreeAccessHelper;
10 | using tokentree.utils.TokenTreeCheckUtils;
11 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/completion/CompletionContextData.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe.completion;
2 |
3 | import haxe.display.Display.CompletionMode;
4 | import haxeLanguageServer.helper.ImportHelper.ImportPosition;
5 | import haxeLanguageServer.tokentree.TokenContext;
6 |
7 | typedef CompletionContextData = {
8 | final replaceRange:Range;
9 | final mode:CompletionMode;
10 | final doc:HxTextDocument;
11 | final indent:String;
12 | final lineAfter:String;
13 | final params:CompletionParams;
14 | final importPosition:ImportPosition;
15 | final tokenContext:TokenContext;
16 | var isResolve:Bool;
17 | }
18 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/documentSymbols/DocumentSymbolsFeature.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe.documentSymbols;
2 |
3 | import haxe.extern.EitherType;
4 | import jsonrpc.CancellationToken;
5 | import jsonrpc.ResponseError;
6 | import jsonrpc.Types.NoData;
7 | import languageServerProtocol.Types.DocumentSymbol;
8 | import languageServerProtocol.Types.SymbolInformation;
9 |
10 | using Lambda;
11 |
12 | class DocumentSymbolsFeature {
13 | final context:Context;
14 |
15 | public function new(context) {
16 | this.context = context;
17 | context.languageServerProtocol.onRequest(DocumentSymbolRequest.type, onDocumentSymbols);
18 | }
19 |
20 | function onDocumentSymbols(params:DocumentSymbolParams, token:CancellationToken, resolve:Null>>->Void,
21 | reject:ResponseError->Void) {
22 | final onResolve = context.startTimer("textDocument/documentSymbol");
23 | final uri = params.textDocument.uri;
24 | final doc = context.documents.getHaxe(uri);
25 | if (doc == null) {
26 | return reject.noFittingDocument(uri);
27 | }
28 | if (doc.tokens == null) {
29 | return reject.noTokens();
30 | }
31 | final symbols = new DocumentSymbolsResolver(doc).resolve();
32 | resolve(symbols);
33 | onResolve(null, countSymbols(symbols) + " symbols");
34 | }
35 |
36 | function countSymbols(symbols:Null>):Int {
37 | return if (symbols == null) {
38 | 0;
39 | } else {
40 | symbols.length + symbols.map(symbol -> countSymbols(symbol.children)).fold((a, b) -> a + b, 0);
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/documentSymbols/SymbolStack.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe.documentSymbols;
2 |
3 | import haxe.display.Display.DisplayModuleTypeKind;
4 | import languageServerProtocol.Types.DocumentSymbol;
5 |
6 | /** (_not_ a video game level, simn) **/
7 | enum SymbolLevel {
8 | Root;
9 | Type(kind:DisplayModuleTypeKind);
10 | Field;
11 | Expression;
12 | }
13 |
14 | abstract SymbolStack(Array<{level:SymbolLevel, symbol:DocumentSymbol}>) {
15 | public var depth(get, set):Int;
16 |
17 | inline function get_depth()
18 | return this.length - 1;
19 |
20 | function set_depth(newDepth:Int) {
21 | if (newDepth > depth) {
22 | // only accounts for increases of 1
23 | if (this[newDepth] == null) {
24 | this[newDepth] = this[newDepth - 1];
25 | }
26 | } else if (newDepth < depth) {
27 | while (depth > newDepth) {
28 | this.pop();
29 | }
30 | }
31 | return depth;
32 | }
33 |
34 | public var level(get, never):SymbolLevel;
35 |
36 | inline function get_level()
37 | return this[depth].level;
38 |
39 | public var root(get, never):DocumentSymbol;
40 |
41 | inline function get_root()
42 | return this[0].symbol;
43 |
44 | public function new() {
45 | this = [
46 | {
47 | level: Root,
48 | symbol: {
49 | name: "root",
50 | kind: Module,
51 | range: null,
52 | selectionRange: null,
53 | children: []
54 | }
55 | }
56 | ];
57 | }
58 |
59 | public function addSymbol(level:SymbolLevel, symbol:DocumentSymbol, opensScope:Bool) {
60 | final parentSymbol = this[depth].symbol;
61 | if (parentSymbol.children == null) {
62 | parentSymbol.children = [];
63 | }
64 | parentSymbol.children.push(symbol);
65 |
66 | if (opensScope) {
67 | this[depth + 1] = {level: level, symbol: symbol};
68 | }
69 | }
70 |
71 | public function getParentTypeKind():Null {
72 | var i = depth;
73 | while (i-- > 0) {
74 | switch this[i].level {
75 | case Type(kind):
76 | return kind;
77 | case _:
78 | }
79 | }
80 | return null;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/foldingRange/FoldingRangeFeature.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe.foldingRange;
2 |
3 | import jsonrpc.CancellationToken;
4 | import jsonrpc.ResponseError;
5 | import jsonrpc.Types.NoData;
6 | import languageServerProtocol.Types.FoldingRange;
7 | import languageServerProtocol.protocol.FoldingRange;
8 |
9 | class FoldingRangeFeature {
10 | final context:Context;
11 |
12 | public function new(context) {
13 | this.context = context;
14 | context.languageServerProtocol.onRequest(FoldingRangeRequest.type, onFoldingRange);
15 | }
16 |
17 | function onFoldingRange(params:FoldingRangeParams, token:CancellationToken, resolve:Array->Void, reject:ResponseError->Void) {
18 | final onResolve = context.startTimer("textDocument/foldingRange");
19 | final uri = params.textDocument.uri;
20 | final doc = context.documents.getHaxe(uri);
21 | if (doc == null) {
22 | return reject.noFittingDocument(uri);
23 | }
24 | if (doc.tokens == null) {
25 | return reject.noTokens();
26 | }
27 | final ranges = new FoldingRangeResolver(doc, context.capabilities.textDocument).resolve();
28 | resolve(ranges);
29 | onResolve(null, ranges.length + " ranges");
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/haxe/foldingRange/FoldingRangeResolver.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.haxe.foldingRange;
2 |
3 | import languageServerProtocol.Types.FoldingRange;
4 | import languageServerProtocol.Types.FoldingRangeKind;
5 | import tokentree.TokenTree;
6 |
7 | using tokentree.TokenTreeAccessHelper;
8 |
9 | class FoldingRangeResolver {
10 | static final regionStartPattern = ~/^\s*[#{]?\s*region\b/;
11 | static final regionEndPattern = ~/^\s*[#}]?\s*end ?region\b/;
12 |
13 | final document:HaxeDocument;
14 | final lineFoldingOnly:Bool;
15 |
16 | public function new(document:HaxeDocument, capabilities:Null) {
17 | this.document = document;
18 | lineFoldingOnly = capabilities!.foldingRange!.lineFoldingOnly == true;
19 | }
20 |
21 | public function resolve():Array {
22 | final ranges:Array = [];
23 | function add(start:Position, end:Position, ?kind:FoldingRangeKind) {
24 | final range:FoldingRange = {
25 | startLine: start.line,
26 | endLine: end.line
27 | };
28 | if (!lineFoldingOnly) {
29 | range.startCharacter = start.character;
30 | range.endCharacter = end.character;
31 | }
32 | if (kind != null) {
33 | range.kind = kind;
34 | }
35 | ranges.push(range);
36 | }
37 |
38 | function addRange(range:haxe.macro.Expr.Position, ?kind:FoldingRangeKind) {
39 | final start = document.positionAt(range.min);
40 | final end = document.positionAt(range.max);
41 | add(start, end, kind);
42 | }
43 |
44 | var firstImport:Null = null;
45 | var lastImport:Null = null;
46 | final conditionalStack = [];
47 | final regionStack = [];
48 | final tokens = document.tokens;
49 | if (tokens == null) {
50 | return [];
51 | }
52 | tokens.tree.filterCallback(function(token:TokenTree, _) {
53 | switch token.tok {
54 | case BrOpen, Const(CString(_)), BkOpen:
55 | final range = tokens.getTreePos(token);
56 | final start = document.positionAt(range.min);
57 | final end = getEndOfPreviousLine(range.max);
58 | if (end.line > start.line) {
59 | add(start, end);
60 | }
61 |
62 | case Kwd(KwdCase), Kwd(KwdDefault):
63 | addRange(tokens.getTreePos(token));
64 |
65 | case Comment(_):
66 | addRange(tokens.getTreePos(token), Comment);
67 |
68 | case CommentLine(s) if (regionStartPattern.match(s)):
69 | regionStack.push(tokens.getPos(token).max);
70 |
71 | case CommentLine(s) if (regionEndPattern.match(s)):
72 | final start = regionStack.pop();
73 | if (start != null) {
74 | final end = tokens.getPos(token);
75 | @:nullSafety(Off)
76 | addRange({file: end.file, min: start, max: end.max}, Region);
77 | }
78 |
79 | case Kwd(KwdImport), Kwd(KwdUsing):
80 | if (firstImport == null) {
81 | firstImport = token;
82 | }
83 | lastImport = token;
84 |
85 | case Sharp(sharp):
86 | // everything except `#if` ends a range / adds a folding marker
87 | if (sharp == "else" || sharp == "elseif" || sharp == "end") {
88 | final start = conditionalStack.pop();
89 | final pos = tokens.getPos(token);
90 | final end = getEndOfPreviousLine(pos.max);
91 | if (start != null && end.line > start.line) {
92 | add(start, end);
93 | }
94 | }
95 |
96 | // everything except `#end` starts a range
97 | if (sharp == "if" || sharp == "else" || sharp == "elseif") {
98 | final pClose:Null = token.access().firstChild().matches(POpen).lastChild().matches(PClose).token;
99 | final pos = if (pClose == null) tokens.getPos(token) else tokens.getPos(pClose);
100 | final start = document.positionAt(pos.max);
101 | start.character++;
102 | conditionalStack.push(start);
103 | }
104 |
105 | case _:
106 | }
107 | return GoDeeper;
108 | });
109 |
110 | if (lastImport != null && firstImport != lastImport) {
111 | final start = tokens.getPos(firstImport);
112 | final end = tokens.getTreePos(lastImport);
113 | addRange({file: start.file, min: start.min, max: end.max}, Imports);
114 | }
115 |
116 | return ranges;
117 | }
118 |
119 | function getEndOfPreviousLine(offset:Int):Position {
120 | final endLine = document.positionAt(offset).line - 1;
121 | final endCharacter = document.lineAt(endLine).length - 1;
122 | return {line: endLine, character: endCharacter};
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/hxml/HoverFeature.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.hxml;
2 |
3 | import haxeLanguageServer.features.hxml.HxmlContextAnalyzer.analyzeHxmlContext;
4 | import haxeLanguageServer.helper.DocHelper.printCodeBlock;
5 | import jsonrpc.CancellationToken;
6 | import jsonrpc.ResponseError;
7 | import jsonrpc.Types.NoData;
8 | import languageServerProtocol.Types.Hover;
9 | import languageServerProtocol.Types.MarkupKind;
10 |
11 | class HoverFeature {
12 | final context:Context;
13 |
14 | public function new(context) {
15 | this.context = context;
16 | context.languageServerProtocol.onRequest(HoverRequest.type, onHover);
17 | }
18 |
19 | public function onHover(params:TextDocumentPositionParams, token:CancellationToken, resolve:Null->Void, reject:ResponseError->Void) {
20 | final uri = params.textDocument.uri;
21 | final doc = context.documents.getHxml(uri);
22 | if (doc == null) {
23 | return reject.noFittingDocument(uri);
24 | }
25 | final pos = params.position;
26 | final line = doc.lineAt(pos.line);
27 | final hxmlContext = analyzeHxmlContext(line, pos);
28 | function makeHover(sections:Array):Hover {
29 | return {
30 | contents: {
31 | kind: MarkDown,
32 | value: sections.join("\n\n---\n")
33 | },
34 | range: hxmlContext.range
35 | }
36 | }
37 | resolve(switch hxmlContext.element {
38 | case Flag(flag) if (flag != null):
39 | var signature = flag.name;
40 | if (flag.argument != null) {
41 | signature += " " + flag.argument.name;
42 | }
43 | makeHover([printCodeBlock(signature, Hxml), flag.description]);
44 |
45 | case EnumValue(value, _) if (value != null):
46 | final sections = [printCodeBlock(value.name, Hxml)];
47 | if (value.description != null) {
48 | sections.push(value.description);
49 | }
50 | makeHover(sections);
51 |
52 | case Define(define) if (define != null):
53 | makeHover([
54 | printCodeBlock(define.getRealName(), Hxml),
55 | define.printDetails(context.haxeServer.haxeVersion)
56 | ]);
57 |
58 | case DefineValue(define, value): null;
59 | case _: null;
60 | });
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/hxml/HxmlContextAnalyzer.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.hxml;
2 |
3 | import haxeLanguageServer.features.hxml.data.Defines;
4 | import haxeLanguageServer.features.hxml.data.Flags;
5 | import haxeLanguageServer.features.hxml.data.Shared;
6 |
7 | using Lambda;
8 |
9 | typedef HxmlContext = {
10 | final element:HxmlElement;
11 | final range:Range;
12 | }
13 |
14 | enum HxmlElement {
15 | Flag(?flag:Flag);
16 | EnumValue(?value:EnumValue, values:EnumValues);
17 | Define(?define:Define);
18 | DefineValue(?define:Define, value:String);
19 | File(?path:String);
20 | Directory(?path:String);
21 | LibraryName(?name:String);
22 | Unknown;
23 | }
24 |
25 | function analyzeHxmlContext(line:String, pos:Position):HxmlContext {
26 | final range = findWordRange(line, pos.character);
27 | line = line.substring(0, range.end);
28 | final parts = ~/\s+/.replace(line.ltrim(), " ").split(" ");
29 | function findFlag(word) {
30 | return HxmlFlags.flatten().find(f -> f.name == word || f.shortName == word || f.deprecatedNames!.contains(word));
31 | }
32 | return {
33 | element: switch parts {
34 | case []: Flag();
35 | case [flag]: Flag(findFlag(flag));
36 | case [flag, arg]:
37 | final flag = findFlag(flag);
38 | switch flag!.argument!.kind {
39 | case null: Unknown;
40 | case Enum(values): EnumValue(values.find(v -> v.name == arg), values);
41 | case Define:
42 | function findDefine(define) {
43 | return getDefines(true).find(d -> d.matches(define));
44 | }
45 | switch arg.split("=") {
46 | case []: Define();
47 | case [define]: Define(findDefine(define));
48 | case [define, value]:
49 | final define = findDefine(define);
50 | final enumValues = define!.getEnumValues();
51 | if (enumValues != null) {
52 | EnumValue(enumValues.find(v -> v.name == arg), enumValues);
53 | } else {
54 | DefineValue(define, value);
55 | }
56 | case _: Unknown;
57 | }
58 | case File: File(arg);
59 | case Directory: Directory(arg);
60 | case LibraryName: LibraryName(arg);
61 | }
62 | case _:
63 | Unknown; // no completion after the first argument
64 | },
65 | range: {
66 | start: {line: pos.line, character: range.start},
67 | end: {line: pos.line, character: range.end}
68 | }
69 | };
70 | }
71 |
72 | private function findWordRange(s:String, index:Int) {
73 | function isWordBoundary(c:String):Bool {
74 | return c.isSpace(0) || c == "=" || c == ":";
75 | }
76 | var start = 0;
77 | var end = 0;
78 | var inWord = false;
79 | for (i in 0...s.length) {
80 | final c = s.charAt(i);
81 | if (isWordBoundary(c)) {
82 | if (inWord) {
83 | inWord = false;
84 | end = i;
85 | if (start <= index && end >= index) {
86 | // "Te|xt"
87 | return {start: start, end: end};
88 | }
89 | }
90 | } else {
91 | if (!inWord) {
92 | inWord = true;
93 | start = i;
94 | }
95 | }
96 | }
97 | // "Text|"
98 | if (inWord) {
99 | return {start: start, end: s.length};
100 | }
101 | // "Text |"
102 | return {start: index, end: index};
103 | }
104 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/features/hxml/data/Shared.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.features.hxml.data;
2 |
3 | typedef EnumValue = {
4 | final name:String;
5 | final ?description:String;
6 | }
7 |
8 | typedef EnumValues = ReadOnlyArray;
9 |
10 | final DceEnumValues:EnumValues = [
11 | {
12 | name: "full",
13 | description: "Apply dead code elimination to all code."
14 | },
15 | {
16 | name: "std",
17 | description: "Only apply dead code elimination to the standard library."
18 | },
19 | {
20 | name: "no",
21 | description: "Disable dead code elimination."
22 | }
23 | ];
24 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/helper/DisplayOffsetConverter.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.helper;
2 |
3 | import js.node.Buffer;
4 |
5 | /**
6 | This is a helper that provides completion between character and bytes offsets.
7 | This is required in Haxe 3.x because it uses byte offsets for positions and display queries.
8 | Haxe 4, however, uses unicode-aware lexer and uses characters for positions, so no
9 | conversion is required. So we have two implementations, one of which is selected based on
10 | Haxe version.
11 | **/
12 | abstract class DisplayOffsetConverter {
13 | public static function create(haxeVersion:SemVer):DisplayOffsetConverter {
14 | return if (haxeVersion >= new SemVer(4, 0, 0)) new Haxe4DisplayOffsetConverter() else new Haxe3DisplayOffsetConverter();
15 | }
16 |
17 | public function byteRangeToCharacterRange(range:Range, doc:HxTextDocument):Range {
18 | return {
19 | start: {
20 | line: range.start.line,
21 | character: byteOffsetToCharacterOffset(doc.lineAt(range.start.line), range.start.character)
22 | },
23 | end: {
24 | line: range.end.line,
25 | character: byteOffsetToCharacterOffset(doc.lineAt(range.end.line), range.end.character)
26 | }
27 | };
28 | }
29 |
30 | public abstract function positionCharToZeroBasedColumn(char:Int):Int;
31 |
32 | public abstract function byteOffsetToCharacterOffset(string:String, byteOffset:Int):Int;
33 |
34 | public abstract function characterOffsetToByteOffset(string:String, offset:Int):Int;
35 | }
36 |
37 | class Haxe3DisplayOffsetConverter extends DisplayOffsetConverter {
38 | public function new() {}
39 |
40 | function positionCharToZeroBasedColumn(char:Int):Int {
41 | return char;
42 | }
43 |
44 | function byteOffsetToCharacterOffset(string:String, byteOffset:Int):Int {
45 | final buf = Buffer.from(string, "utf-8");
46 | return buf.toString("utf-8", 0, byteOffset).length;
47 | }
48 |
49 | function characterOffsetToByteOffset(string:String, offset:Int):Int {
50 | if (offset == 0)
51 | return 0;
52 | else if (offset == string.length)
53 | return Buffer.byteLength(string, "utf-8");
54 | else
55 | return Buffer.byteLength(string.substr(0, offset), "utf-8");
56 | }
57 | }
58 |
59 | class Haxe4DisplayOffsetConverter extends DisplayOffsetConverter {
60 | public function new() {}
61 |
62 | function positionCharToZeroBasedColumn(char:Int):Int {
63 | return char - 1;
64 | }
65 |
66 | function byteOffsetToCharacterOffset(string:String, offset:Int):Int {
67 | return inline offsetSurrogates(string, offset, 1);
68 | }
69 |
70 | function characterOffsetToByteOffset(string:String, offset:Int):Int {
71 | return inline offsetSurrogates(string, offset, -1);
72 | }
73 |
74 | function offsetSurrogates(string:String, offset:Int, direction:Int):Int {
75 | var ret = offset;
76 | var i = 0, j = 0;
77 | while (j < string.length && i < offset) {
78 | var ch = string.charCodeAt(j).sure();
79 | if (ch >= 0xD800 && ch < 0xDC00) {
80 | ret += direction;
81 | j++;
82 | }
83 | i++;
84 | j++;
85 | }
86 | return ret;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/helper/DocHelper.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.helper;
2 |
3 | import haxeLanguageServer.helper.JavadocHelper.DocTag;
4 |
5 | class DocHelper {
6 | static final reStartsWhitespace = ~/^\s*/;
7 | static final reEndsWithWhitespace = ~/\s*$/;
8 |
9 | /** Stolen from dox **/
10 | public static function trim(doc:String) {
11 | if (doc == null)
12 | return '';
13 |
14 | // trim leading asterisks
15 | while (doc.charAt(0) == '*')
16 | doc = doc.substr(1);
17 |
18 | // trim trailing asterisks
19 | while (doc.charAt(doc.length - 1) == '*')
20 | doc = doc.substr(0, doc.length - 1);
21 |
22 | // trim additional whitespace
23 | doc = doc.trim();
24 |
25 | // detect doc comment style/indent
26 | final ereg = ~/^([ \t]+(\* )?)[^\s\*]/m;
27 | final matched = ereg.match(doc);
28 |
29 | if (matched) {
30 | var string = ereg.matched(1);
31 |
32 | // escape asterisk and allow one optional space after it
33 | string = string.split('* ').join('\\* ?');
34 |
35 | final indent = new EReg("^" + string, "gm");
36 | doc = indent.replace(doc, "");
37 | }
38 |
39 | // TODO: check why this is necessary (dox doesn't seem to need it...)
40 | if (doc.charAt(0) == '*')
41 | doc = doc.substr(1).ltrim();
42 |
43 | return doc;
44 | }
45 |
46 | public static function markdownFormat(doc:String):String {
47 | function tableLine(a, b)
48 | return '| $a | $b |\n';
49 | function tableHeader(a, b)
50 | return "\n" + tableLine(a, b) + tableLine("------", "------");
51 | function replaceNewlines(s:String, by:String)
52 | return s.replace("\n", by).replace("\r", by);
53 | function mapDocTags(tags:Array)
54 | return tags.map(function(p) {
55 | final desc = replaceNewlines(p.doc, " ");
56 | return tableLine("`" + p.value + "`", desc);
57 | }).join("");
58 |
59 | doc = trim(doc);
60 | final docInfos = JavadocHelper.parse(doc);
61 | var result = docInfos.doc;
62 | final hasParams = docInfos.params.length > 0;
63 | final hasReturn = docInfos.returns != null;
64 |
65 | result += "\n";
66 |
67 | if (docInfos.deprecated != null)
68 | result += "\n**Deprecated:** " + docInfos.deprecated.doc + "\n";
69 |
70 | if (hasParams || hasReturn)
71 | result += tableHeader("Argument", "Description");
72 | if (hasParams)
73 | result += mapDocTags(docInfos.params);
74 | if (hasReturn)
75 | result += tableLine("`return`", @:nullSafety(Off) replaceNewlines(docInfos.returns.doc, " "));
76 |
77 | if (docInfos.throws.length > 0)
78 | result += tableHeader("Exception", "Description") + mapDocTags(docInfos.throws);
79 |
80 | if (docInfos.events.length > 0)
81 | result += tableHeader("Event", "Description") + mapDocTags(docInfos.events);
82 |
83 | if (docInfos.sees.length > 0)
84 | result += "\nSee also:\n" + docInfos.sees.map(function(p) return "* " + p.doc).join("\n") + "\n";
85 |
86 | if (docInfos.since != null)
87 | result += '\n_Available since ${docInfos.since.doc}_';
88 |
89 | return result;
90 | }
91 |
92 | public static function extractText(doc:String):Null {
93 | if (doc == null) {
94 | return null;
95 | }
96 | var result = "";
97 | for (line in doc.trim().split("\n")) {
98 | line = line.trim();
99 | if (line.startsWith("*")) // JavaDoc-style comments
100 | line = line.substr(1);
101 | result += if (line == "") "\n\n" else line + " ";
102 | }
103 | return result;
104 | }
105 |
106 | public static function printCodeBlock(content:String, languageId:LanguageId):String {
107 | return '```$languageId\n$content\n```';
108 | }
109 |
110 | /**
111 | expands range to encompass full lines when range has leading or trailing whitespace in first and / or last line
112 |
113 | @param doc referenced document
114 | @param range selected range inside document
115 | **/
116 | public static function untrimRange(doc:HxTextDocument, range:Range) {
117 | final startLine = doc.lineAt(range.start.line);
118 | if (reStartsWhitespace.match(startLine.substring(0, range.start.character)))
119 | range = {
120 | start: {
121 | line: range.start.line,
122 | character: 0
123 | },
124 | end: range.end
125 | };
126 |
127 | final endLine = if (range.start.line == range.end.line) startLine else doc.lineAt(range.end.line);
128 | if (reEndsWithWhitespace.match(endLine.substring(range.end.character)))
129 | range = {
130 | start: range.start,
131 | end: {
132 | line: range.end.line + 1,
133 | character: 0
134 | }
135 | };
136 | return range;
137 | }
138 | }
139 |
140 | enum abstract LanguageId(String) to String {
141 | final Haxe = "haxe";
142 | final HaxeType = "haxe.type";
143 | final HaxeArgument = "haxe.argument";
144 | final Hxml = "hxml";
145 | }
146 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/helper/FormatterHelper.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.helper;
2 |
3 | import formatter.Formatter;
4 | import tokentree.TokenTreeBuilder;
5 |
6 | class FormatterHelper {
7 | public static function formatText(doc:HxTextDocument, context:Context, code:String, entryPoint:TokenTreeEntryPoint):String {
8 | var path;
9 | var origin;
10 | if (doc.uri.isFile()) {
11 | path = doc.uri.toFsPath().toString();
12 | origin = SourceFile(path);
13 | } else {
14 | path = context.workspacePath.toString();
15 | origin = Snippet;
16 | }
17 | final config = Formatter.loadConfig(path);
18 | switch Formatter.format(Code(code, origin), config, null, entryPoint) {
19 | case Success(formattedCode):
20 | return formattedCode;
21 | case Failure(_):
22 | case Disabled:
23 | }
24 | return code;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/helper/FsHelper.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.helper;
2 |
3 | import haxe.io.Path;
4 | import js.lib.Promise;
5 | import js.node.Fs.Fs;
6 | import sys.FileSystem;
7 | import sys.io.File;
8 |
9 | class FsHelper {
10 | public static function cp(source:String, destination:String):Promise {
11 | var promises = new Array>();
12 | var stats = Fs.lstatSync(source);
13 |
14 | if (stats.isDirectory()) {
15 | if (!FileSystem.exists(destination))
16 | FileSystem.createDirectory(destination);
17 | var files = Fs.readdirSync(source);
18 |
19 | for (f in files) {
20 | var source = Path.join([source, f]);
21 | var destination = Path.join([destination, f]);
22 | var stats = Fs.statSync(source);
23 | promises.push((if (stats.isDirectory()) cp else copyFile)(source, destination));
24 | }
25 | } else if (stats.isFile()) {
26 | promises.push(copyFile(source, destination));
27 | }
28 |
29 | return Promise.all(promises).then((_) -> null);
30 | }
31 |
32 | public static function rmdir(path:String):Promise {
33 | try {
34 | if (!Fs.existsSync(path))
35 | return Promise.resolve();
36 |
37 | var stats = Fs.lstatSync(path);
38 | if (!stats.isDirectory())
39 | return rmFile(path);
40 |
41 | return Promise.all([
42 | for (f in Fs.readdirSync(path))
43 | rmdir(Path.join([path, f]))
44 | ]).then((_) -> Fs.rmdirSync(path));
45 | } catch (err) {
46 | return Promise.reject(err);
47 | }
48 | }
49 |
50 | public static function rmFile(path:String):Promise {
51 | Fs.unlinkSync(path);
52 | return Promise.resolve();
53 | }
54 |
55 | public static function copyFile(source:String, destination:String):Promise {
56 | var dir = Path.directory(destination);
57 | if (!FileSystem.exists(dir))
58 | FileSystem.createDirectory(dir);
59 |
60 | File.copy(source, destination);
61 | return Promise.resolve();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/haxeLanguageServer/helper/HaxePosition.hx:
--------------------------------------------------------------------------------
1 | package haxeLanguageServer.helper;
2 |
3 | import languageServerProtocol.Types.Location;
4 |
5 | class HaxePosition {
6 | static final positionRe = ~/^(.+):(\d+): (?:lines (\d+)-(\d+)|character(?:s (\d+)-| )(\d+))$/;
7 | static final properFileNameCaseCache = new Map();
8 | static final isWindows = (Sys.systemName() == "Windows");
9 |
10 | public static function parse(pos:String, doc:HxTextDocument, cache:Null