From 761d7583a8b4b80a3ddc539e83d16dd6f44b39b2 Mon Sep 17 00:00:00 2001 From: Mark Wiemer <7833360+mark-wiemer@users.noreply.github.com> Date: Sat, 29 Jul 2023 08:13:46 -0700 Subject: [PATCH] Fixup IntelliSense-related behavior (#368) * Fixup typo in 3.3.0 changelog * Remove space from trigger char list (#110) * Remove unused code in completionProvider.ts * Replace Method.document with Method.uriString * Refactor completion items for methods * Refactor `preChar` calc and add comments * Cleanup completionProvider so it's testable * Fix parser casing * Document buildScript and Script * Fixup provideCompletionItemsInner docs * Update settings descriptions * prefer const * cleanup debug dispatcher start * WIP deep pick and basic tests * Change Omit to Pick * Fixup completionProvider test * Cleanup API for completion items * Fixup types * a * fixup comments * wip second test * fixup intellisense settings * note outer tests TBD * Remove unnecessary async/await * Improve docs for Method class * Show comma-space separated params in IntelliSense * Simplify PartialMethod type, removing DeepPick * More tests * Finalize provideCompletionItemsInner * Remove console log * Run all tests, not just new ones! * Mark format tests for consolidation * Fixup provideDocumentFormattingEdits unused param * Add manual tests to repo, update demo * fixup demos * Add completion provider manual tests * Change `SharpDirective` to `IfDirective` for clarity * Fixup manual tests --- .eslintrc | 3 +- Changelog.md | 2 +- demos/demo_for_ahk_v1.ahk | 106 ------ demos/manualTests/completionProvider.ahk | 27 ++ demos/manualTests/debugger.ahk | 12 + demos/manualTests/folding.ahk | 7 + demos/manualTests/hover.ahk | 5 + demos/manualTests/runSelection.ahk | 10 + demos/v1Demo.ahk | 220 +++++++++++ demos/{demo_for_ahk_v2.ahk => v2Demo.ahk} | 0 package-lock.json | 161 ++++++++ package.json | 20 +- src/common/global.ts | 6 +- src/debugger/debugDispatcher.ts | 6 +- src/extension.ts | 1 - src/parser/model.ts | 15 +- src/parser/parser.ts | 6 +- src/providers/completionProvider.ts | 192 ++++++---- src/providers/defProvider.ts | 3 +- src/providers/formattingProvider.ts | 12 +- src/providers/formattingProvider.types.ts | 2 +- src/providers/formattingProvider.utils.ts | 18 +- src/providers/signatureProvider.ts | 4 +- src/providers/symbolProvider.ts | 2 +- src/test/suite/format/format.test.ts | 23 +- ...irective.in.ahk => 55-if-directive.in.ahk} | 0 ...ective.out.ahk => 55-if-directive.out.ahk} | 0 ...dent-code-after-if-directive-false.in.ahk} | 0 ...ent-code-after-if-directive-false.out.ahk} | 0 ...ndent-code-after-if-directive-true.in.ahk} | 0 ...dent-code-after-if-directive-true.out.ahk} | 0 ...in.ahk => legacy-text-if-directive.in.ahk} | 0 ...t.ahk => legacy-text-if-directive.out.ahk} | 0 src/test/suite/parser/parser.test.ts | 6 +- .../completionProvider.test.ts | 358 ++++++++++++++++++ .../formattingProvider.utils.test.ts | 36 +- src/test/suite/service/runnerService.test.ts | 2 +- 37 files changed, 998 insertions(+), 267 deletions(-) delete mode 100644 demos/demo_for_ahk_v1.ahk create mode 100644 demos/manualTests/completionProvider.ahk create mode 100644 demos/manualTests/debugger.ahk create mode 100644 demos/manualTests/folding.ahk create mode 100644 demos/manualTests/hover.ahk create mode 100644 demos/manualTests/runSelection.ahk create mode 100644 demos/v1Demo.ahk rename demos/{demo_for_ahk_v2.ahk => v2Demo.ahk} (100%) rename src/test/suite/format/samples/{55-sharp-directive.in.ahk => 55-if-directive.in.ahk} (100%) rename src/test/suite/format/samples/{55-sharp-directive.out.ahk => 55-if-directive.out.ahk} (100%) rename src/test/suite/format/samples/{indent-code-after-sharp-directive-false.in.ahk => indent-code-after-if-directive-false.in.ahk} (100%) rename src/test/suite/format/samples/{indent-code-after-sharp-directive-false.out.ahk => indent-code-after-if-directive-false.out.ahk} (100%) rename src/test/suite/format/samples/{indent-code-after-sharp-directive-true.in.ahk => indent-code-after-if-directive-true.in.ahk} (100%) rename src/test/suite/format/samples/{indent-code-after-sharp-directive-true.out.ahk => indent-code-after-if-directive-true.out.ahk} (100%) rename src/test/suite/format/samples/{legacy-text-sharp-directive.in.ahk => legacy-text-if-directive.in.ahk} (100%) rename src/test/suite/format/samples/{legacy-text-sharp-directive.out.ahk => legacy-text-if-directive.out.ahk} (100%) create mode 100644 src/test/suite/providers/completionProvider/completionProvider.test.ts diff --git a/.eslintrc b/.eslintrc index 8f397c07..a89fa76d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -11,7 +11,8 @@ "@typescript-eslint/semi": "warn", "curly": "warn", "eqeqeq": "warn", - "no-throw-literal": "warn" + "no-throw-literal": "warn", + "prefer-const": "error" }, "ignorePatterns": ["out", "dist", "**/*.d.ts"] } diff --git a/Changelog.md b/Changelog.md index ee2af5a2..e3dd03a0 100644 --- a/Changelog.md +++ b/Changelog.md @@ -25,7 +25,7 @@ Fixes: -- Fix several syntax highlighting issues([#85](https://github.com/mark-wiemer/vscode-autohotkey-plus-plus/issues/85), [#318](https://github.com/mark-wiemer/vscode-autohotkey-plus-plus/issues/318)) +- Fix several syntax highlighting issues ([#85](https://github.com/mark-wiemer/vscode-autohotkey-plus-plus/issues/85), [#318](https://github.com/mark-wiemer/vscode-autohotkey-plus-plus/issues/318)) - Fix minor debugger issues introduced in 3.1.0 ([#279](https://github.com/mark-wiemer/vscode-autohotkey-plus-plus/issues/279)) - Fix debugging a file with a space in its name ([#134](https://github.com/mark-wiemer/vscode-autohotkey-plus-plus/issues/134)) - Fix formatting for some bad labels (two colons) ([PR #325](https://github.com/mark-wiemer/vscode-autohotkey-plus-plus/pull/325)) diff --git a/demos/demo_for_ahk_v1.ahk b/demos/demo_for_ahk_v1.ahk deleted file mode 100644 index 4c02d737..00000000 --- a/demos/demo_for_ahk_v1.ahk +++ /dev/null @@ -1,106 +0,0 @@ -globalVar := "Global" -global SuperGlobalVar := "SuperGlobal" -function() -return -function() -{ - globalVar := "Local" - SuperGlobalVar := "Local" - bool := true - str := "string" - if (str == "str") { - MsgBox Overwrite primitive variable! - } - str_multiline := " - (LTrim - line 1 - line 2 - line 3 - )" - int := 123 - float := 123.456 - - emptyArray := [] - smallArray := [1, 2, { str: "string" }] - sparseArray := { 1: 1, 3: 3 } - arrayLike := { 1: 1, 2: 2, 3: 3, length: 3 } - bigArray := [] - Loop 150 { - bigArray.push(A_Index) - } - if (bigArray == "str") { - MsgBox Overwrite object variable! - } - - obj := { str: str, int: int, float: float } - objobj := { str: str, obj: obj } - objobjobj := { str: str, int: int, obj: { str: str, obj: obj } } - - circular := {} - circular.circular := circular - instance := new Cls() - - enum := obj._NewEnum() -} -class Cls -{ - instanceVar := "instance" - static str := "string" - static num := 123 - static obj := { str: "string", int: 123, float: 123.456 } - property[] { - get { - } - } - method() { - } -} - -; Block comments and nested regions -/* ;region -Collapse me! -{ - Collapse me too! -} -*/ ;endregion - -; Hotkeys and Keywords - -<#Tab:: AltTab - -; FUNCTIONS - -; Method header comment accessible to IntelliSense -LAlt() { - ; do a thing -} - -; Function calls (with a space before parens) -foo() -bar () -baz () - -; Functions with keyword names -LAlt() -Pause() -AppsKey() -CapsLock() - -; SUBROUTINES - -; ExitApp indentation for subroutines -MySub: - foo() -ExitApp - -; RUN SELECTION - -; Select the following line and hit `Ctrl + F8` to run selection -f1:: MsgBox, You hit F1 - -; The F2 hotkey will not work because it was not part of the selection -f2:: MsgBox, You hit F2 - -; Formatting line below ternary with third operand a string value -true ? 1 : "string" -foo() diff --git a/demos/manualTests/completionProvider.ahk b/demos/manualTests/completionProvider.ahk new file mode 100644 index 00000000..b9141a95 --- /dev/null +++ b/demos/manualTests/completionProvider.ahk @@ -0,0 +1,27 @@ +#Requires AutoHotkey 1.1.33+ +#SingleInstance force + +; Completion provider manual tests +; Use Ctrl+Space to trigger suggestions + +; compTest comment +compTest(p1, p2) { + p3 := 1 + ; Suggestions within method include locals + p +} + +; Suggestions outside of method exclude locals +p + +; Default settings (enabled IntelliSense, enabled parsing) shows suggestions +c + +; Disabled IntelliSense with enabled parsing shows no suggestions +c + +; Enabled IntelliSense with disabled parsing shows no suggestions +c + +; Disabled IntelliSense with disabled parsing shows no suggestions +c diff --git a/demos/manualTests/debugger.ahk b/demos/manualTests/debugger.ahk new file mode 100644 index 00000000..66579189 --- /dev/null +++ b/demos/manualTests/debugger.ahk @@ -0,0 +1,12 @@ +#Requires AutoHotkey 1.1.33+ +#SingleInstance force + +; Basic debugger test +; Ensure you've run `npm i` before running this test +; 1. Stop on breakpoint +; 2. Step forward works -- Global variables updates +; 3. No errors in Debug Console + +x := 1 +y := 2 ; Breakpoint here +z := 3 diff --git a/demos/manualTests/folding.ahk b/demos/manualTests/folding.ahk new file mode 100644 index 00000000..6f885e00 --- /dev/null +++ b/demos/manualTests/folding.ahk @@ -0,0 +1,7 @@ +/* + I'm collapsible! +*/ + +/* +So am I +*/ diff --git a/demos/manualTests/hover.ahk b/demos/manualTests/hover.ahk new file mode 100644 index 00000000..11ad3b5e --- /dev/null +++ b/demos/manualTests/hover.ahk @@ -0,0 +1,5 @@ +; I show up in IntelliSense when hovering only when parsing is enabled +hoverOverMePlease() +{ + +} diff --git a/demos/manualTests/runSelection.ahk b/demos/manualTests/runSelection.ahk new file mode 100644 index 00000000..3ac6213e --- /dev/null +++ b/demos/manualTests/runSelection.ahk @@ -0,0 +1,10 @@ +#Requires AutoHotkey 1.1.33+ +#SingleInstance force + +; Select the following line and hit `Ctrl + F8` to run selection +f1:: MsgBox, You hit F1 + +; Now press F1 and you should see a message box. + +; The F2 hotkey will not work because it was not part of the selection +f2:: MsgBox, You hit F2 diff --git a/demos/v1Demo.ahk b/demos/v1Demo.ahk new file mode 100644 index 00000000..922393a4 --- /dev/null +++ b/demos/v1Demo.ahk @@ -0,0 +1,220 @@ +globalVar := "Global" +global SuperGlobalVar := "SuperGlobal" +function() +return +function() +{ + globalVar := "Local" + SuperGlobalVar := "Local" + bool := true + str := "string" + if (str == "str") { + MsgBox Overwrite primitive variable! + } + str_multiline := " + (LTrim + line 1 + line 2 + line 3 + )" + int := 123 + float := 123.456 + + emptyArray := [] + smallArray := [1, 2, { str: "string" }] + sparseArray := { 1: 1, 3: 3 } + arrayLike := { 1: 1, 2: 2, 3: 3, length: 3 } + bigArray := [] + Loop 150 { + bigArray.push(A_Index) + } + if (bigArray == "str") { + MsgBox Overwrite object variable! + } + + obj := { str: str, int: int, float: float } + objobj := { str: str, obj: obj } + objobjobj := { str: str, int: int, obj: { str: str, obj: obj } } + + circular := {} + circular.circular := circular + instance := new Cls() + + enum := obj._NewEnum() +} +class Cls +{ + instanceVar := "instance" + static str := "string" + static num := 123 + static obj := { str: "string", int: 123, float: 123.456 } + property[] { + get { + } + } + method() { + } +} + +; Block comments and nested regions +/* ;region +Collapse me! +{ + Collapse me too! +} +*/ ;endregion + +; Hotkeys and Keywords + +<#Tab:: AltTab + +; FUNCTIONS + +; Function calls (with a space before parens) +function() +function () + +; Functions with keyword names +LAlt() { +} +Pause() { +} +AppsKey() { +} +CapsLock() { +} + +; Method header comments appear on hover +hoverToSeeComment() { +} + + +; SUBROUTINES + +; ExitApp indentation for subroutines +MySub: + function() +ExitApp + +; RUN SELECTION + +; Select the following line and hit `Ctrl + F8` to run selection +f1:: MsgBox, You hit F1 + +; The F2 hotkey will not work because it was not part of the selection +f2:: MsgBox, You hit F2 + +; Formatting line below ternary with third operand a string value +true ? 1 : "string" +function() + +;;;;;;;;;; +; v3.2.0 ; +;;;;;;;;;; + +;;; +; Move lines of code down with correct indentation +;;; + +var +foo() { + if expression + code + code + if (expression) + code + if (expression) { + code + } + +} + +;;; +; Check out these correctly-colored lines of code! +;;; + +; https://github.com/vscode-autohotkey/ahkpp/pull/278 +Hotstring("::ykhis", "you know how it is") +Hotstring(":C:OOS", "out-of::-spec") + +; https://github.com/vscode-autohotkey/ahkpp/issues/69 +#If WinActive("ahk_class Notepad") or WinActive(MyWindowTitle) +#If + +; https://github.com/vscode-autohotkey/ahkpp/issues/86 +#Include Chrome.ahk ; and a nice green comment +#IncludeAgain Chrome.ahk ; and another green comment + +; https://github.com/vscode-autohotkey/ahkpp/issues/295 +foo(); this is not actually a comment, it doesn't have a preceding space + +;;; +; And these well-formatted snippets: +;;; + +; https://github.com/vscode-autohotkey/ahkpp/issues/291 +if (expression) + ; + code + +; https://github.com/vscode-autohotkey/ahkpp/issues/290 +MsgBox, 4, , Would you like to continue?, 5 ; 5-second timeout. +IfMsgBox, No + Return ; User pressed the "No" button. +IfMsgBox, Timeout + Return ; i.e. Assume "No" if it timed out. +; Otherwise, continue: +; ... + +F1 & F2 Up:: + code +return + +; https://github.com/vscode-autohotkey/ahkpp/issues/303 +F1 & F2 Up:: + code +return + +::btw:: + code +return + +; https://github.com/vscode-autohotkey/ahkpp/issues/316 +if (expression) + obj := { 0:"" + , a: 1 + , b: 2 } +else + obj := { 0:"" + , a: 2 + , b: 1 } +code + +; https://github.com/vscode-autohotkey/ahkpp/pull/287 +{ + foo() { + if + if + if + return + } + + foo() { + for + for + if + return + } +} + +;;; +; Hover messages are now trimmed +;;; + +; <-- those spaces will be gone! +hoverOverMe() { + +} + +#HotIf +#HotkeyInterval diff --git a/demos/demo_for_ahk_v2.ahk b/demos/v2Demo.ahk similarity index 100% rename from demos/demo_for_ahk_v2.ahk rename to demos/v2Demo.ahk diff --git a/package-lock.json b/package-lock.json index 518d9aea..5d126ce7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "xml2js": "^0.6.2" }, "devDependencies": { + "@types/chai": "^4.3.5", "@types/fs-extra": "^9.0.7", "@types/glob": "^7.1.3", "@types/mocha": "^9.1.1", @@ -25,6 +26,7 @@ "@typescript-eslint/parser": "^5.37.0", "@vscode/test-electron": "^1.6.1", "@vscode/vsce": "^2.19.0", + "chai": "^4.3.7", "esbuild": "^0.15.7", "esbuild-plugin-eslint": "^0.1.1", "eslint": "^8.23.1", @@ -177,6 +179,12 @@ "node": ">= 6" } }, + "node_modules/@types/chai": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", + "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==", + "dev": true + }, "node_modules/@types/fs-extra": { "version": "9.0.13", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", @@ -762,6 +770,15 @@ "node": ">=8" } }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -1015,6 +1032,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/chainsaw": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", @@ -1055,6 +1090,15 @@ "node": ">=8" } }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/cheerio": { "version": "1.0.0-rc.12", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", @@ -1357,6 +1401,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -2376,6 +2432,15 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", @@ -3119,6 +3184,15 @@ "node": ">=8" } }, + "node_modules/loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -3658,6 +3732,15 @@ "node": ">=8" } }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -4479,6 +4562,15 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", @@ -4943,6 +5035,12 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, + "@types/chai": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", + "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==", + "dev": true + }, "@types/fs-extra": { "version": "9.0.13", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", @@ -5357,6 +5455,12 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -5542,6 +5646,21 @@ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true }, + "chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, "chainsaw": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", @@ -5572,6 +5691,12 @@ } } }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true + }, "cheerio": { "version": "1.0.0-rc.12", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", @@ -5788,6 +5913,15 @@ "mimic-response": "^3.1.0" } }, + "deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -6461,6 +6595,12 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true + }, "get-intrinsic": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", @@ -6992,6 +7132,15 @@ } } }, + "loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "requires": { + "get-func-name": "^2.0.0" + } + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -7400,6 +7549,12 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -7990,6 +8145,12 @@ "prelude-ls": "^1.2.1" } }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", diff --git a/package.json b/package.json index e0d4e0fc..500cea3d 100644 --- a/package.json +++ b/package.json @@ -126,11 +126,6 @@ "default": "C:/Program Files/AutoHotkey/AutoHotkey.chm", "description": "Path to the AHK Help document." }, - "ahk++.file.maximumParseLength": { - "type": "number", - "default": 10000, - "markdownDescription": "Number of lines to parse for each AHK file. Larger numbers may impact performance. Parsing is used by IntelliSense only, not AHK itself. Changing this value will not affect the behavior of AHK scripts.\n- -1: Unlimited parsing.\n- 0: No parsing.\n\nRequires restart to take effect." - }, "ahk++.file.templateSnippetName": { "type": "string", "default": "AhkTemplate", @@ -147,10 +142,10 @@ "default": true, "description": "Indent code after label." }, - "ahk++.formatter.indentCodeAfterSharpDirective": { + "ahk++.formatter.indentCodeAfterIfDirective": { "type": "boolean", "default": true, - "description": "Indent code after directives, that creates context-sensitive hotkeys and hotstrings.\nExample: #If / #IfWinActive / #IfWinNotActive / #IfWinExist / #IfWinNotExist." + "description": "Indent code after a directive that creates context-sensitive hotkeys or hotstrings (#If, #IfWinActive, #IfWinNotActive, #IfWinExist, #IfWinNotExist)." }, "ahk++.formatter.preserveIndent": { "type": "boolean", @@ -162,10 +157,15 @@ "default": true, "description": "Trim extra spaces between words." }, - "ahk++.language.enableIntellisense": { + "ahk++.intellisense.enableIntellisense": { "type": "boolean", "default": true, - "description": "Enable IntelliSense (Preview). Changes take effect after reload." + "description": "Add suggestions for relevant variables and methods in the workspace. Requires parsing. Changes take effect after reload." + }, + "ahk++.intellisense.maximumParseLength": { + "type": "number", + "default": 10000, + "markdownDescription": "Number of lines to parse for each AHK file. Larger numbers may impact performance. Parsing is used by IntelliSense only, not AHK itself. Changing this value will not affect the behavior of AHK scripts.\n- -1: Unlimited parsing.\n- 0: No parsing.\n\nChanges take effect after reload." }, "ahk++.menu.showDebugButton": { "type": "boolean", @@ -360,6 +360,7 @@ "xml2js": "^0.6.2" }, "devDependencies": { + "@types/chai": "^4.3.5", "@types/fs-extra": "^9.0.7", "@types/glob": "^7.1.3", "@types/mocha": "^9.1.1", @@ -370,6 +371,7 @@ "@typescript-eslint/parser": "^5.37.0", "@vscode/test-electron": "^1.6.1", "@vscode/vsce": "^2.19.0", + "chai": "^4.3.7", "esbuild": "^0.15.7", "esbuild-plugin-eslint": "^0.1.1", "eslint": "^8.23.1", diff --git a/src/common/global.ts b/src/common/global.ts index 2c72c5f3..91db3340 100644 --- a/src/common/global.ts +++ b/src/common/global.ts @@ -30,12 +30,12 @@ export enum ConfigKey { compileIcon = 'compile.compileIcon', useMpress = 'compile.useMpress', compilePath = 'file.compilePath', - enableIntellisense = 'language.enableIntellisense', + enableIntellisense = 'intellisense.enableIntellisense', executePath = 'file.executePath', helpPath = 'file.helpPath', + indentCodeAfterIfDirective = 'formatter.indentCodeAfterIfDirective', indentCodeAfterLabel = 'formatter.indentCodeAfterLabel', - indentCodeAfterSharpDirective = 'formatter.indentCodeAfterSharpDirective', - maximumParseLength = 'file.maximumParseLength', + maximumParseLength = 'intellisense.maximumParseLength', preserveIndent = 'formatter.preserveIndent', templateSnippetName = 'file.templateSnippetName', trimExtraSpaces = 'formatter.trimExtraSpaces', diff --git a/src/debugger/debugDispatcher.ts b/src/debugger/debugDispatcher.ts index d4d600aa..3ab87624 100644 --- a/src/debugger/debugDispatcher.ts +++ b/src/debugger/debugDispatcher.ts @@ -33,7 +33,8 @@ export class DebugDispatcher extends EventEmitter { /** Start executing the given program. */ public async start(args: LaunchRequestArguments) { - let { runtime, dbgpSettings = {} } = args; + const runtime = args.runtime ?? Global.getConfig(ConfigKey.executePath); + const dbgpSettings = args.dbgpSettings ?? {}; // names may used by AHK, let's not change them for now const { maxChildren, maxData }: LaunchRequestArguments['dbgpSettings'] = { @@ -41,9 +42,6 @@ export class DebugDispatcher extends EventEmitter { maxData: 131072, ...dbgpSettings, }; - if (!runtime) { - runtime = Global.getConfig(ConfigKey.executePath); - } this.breakPointHandler = new BreakPointHandler(); this.stackHandler = new StackHandler(); diff --git a/src/extension.ts b/src/extension.ts index 66b0b4c6..5e81a1b6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -80,7 +80,6 @@ export function activate(context: vscode.ExtensionContext) { vscode.languages.registerCompletionItemProvider( language, new CompletionProvider(), - ' ', '.', ), ); diff --git a/src/parser/model.ts b/src/parser/model.ts index 4b8bed4e..6be20998 100644 --- a/src/parser/model.ts +++ b/src/parser/model.ts @@ -1,5 +1,6 @@ import * as vscode from 'vscode'; +/** Symbols and structures parsed from a file */ export interface Script { methods: Method[]; refs: Ref[]; @@ -50,12 +51,14 @@ export class Method { public full: string; public endLine: number; constructor( + // TODO very similar to `this.full`, maybe merge them? public origin: string, public name: string, - public document: vscode.TextDocument, + public uriString: string, public line: number, public character: number, public withQuote: boolean, + /** Method header comment */ public comment: string, ) { this.buildParams(); @@ -63,20 +66,22 @@ export class Method { } private buildParams() { - const refPattern = /\s*\((.+?)\)\s*$/; + /** Captures the parameters in a method header */ + const paramRegex = /\s*\((.+?)\)\s*$/; if (this.origin !== this.name) { - const paramsMatch = this.origin.match(refPattern); + const paramsMatch = this.origin.match(paramRegex); if (paramsMatch) { this.params = paramsMatch[1] .split(',') .filter((param) => param.trim()) .map((param) => { - const paramMatch = param.match(/[^:=* \t]+/); + const alphanumericRegex = /[^:=* \t]+/; + const paramMatch = param.match(alphanumericRegex); return paramMatch?.[0] ?? param; }); this.full = this.origin.replace( paramsMatch[1], - this.params.join(','), + this.params.join(', '), ); } else { this.params = []; diff --git a/src/parser/parser.ts b/src/parser/parser.ts index add73029..b9273022 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -45,7 +45,7 @@ export class Parser { } /** - * detect method list by document + * Parse the document into a Script and add it to the cache * @param document */ public static async buildScript( @@ -324,7 +324,7 @@ export class Parser { return new Method( methodFullName, methodName, - document, + document.uri.toString(), line, character, true, @@ -340,7 +340,7 @@ export class Parser { return new Method( methodFullName, methodName, - document, + document.uri.toString(), line, character, false, diff --git a/src/providers/completionProvider.ts b/src/providers/completionProvider.ts index 8c86b5ca..65396b58 100644 --- a/src/providers/completionProvider.ts +++ b/src/providers/completionProvider.ts @@ -1,95 +1,127 @@ import * as vscode from 'vscode'; import { Parser } from '../parser/parser'; import { SnippetString } from 'vscode'; +import { Method, Variable } from '../parser/model'; -export class CompletionProvider implements vscode.CompletionItemProvider { - private keywordList: string[] = []; - private keywordCompletionItems: vscode.CompletionItem[] = []; - constructor() { - this.initKeywordCompletionItem(); +type SimpleMethod = Pick< + Method, + 'params' | 'name' | 'full' | 'comment' | 'uriString' | 'line' | 'endLine' +> & { variables: string[] }; + +/** A completion item for the method itself. */ +const completionItemForMethod = ( + method: Pick< + SimpleMethod, + 'params' | 'name' | 'full' | 'comment' | 'variables' + >, +): vscode.CompletionItem => { + // foo() -> foo, foo(bar) -> foo(bar) + const completionItem = new vscode.CompletionItem( + method.params.length === 0 ? method.name : method.full, + vscode.CompletionItemKind.Method, + ); + completionItem.insertText = method.params.length + ? new SnippetString(`${method.name}($1)`) + : `${method.name}()`; + completionItem.detail = method.comment; + return completionItem; +}; + +/** + * True if the line number is within the method + * and the method is in the same file. + */ +const shouldSuggestMethodLocals = ( + method: Pick, + uriString: string, + lineNumber: number, +): boolean => + method.uriString === uriString && + method.line <= lineNumber && + lineNumber <= method.endLine; + +/** A completion item for each of the method's local variables and parameters. */ +const completionItemsForMethodLocals = ( + method: Pick, +): vscode.CompletionItem[] => + method.params + .concat(method.variables) + .map( + (local) => + new vscode.CompletionItem( + local, + vscode.CompletionItemKind.Variable, + ), + ); + +/** + * A completion item for the method itself. + * Also one for each of its locals if the line number is within the method. + */ +const completionItemsForMethod = ( + method: SimpleMethod, + uriString: string, + lineNumber: number, +): vscode.CompletionItem[] => { + const result: vscode.CompletionItem[] = [completionItemForMethod(method)]; + + if (shouldSuggestMethodLocals(method, uriString, lineNumber)) { + result.push(...completionItemsForMethodLocals(method)); } + return result; +}; + +const completionItemForVariable = (variable: string): vscode.CompletionItem => + new vscode.CompletionItem(variable, vscode.CompletionItemKind.Variable); + +/** + * Suggests all methods and the locals of the current method, if any. + * Suggests all variables provided. + * @param methods The methods to suggest + * @param uriString The URI of the current file + * @param lineNumber The line number of the cursor + * @param variables The variables to suggest + * @returns The completion items + */ +export const provideCompletionItemsInner = ( + methods: SimpleMethod[], + uriString: string, + lineNumber: number, + variables: string[], +): vscode.CompletionItem[] => + methods + .map((m) => completionItemsForMethod(m, uriString, lineNumber)) + .reduce((a, b) => a.concat(b), []) + .concat(variables.map(completionItemForVariable)); + +export class CompletionProvider implements vscode.CompletionItemProvider { + // TODO add tests public async provideCompletionItems( document: vscode.TextDocument, position: vscode.Position, - ): Promise { - const prePosition = - position.character === 0 - ? position - : new vscode.Position(position.line, position.character - 1); - const preChart = - position.character === 0 - ? null - : document.getText(new vscode.Range(prePosition, position)); - if (preChart === '.') { + ): Promise { + // If the cursor is just after a dot, don't suggest anything. + // Default suggestions will still apply. + const preChar = document.getText( + new vscode.Range(position.translate(0, -1), position), + ); + if (preChar === '.') { return []; } - const result: vscode.CompletionItem[] = []; - - (await Parser.getAllMethod()).forEach((method) => { - const completionItem = new vscode.CompletionItem( - method.params.length === 0 ? method.name : method.full, - vscode.CompletionItemKind.Method, - ); - if (method.params.length === 0) { - completionItem.insertText = method.name + '()'; - } else { - completionItem.insertText = new SnippetString( - method.name + '($1)', - ); - } - completionItem.detail = method.comment; - result.push(completionItem); - if ( - method.document === document && - position.line >= method.line && - position.line <= method.endLine - ) { - for (const param of method.params) { - result.push( - new vscode.CompletionItem( - param, - vscode.CompletionItemKind.Variable, - ), - ); - } - for (const variable of method.variables) { - result.push( - new vscode.CompletionItem( - variable.name, - vscode.CompletionItemKind.Variable, - ), - ); - } - } - }); - + // Suggest all methods and the locals of the current method, if any + // Suggest all variables in the current file + const methods = await Parser.getAllMethod(); const script = await Parser.buildScript(document, { usingCache: true }); - script.variables.forEach((variable) => { - const completionItem = new vscode.CompletionItem( - variable.name, - vscode.CompletionItemKind.Variable, - ); - result.push(completionItem); - }); - - return this.keywordCompletionItems.concat(result); - } - - public resolveCompletionItem?( - item: vscode.CompletionItem, - ): vscode.ProviderResult { - return item; - } - - private initKeywordCompletionItem() { - this.keywordList.forEach((keyword) => { - const keywordCompletionItem = new vscode.CompletionItem( - keyword + ' ', - ); - keywordCompletionItem.kind = vscode.CompletionItemKind.Property; - this.keywordCompletionItems.push(keywordCompletionItem); - }); + return provideCompletionItemsInner( + methods.map((m) => ({ + ...m, + variables: m.variables.map((v) => v.name), + })), + document.uri.toString(), + position.line, + script.variables.map((v) => v.name), + ); } } diff --git a/src/providers/defProvider.ts b/src/providers/defProvider.ts index 2f11985a..66ff6c9c 100644 --- a/src/providers/defProvider.ts +++ b/src/providers/defProvider.ts @@ -25,9 +25,8 @@ export class DefProvider implements vscode.DefinitionProvider { ) { const method = await Parser.getMethodByName(document, word); if (method) { - const methodDoc = method.document; return new vscode.Location( - methodDoc.uri, + vscode.Uri.parse(method.uriString), new vscode.Position(method.line, method.character), ); } diff --git a/src/providers/formattingProvider.ts b/src/providers/formattingProvider.ts index 747275d1..31ca8927 100644 --- a/src/providers/formattingProvider.ts +++ b/src/providers/formattingProvider.ts @@ -261,7 +261,7 @@ export const internalFormat = ( // SETTINGS' ALIASES const indentCodeAfterLabel = options.indentCodeAfterLabel; - const indentCodeAfterSharpDirective = options.indentCodeAfterSharpDirective; + const indentCodeAfterIfDirective = options.indentCodeAfterIfDirective; const trimSpaces = options.trimExtraSpaces; // REGULAR EXPRESSION @@ -841,7 +841,7 @@ export const internalFormat = ( // F1:: MsgBox Help if ( purifiedLine.match('^' + sharpDirective + '\\b.+') && - indentCodeAfterSharpDirective + indentCodeAfterIfDirective ) { depth++; } @@ -946,7 +946,7 @@ export class FormatProvider implements vscode.DocumentFormattingEditProvider { public provideDocumentFormattingEdits( document: vscode.TextDocument, options: vscode.FormattingOptions, - token: vscode.CancellationToken, + _: vscode.CancellationToken, ): vscode.TextEdit[] { const stringToFormat = documentToString(document); @@ -958,8 +958,8 @@ export class FormatProvider implements vscode.DocumentFormattingEditProvider { ConfigKey.indentCodeAfterLabel, ); - const indentCodeAfterSharpDirective = Global.getConfig( - ConfigKey.indentCodeAfterSharpDirective, + const indentCodeAfterIfDirective = Global.getConfig( + ConfigKey.indentCodeAfterIfDirective, ); const preserveIndent = Global.getConfig( @@ -974,7 +974,7 @@ export class FormatProvider implements vscode.DocumentFormattingEditProvider { ...options, allowedNumberOfEmptyLines, indentCodeAfterLabel, - indentCodeAfterSharpDirective, + indentCodeAfterIfDirective, preserveIndent, trimExtraSpaces, }); diff --git a/src/providers/formattingProvider.types.ts b/src/providers/formattingProvider.types.ts index 783cd60e..6756710c 100644 --- a/src/providers/formattingProvider.types.ts +++ b/src/providers/formattingProvider.types.ts @@ -5,7 +5,7 @@ export type FormatOptions = Pick< 'tabSize' | 'insertSpaces' > & { indentCodeAfterLabel: boolean; - indentCodeAfterSharpDirective: boolean; + indentCodeAfterIfDirective: boolean; preserveIndent: boolean; trimExtraSpaces: boolean; allowedNumberOfEmptyLines: number; diff --git a/src/providers/formattingProvider.utils.ts b/src/providers/formattingProvider.utils.ts index edc42fdb..4d542bde 100644 --- a/src/providers/formattingProvider.utils.ts +++ b/src/providers/formattingProvider.utils.ts @@ -303,7 +303,7 @@ export function purify(original: string): string { // OUT: ControlSend(params) for (const command of commandList) { /** String with regular expression pattern */ - let pattern = + const pattern = '(' + // begin 1st capture group '^\\s*' + // \b will do this: foo(Gui) { => foo(Gui command + @@ -311,7 +311,7 @@ export function purify(original: string): string { '(?!\\()' + // after command must not be open brace '(', otherwise it's function ')' + // end 1st capture group '.*'; // this will be removed from string - let regExp = new RegExp(pattern, 'i'); + const regExp = new RegExp(pattern, 'i'); if (original.search(regExp) !== -1) { cmdTrim = original.replace(regExp, '$1'); break; @@ -423,8 +423,8 @@ export type BraceChar = '{' | '}'; * @return Number of not matched braces */ export function braceNumber(line: string, braceChar: BraceChar): number { - let braceRegEx = new RegExp(braceChar, 'g'); - let braceNum = + const braceRegEx = new RegExp(braceChar, 'g'); + const braceNum = replaceAll(line, /{[^{}]*}/g, '').match(braceRegEx)?.length ?? 0; return braceNum; } @@ -486,7 +486,7 @@ export function alignLineAssignOperator( /** The line comment. Empty string if no line comment exists */ const comment = /;.+/.exec(original)?.[0] ?? ''; // Save comment original = normalizeLineAssignOperator(original); - let position = original.indexOf('='); // = operator position + const position = original.indexOf('='); // = operator position return original .replace(/\s(?=:?=)/, ' '.repeat(targetPosition - position + 1)) // Align assignment .concat(comment) // Restore comment @@ -514,7 +514,7 @@ export function replaceAll( replace: string, ): string { while (true) { - let len = text.length; + const len = text.length; text = text.replace(search, replace); if (len === text.length) { // Nothing was replaced, break loop. @@ -584,7 +584,7 @@ export class FlowOfControlNestDepth { } pop() { - let result = this.depth.pop(); + const result = this.depth.pop(); this.restoreEmptyDepth(); return result; } @@ -604,8 +604,8 @@ export class FlowOfControlNestDepth { */ restoreDepth() { /** Index of element right after last `-1` element. */ - let index = this.depth.lastIndexOf(-1) + 1; - let element = this.depth[index]; + const index = this.depth.lastIndexOf(-1) + 1; + const element = this.depth[index]; this.depth.splice(index); return element; } diff --git a/src/providers/signatureProvider.ts b/src/providers/signatureProvider.ts index cd49083a..abd691a5 100644 --- a/src/providers/signatureProvider.ts +++ b/src/providers/signatureProvider.ts @@ -5,8 +5,8 @@ export class SignatureProvider implements vscode.SignatureHelpProvider { public async provideSignatureHelp( document: vscode.TextDocument, position: vscode.Position, - token: vscode.CancellationToken, - context: vscode.SignatureHelpContext, + _: vscode.CancellationToken, + __: vscode.SignatureHelpContext, ): Promise { let methodPosition: vscode.Position; const lineText = document.lineAt(position.line).text; diff --git a/src/providers/symbolProvider.ts b/src/providers/symbolProvider.ts index 65b0531e..3729a11d 100644 --- a/src/providers/symbolProvider.ts +++ b/src/providers/symbolProvider.ts @@ -17,7 +17,7 @@ export class SymbolProvider implements vscode.DocumentSymbolProvider { vscode.SymbolKind.Method, method.comment, new vscode.Location( - method.document.uri, + vscode.Uri.parse(method.uriString), new vscode.Position(method.line, method.character), ), ), diff --git a/src/test/suite/format/format.test.ts b/src/test/suite/format/format.test.ts index 8dc92e7b..670c7dc1 100644 --- a/src/test/suite/format/format.test.ts +++ b/src/test/suite/format/format.test.ts @@ -1,3 +1,4 @@ +// TODO move to providers/formatting/formattingProvider.test.ts import { getDocument } from '../../utils'; import * as assert from 'assert'; import * as fs from 'fs-extra'; @@ -23,7 +24,7 @@ const defaultOptions = { insertSpaces: true, allowedNumberOfEmptyLines: 1, indentCodeAfterLabel: true, - indentCodeAfterSharpDirective: true, + indentCodeAfterIfDirective: true, preserveIndent: false, trimExtraSpaces: true, }; @@ -31,7 +32,7 @@ const formatTests: FormatTest[] = [ { filenameRoot: '25-multiline-string' }, { filenameRoot: '28-switch-case' }, { filenameRoot: '40-command-inside-text' }, - { filenameRoot: '55-sharp-directive' }, + { filenameRoot: '55-if-directive' }, { filenameRoot: '56-return-command-after-label' }, { filenameRoot: '58-parentheses-indentation' }, { filenameRoot: '59-one-command-indentation' }, @@ -73,26 +74,26 @@ const formatTests: FormatTest[] = [ { filenameRoot: 'align-assignment' }, { filenameRoot: 'demo' }, { - filenameRoot: 'indent-code-after-label-false', - options: { indentCodeAfterLabel: false }, + filenameRoot: 'indent-code-after-if-directive-false', + options: { indentCodeAfterIfDirective: false }, }, { - filenameRoot: 'indent-code-after-label-true', - options: { indentCodeAfterLabel: true }, + filenameRoot: 'indent-code-after-if-directive-true', + options: { indentCodeAfterIfDirective: true }, }, { - filenameRoot: 'indent-code-after-sharp-directive-false', - options: { indentCodeAfterSharpDirective: false }, + filenameRoot: 'indent-code-after-label-false', + options: { indentCodeAfterLabel: false }, }, { - filenameRoot: 'indent-code-after-sharp-directive-true', - options: { indentCodeAfterSharpDirective: true }, + filenameRoot: 'indent-code-after-label-true', + options: { indentCodeAfterLabel: true }, }, { filenameRoot: 'insert-spaces-false', options: { insertSpaces: false }, }, - { filenameRoot: 'legacy-text-sharp-directive' }, + { filenameRoot: 'legacy-text-if-directive' }, { filenameRoot: 'label-colon' }, { filenameRoot: 'label-combination' }, { filenameRoot: 'label-fall-through' }, diff --git a/src/test/suite/format/samples/55-sharp-directive.in.ahk b/src/test/suite/format/samples/55-if-directive.in.ahk similarity index 100% rename from src/test/suite/format/samples/55-sharp-directive.in.ahk rename to src/test/suite/format/samples/55-if-directive.in.ahk diff --git a/src/test/suite/format/samples/55-sharp-directive.out.ahk b/src/test/suite/format/samples/55-if-directive.out.ahk similarity index 100% rename from src/test/suite/format/samples/55-sharp-directive.out.ahk rename to src/test/suite/format/samples/55-if-directive.out.ahk diff --git a/src/test/suite/format/samples/indent-code-after-sharp-directive-false.in.ahk b/src/test/suite/format/samples/indent-code-after-if-directive-false.in.ahk similarity index 100% rename from src/test/suite/format/samples/indent-code-after-sharp-directive-false.in.ahk rename to src/test/suite/format/samples/indent-code-after-if-directive-false.in.ahk diff --git a/src/test/suite/format/samples/indent-code-after-sharp-directive-false.out.ahk b/src/test/suite/format/samples/indent-code-after-if-directive-false.out.ahk similarity index 100% rename from src/test/suite/format/samples/indent-code-after-sharp-directive-false.out.ahk rename to src/test/suite/format/samples/indent-code-after-if-directive-false.out.ahk diff --git a/src/test/suite/format/samples/indent-code-after-sharp-directive-true.in.ahk b/src/test/suite/format/samples/indent-code-after-if-directive-true.in.ahk similarity index 100% rename from src/test/suite/format/samples/indent-code-after-sharp-directive-true.in.ahk rename to src/test/suite/format/samples/indent-code-after-if-directive-true.in.ahk diff --git a/src/test/suite/format/samples/indent-code-after-sharp-directive-true.out.ahk b/src/test/suite/format/samples/indent-code-after-if-directive-true.out.ahk similarity index 100% rename from src/test/suite/format/samples/indent-code-after-sharp-directive-true.out.ahk rename to src/test/suite/format/samples/indent-code-after-if-directive-true.out.ahk diff --git a/src/test/suite/format/samples/legacy-text-sharp-directive.in.ahk b/src/test/suite/format/samples/legacy-text-if-directive.in.ahk similarity index 100% rename from src/test/suite/format/samples/legacy-text-sharp-directive.in.ahk rename to src/test/suite/format/samples/legacy-text-if-directive.in.ahk diff --git a/src/test/suite/format/samples/legacy-text-sharp-directive.out.ahk b/src/test/suite/format/samples/legacy-text-if-directive.out.ahk similarity index 100% rename from src/test/suite/format/samples/legacy-text-sharp-directive.out.ahk rename to src/test/suite/format/samples/legacy-text-if-directive.out.ahk diff --git a/src/test/suite/parser/parser.test.ts b/src/test/suite/parser/parser.test.ts index b3dd343d..9f4d0be9 100644 --- a/src/test/suite/parser/parser.test.ts +++ b/src/test/suite/parser/parser.test.ts @@ -2,12 +2,12 @@ import { getDocument } from '../../utils'; import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; -import { Parser } from '../../../parser/Parser'; +import { Parser } from '../../../parser/parser'; suite('Parser', () => { suite('detectVariableByLine', () => { // List of test data - let dataList = [ + const dataList = [ // { // in: // input test string // rs: // expected result - number of detected variables inside line with command @@ -40,7 +40,7 @@ suite('Parser', () => { suite('getRemarkByLine', () => { // List of test data - let dataList = [ + const dataList = [ // { // in: // input test string // rs: // expected result diff --git a/src/test/suite/providers/completionProvider/completionProvider.test.ts b/src/test/suite/providers/completionProvider/completionProvider.test.ts new file mode 100644 index 00000000..afa3d853 --- /dev/null +++ b/src/test/suite/providers/completionProvider/completionProvider.test.ts @@ -0,0 +1,358 @@ +import * as vscode from 'vscode'; +import { assert } from 'chai'; +import { provideCompletionItemsInner } from '../../../../providers/completionProvider'; + +// tests for completionItemsForMethod +suite('completionProvider', () => { + // TODO outer + // parsing vs no parsing + // intellisense enabled vs disabled + + suite('provideCompletionItemsInner', () => { + const tests: [ + name: string, + args: Parameters, + expected: ReturnType, + ][] = [ + ['no methods or variables', [[], 'mockUri', 1, []], []], + [ + 'diff file, outside method, no locals', + [ + [ + { + comment: 'mockComment', + endLine: 0, + full: 'mockName()', + line: 0, + name: 'mockName', + params: [], + uriString: 'mockUri1', + variables: [], + }, + ], + 'mockUri2', + 1, + [], + ], + [ + { + detail: 'mockComment', + insertText: 'mockName()', + kind: vscode.CompletionItemKind.Method, + label: 'mockName', + }, + ], + ], + [ + 'diff file, outside method, only params', + [ + [ + { + comment: 'mockComment', + endLine: 0, + full: 'mockName(mockParam1, mockParam2)', + line: 0, + name: 'mockName', + params: ['mockParam1', 'mockParam2'], + uriString: 'mockUri1', + variables: [], + }, + ], + 'mockUri2', + 1, + [], + ], + [ + { + detail: 'mockComment', + insertText: new vscode.SnippetString('mockName($1)'), + kind: vscode.CompletionItemKind.Method, + label: 'mockName(mockParam1, mockParam2)', + }, + ], + ], + [ + 'diff file, inside method, ignore local variables', + [ + [ + { + comment: 'mockComment', + endLine: 2, + full: 'mockName(mockParam1, mockParam2)', + line: 0, + name: 'mockName', + params: ['mockParam1', 'mockParam2'], + uriString: 'mockUri1', + variables: ['mockVariable1'], + }, + ], + 'mockUri2', + 1, + [], + ], + [ + { + detail: 'mockComment', + insertText: new vscode.SnippetString('mockName($1)'), + kind: vscode.CompletionItemKind.Method, + label: 'mockName(mockParam1, mockParam2)', + }, + ], + ], + [ + 'same file, outside method, no locals', + [ + [ + { + comment: 'mockComment', + endLine: 0, + full: 'mockName()', + line: 0, + name: 'mockName', + params: [], + uriString: 'mockUri1', + variables: [], + }, + ], + 'mockUri1', + 1, + [], + ], + [ + { + detail: 'mockComment', + insertText: 'mockName()', + kind: vscode.CompletionItemKind.Method, + label: 'mockName', + }, + ], + ], + [ + 'same file, outside method, ignore local variables', + [ + [ + { + comment: 'mockComment', + endLine: 0, + full: 'mockName(mockParam1, mockParam2)', + line: 0, + name: 'mockName', + params: ['mockParam1', 'mockParam2'], + uriString: 'mockUri1', + variables: ['mockVariable1'], + }, + ], + 'mockUri1', + 1, + [], + ], + [ + { + detail: 'mockComment', + insertText: new vscode.SnippetString('mockName($1)'), + kind: vscode.CompletionItemKind.Method, + label: 'mockName(mockParam1, mockParam2)', + }, + ], + ], + [ + 'same file, inside method, include locals (params first)', + [ + [ + { + comment: 'mockComment', + endLine: 2, + full: 'mockName(mockParam1, mockParam2)', + line: 0, + name: 'mockName', + params: ['mockParam1', 'mockParam2'], + uriString: 'mockUri1', + variables: ['mockVariable1'], + }, + ], + 'mockUri1', + 1, + [], + ], + [ + { + detail: 'mockComment', + insertText: new vscode.SnippetString('mockName($1)'), + kind: vscode.CompletionItemKind.Method, + label: 'mockName(mockParam1, mockParam2)', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockParam1', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockParam2', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockVariable1', + }, + ], + ], + [ + 'same file, inside one of two methods, include locals of only the current method', + [ + [ + { + comment: 'mockComment1', + endLine: 2, + full: 'mockName1(mockParam1_1, mockParam1_2)', + line: 0, + name: 'mockName1', + params: ['mockParam1_1', 'mockParam1_2'], + uriString: 'mockUri', + variables: ['mockVariable1_1'], + }, + { + comment: 'mockComment2', + endLine: 4, + full: 'mockName2(mockParam2_1, mockParam2_2)', + line: 3, + name: 'mockName2', + params: ['mockParam2_1', 'mockParam2_2'], + uriString: 'mockUri', + variables: ['mockVariable2_1'], + }, + ], + 'mockUri', + 1, + [], + ], + [ + { + detail: 'mockComment1', + insertText: new vscode.SnippetString('mockName1($1)'), + kind: vscode.CompletionItemKind.Method, + label: 'mockName1(mockParam1_1, mockParam1_2)', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockParam1_1', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockParam1_2', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockVariable1_1', + }, + { + detail: 'mockComment2', + insertText: new vscode.SnippetString('mockName2($1)'), + kind: vscode.CompletionItemKind.Method, + label: 'mockName2(mockParam2_1, mockParam2_2)', + }, + ], + ], + [ + 'just variables', + [[], 'mockUri', 1, ['mockVariable1', 'mockVariable2']], + [ + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockVariable1', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockVariable2', + }, + ], + ], + [ + 'the big one', + [ + [ + { + comment: 'mockComment1', + endLine: 2, + full: 'mockName1(mockParam1_1, mockParam1_2)', + line: 0, + name: 'mockName1', + params: ['mockParam1_1', 'mockParam1_2'], + uriString: 'mockUri1', + variables: ['mockVariable1_1'], + }, + { + comment: 'mockComment2', + endLine: 4, + full: 'mockName2(mockParam2_1, mockParam2_2)', + line: 3, + name: 'mockName2', + params: ['mockParam2_1', 'mockParam2_2'], + uriString: 'mockUri1', + variables: ['mockVariable2_1'], + }, + { + comment: 'mockComment3', + endLine: 2, + full: 'mockName3(mockParam3_1, mockParam3_2)', + line: 0, + name: 'mockName3', + params: ['mockParam3_1', 'mockParam3_2'], + uriString: 'mockUri2', + variables: ['mockVariable3_1'], + }, + ], + 'mockUri1', + 1, + ['mockVariable1', 'mockVariable2'], + ], + [ + { + detail: 'mockComment1', + insertText: new vscode.SnippetString('mockName1($1)'), + kind: vscode.CompletionItemKind.Method, + label: 'mockName1(mockParam1_1, mockParam1_2)', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockParam1_1', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockParam1_2', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockVariable1_1', + }, + { + detail: 'mockComment2', + insertText: new vscode.SnippetString('mockName2($1)'), + kind: vscode.CompletionItemKind.Method, + label: 'mockName2(mockParam2_1, mockParam2_2)', + }, + { + detail: 'mockComment3', + insertText: new vscode.SnippetString('mockName3($1)'), + kind: vscode.CompletionItemKind.Method, + label: 'mockName3(mockParam3_1, mockParam3_2)', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockVariable1', + }, + { + kind: vscode.CompletionItemKind.Variable, + label: 'mockVariable2', + }, + ], + ], + ]; + tests.forEach(([name, args, expected]) => + test(name, () => + assert.deepEqual( + provideCompletionItemsInner(...args), + expected, + ), + ), + ); + }); +}); diff --git a/src/test/suite/providers/formatting/formattingProvider.utils.test.ts b/src/test/suite/providers/formatting/formattingProvider.utils.test.ts index db30c5d8..8e1aa96c 100644 --- a/src/test/suite/providers/formatting/formattingProvider.utils.test.ts +++ b/src/test/suite/providers/formatting/formattingProvider.utils.test.ts @@ -39,7 +39,7 @@ suite('FormattingProvider utils', () => { bn: number; } // List of test data - let dataList: TestBraceData[] = [ + const dataList: TestBraceData[] = [ // { // in: , // input test string // bc: , // brace character @@ -83,7 +83,7 @@ suite('FormattingProvider utils', () => { suite('buildIndentationChars', () => { // List of test data - let dataList = [ + const dataList = [ // { // dp: , // depth of indentation // rs: , // expected result @@ -140,7 +140,7 @@ suite('FormattingProvider utils', () => { suite('buildIndentedLine', () => { // List of test data - let dataList = [ + const dataList = [ // { // dp: , // depth of indentation // fl: , // formatted line @@ -223,7 +223,7 @@ suite('FormattingProvider utils', () => { suite('hasMoreCloseParens', () => { // List of test data - let dataList = [ + const dataList = [ // { // in: , // input test string // rs: , // expected result @@ -258,7 +258,7 @@ suite('FormattingProvider utils', () => { suite('hasMoreOpenParens', () => { // List of test data - let dataList = [ + const dataList = [ // { // in: , // input test string // rs: , // expected result @@ -293,7 +293,7 @@ suite('FormattingProvider utils', () => { suite('purify', () => { // List of test data - let dataList = [ + const dataList = [ // { // in: , // input test string // rs: , // expected result @@ -376,7 +376,7 @@ suite('FormattingProvider utils', () => { suite('removeEmptyLines', () => { // List of test data - let dataList = [ + const dataList = [ // { // in: , // input test string // ln: , // allowed empty lines @@ -500,7 +500,7 @@ suite('FormattingProvider utils', () => { suite('trimExtraSpaces', () => { // List of test data - let dataList = [ + const dataList = [ // { // in: , // input test string // rs: , // expected result @@ -543,7 +543,7 @@ suite('FormattingProvider utils', () => { suite('normalizeLineAssignOperator', () => { // List of test data - let dataList = [ + const dataList = [ // { // in: , // input test string // rs: , // expected result @@ -623,7 +623,7 @@ suite('FormattingProvider utils', () => { abc := a + b ; beautiful operator := */ - let dataList = [ + const dataList = [ // { // in: , // input test string // tp: , // target position @@ -692,7 +692,7 @@ suite('FormattingProvider utils', () => { suite('FlowOfControlNestDepth.enterBlockOfCode', () => { // List of test data - let dataList = [ + const dataList = [ // { // in: , // input array // bn: , // brace number @@ -721,7 +721,7 @@ suite('FormattingProvider utils', () => { suite('FlowOfControlNestDepth.exitBlockOfCode', () => { // List of test data - let dataList = [ + const dataList = [ // { // in: , // input array // bn: , // brace number @@ -750,7 +750,7 @@ suite('FormattingProvider utils', () => { suite('FlowOfControlNestDepth.pop', () => { // List of test data - let dataList = [ + const dataList = [ // { // in: , // input array // rs: , // expected result @@ -770,7 +770,7 @@ suite('FormattingProvider utils', () => { suite('FlowOfControlNestDepth.restoreEmptyDepth', () => { // List of test data - let dataList = [ + const dataList = [ // { // in: , // input array // rs: , // expected result @@ -790,7 +790,7 @@ suite('FormattingProvider utils', () => { suite('FlowOfControlNestDepth.restoreDepth', () => { // List of test data - let dataList = [ + const dataList = [ // { // in: , // input array // rs: , // expected result @@ -820,7 +820,7 @@ suite('FormattingProvider utils', () => { suite('alignSingleLineComments', () => { // List of test data - let dataList = [ + const dataList = [ // { // in: , // input test string // rs: , // expected result @@ -897,7 +897,7 @@ suite('FormattingProvider utils', () => { suite('calculateDepth', () => { // List of test data - let dataList = [ + const dataList = [ // { // in: , // input test string // rs: , // expected result @@ -939,7 +939,7 @@ suite('FormattingProvider utils', () => { suite('nextLineIsOneCommandCode', () => { // List of test data - let dataList = [ + const dataList = [ // { // in: , // input test string // rs: , // expected result diff --git a/src/test/suite/service/runnerService.test.ts b/src/test/suite/service/runnerService.test.ts index 99a74a53..5c51bd8a 100644 --- a/src/test/suite/service/runnerService.test.ts +++ b/src/test/suite/service/runnerService.test.ts @@ -20,7 +20,7 @@ suite('runnerService', () => { useMpress: false, }; - let tests: { + const tests: { name: string; compilePath: string; scriptPath: string;