From d1f28d573d0a72296ebfd59d3ad07b8a3e84c271 Mon Sep 17 00:00:00 2001 From: Mark Wiemer Date: Tue, 25 Feb 2025 05:07:57 -0800 Subject: [PATCH 1/9] Recommend change case extension --- .vscode/extensions.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 46876baf..1ac50bed 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -9,6 +9,7 @@ "esbenp.prettier-vscode", "simonsiefke.svg-preview", "meganrogge.template-string-converter", - "tyriar.sort-lines" + "tyriar.sort-lines", + "wmaurer.change-case" ] } From d5cc6811cf286850cdec483afd587da3e891416a Mon Sep 17 00:00:00 2001 From: Mark Wiemer Date: Tue, 25 Feb 2025 05:40:19 -0800 Subject: [PATCH 2/9] Add launch configs for "current file" --- .vscode/launch.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 146b0eac..20dfe134 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,6 +1,6 @@ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. -// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 +// https://code.visualstudio.com/docs/editor/debugging-configuration // See `code --help` for launch args { "version": "0.2.0", @@ -24,6 +24,18 @@ "testConfiguration": "${workspaceFolder}/.vscode-test.mjs" }, // below configs are just for manual tests, not for debugging this project + { + "name": "AHK v1 (current file)", + "type": "ahk", + "request": "launch", + "program": "${file}" + }, + { + "name": "AHK v2 (current file)", + "type": "ahk2", + "request": "launch", + "program": "${file}" + }, { "name": "AHK v1 \\", "type": "ahk", From dcde4d639afb842fc1052a7a6f52dbf7153dd408 Mon Sep 17 00:00:00 2001 From: Mark Wiemer Date: Tue, 25 Feb 2025 05:40:30 -0800 Subject: [PATCH 3/9] Document initSnippetCache --- src/providers/ahkHoverProvider.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/providers/ahkHoverProvider.ts b/src/providers/ahkHoverProvider.ts index 4e9aa31f..c9e120fc 100644 --- a/src/providers/ahkHoverProvider.ts +++ b/src/providers/ahkHoverProvider.ts @@ -104,6 +104,10 @@ export class AhkHoverProvider implements HoverProvider { return { word, charAfter }; } + /** + * Loads built-in snippets from the extension. + * @see [snippetsV1.json](../../language/snippetsV1.json) + */ private initSnippetCache(context: ExtensionContext) { const ahk = JSON.parse( readFileSync( From b33cefa9d1c9404b33751654db22e4cf494d9507 Mon Sep 17 00:00:00 2001 From: Mark Wiemer Date: Tue, 25 Feb 2025 06:36:35 -0800 Subject: [PATCH 4/9] Improve documentation --- src/parser/parser.ts | 4 +++ src/providers/ahkHoverProvider.ts | 46 ++++++++++++++++++------- src/providers/defProvider.ts | 14 +++++--- src/providers/defProvider.utils.test.ts | 10 ++++-- src/providers/defProvider.utils.ts | 36 +++++++++++-------- 5 files changed, 76 insertions(+), 34 deletions(-) diff --git a/src/parser/parser.ts b/src/parser/parser.ts index ef15c20f..a8c10434 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -6,6 +6,7 @@ import { pathsToBuild } from './parser.utils'; import { Out } from '../common/out'; const startBlockComment = / *\/\*/; +// todo does endBlockComment work on lines like `; */` ? const endBlockComment = / *\*\//; const documentCache = new Map(); @@ -49,6 +50,7 @@ export class Parser { * Parse the document into a Script and add it to the cache * @param document */ + // todo add tests public static async buildScript( document: vscode.TextDocument, options: BuildScriptOptions = {}, @@ -148,6 +150,7 @@ export class Parser { * If a method of this name exists in the current file, returns that method. * Otherwise, searches through document cache to find the matching method. * Matches are not case-sensitive and only need to match method name. + * Note that duplicate method definitions are not allowed in AHK v1. */ public static async getMethodByName( document: vscode.TextDocument, @@ -161,6 +164,7 @@ export class Parser { } } // todo this should prioritize included files first. + // https://github.com/mark-wiemer/ahkpp/issues/205 for (const filePath of localCache.keys()) { for (const method of localCache.get(filePath).methods) { if (method.name.toLowerCase() === name) { diff --git a/src/providers/ahkHoverProvider.ts b/src/providers/ahkHoverProvider.ts index c9e120fc..34c03568 100644 --- a/src/providers/ahkHoverProvider.ts +++ b/src/providers/ahkHoverProvider.ts @@ -29,7 +29,16 @@ export class AhkHoverProvider implements HoverProvider { this.initSnippetCache(context); } - public async provideHover(document: TextDocument, position: Position) { + /** + * - If there's no context, return null. + * - If there's a snippet hover, return it. + * - If there's a method hover, return it. + * - Otherwise, return null. + */ + public async provideHover( + document: TextDocument, + position: Position, + ): Promise { const context = this.buildContext(document, position); if (!context) { return null; @@ -41,20 +50,23 @@ export class AhkHoverProvider implements HoverProvider { } const method = await Parser.getMethodByName(document, context.word); - if (method) { - const contents = new MarkdownString('', true).appendCodeblock( - method.full, - ); - if (method.comment) { - contents.appendText(method.comment); - } - return new Hover(contents); + if (!method) { + return null; } - - return null; + const contents = new MarkdownString('', true).appendCodeblock( + method.full, + ); + if (method.comment) { + contents.appendText(method.comment); + } + return new Hover(contents); } - private tryGetSnippetHover(context: Context): Hover { + /** + * Returns the matching snippet hover, if it exists. + * Otherwise returns undefined. + */ + private tryGetSnippetHover(context: Context): Hover | undefined { let snippetKey = context.word.toLowerCase(); if (context.charAfter === '(') { snippetKey += '()'; @@ -73,7 +85,15 @@ export class AhkHoverProvider implements HoverProvider { return new Hover(content); } - private buildContext(document: TextDocument, position: Position): Context { + /** + * Gets the `Context` for the provided document and position + * + * @returns `undefined` if the position is not on a word + */ + private buildContext( + document: TextDocument, + position: Position, + ): Context | undefined { const line = position.line; const wordRange = document.getWordRangeAtPosition(position); if (!wordRange) { diff --git a/src/providers/defProvider.ts b/src/providers/defProvider.ts index f7215d3a..3fc1261a 100644 --- a/src/providers/defProvider.ts +++ b/src/providers/defProvider.ts @@ -104,16 +104,22 @@ export class DefProvider implements vscode.DefinitionProvider { /** * If the position is on an `#Include` line - * and the included path is an existing file, + * and the included path* is an existing file, * returns a Location at the beginning of the included file. * * Otherwise returns undefined. * - ** Currently assumes the working directory is the script path and - * does not respect previous `#include dir` directives + * \* "Included path" is currently calculated with assumptions: + * - A_WorkingDir === A_ScriptDir + * - No previous `#Include Directory` lines changing the "include working directory" + * + * todo remove tryGetFileLink assumptions */ export async function tryGetFileLink( - /** @example '/c:/path/to/file.ahk' */ + /** + * Path of the current script + * @example '/c:/path/to/file.ahk' + */ docPath: string, text: string, ): Promise { diff --git a/src/providers/defProvider.utils.test.ts b/src/providers/defProvider.utils.test.ts index 44215954..547381b5 100644 --- a/src/providers/defProvider.utils.test.ts +++ b/src/providers/defProvider.utils.test.ts @@ -2,7 +2,7 @@ import { suite, test } from 'mocha'; import assert from 'assert'; import { getIncludedPath, resolveIncludedPath } from './defProvider.utils'; -suite(getIncludedPath.name, () => { +suite.only(getIncludedPath.name, () => { const tests: [ name: string, args: Parameters, @@ -24,7 +24,13 @@ suite(getIncludedPath.name, () => { ['escaped `;` with whitespace', ['#include a `;b.ahk'], 'a `;b.ahk'], ['escaped `;` without whitespace', ['#include a`;b.ahk'], 'a`;b.ahk'], ['unescaped `;` without whitespace', ['#include a;b.ahk'], 'a;b.ahk'], - ['unescaped `;` with whitespace', ['#include a ;b.ahk'], 'a'], + ['unescaped `;` with preceding whitespace', ['#include a ;b.ahk'], 'a'], + ['unescaped `;` with trailing whitespace', ['#include a ; b.ahk'], 'a'], + [ + 'escaped `;` with trailing whitespace', + ['#include a `; b.ahk'], + 'a `; b.ahk', + ], ['unescaped valid `%`', ['#include %A_ScriptDir%'], '%A_ScriptDir%'], ['unescaped `<` and `>`', ['#include '], ''], ]; diff --git a/src/providers/defProvider.utils.ts b/src/providers/defProvider.utils.ts index 2256cdeb..9d2cd314 100644 --- a/src/providers/defProvider.utils.ts +++ b/src/providers/defProvider.utils.ts @@ -5,22 +5,29 @@ import { isAbsolute, join, normalize } from 'path'; /** ** Returns the string representing the included path after the `#include`. ** Only works for actual `#include` directives, not comments or strings containing `#include`. + ** Does not escape semi-colons, returns raw string including backtick character. ** Does not resolve or normalize the included path. * @example - * getIncludedPath('#include , a b.ahk') === 'a b.ahk' - * getIncludedPath(' #include path/to/file.ahk') === 'path/to/file.ahk' - * getIncludedPath('include , a b.ahk') === undefined // no `#` - * getIncludedPath('; #include , a b.ahk') === undefined - * getIncludedPath('x := % "#include , a b.ahk"') === undefined - * getIncludedPath('#include a') === 'a' - * getIncludedPath('#include %A_ScriptDir%') === '%A_ScriptDir%' - * getIncludedPath('#include ') === '' - * getIncludedPath('#include semi-colon ;and-more.ahk') === 'semi-colon' - * getIncludedPath('#include semi-colon`;and-more.ahk') === 'semi-colon`;and-more.ahk' + * ('#include , a b.ahk') === 'a b.ahk' + * (' #include path/to/file.ahk') === 'path/to/file.ahk' + * ('include , a b.ahk') === undefined // no `#` + * ('; #include , a b.ahk') === undefined + * ('x := % "#include , a b.ahk"') === undefined + * ('#include a') === 'a' + * ('#include %A_ScriptDir%') === '%A_ScriptDir%' + * ('#include ') === '' + * ('#include semi-colon ;and-more.ahk') === 'semi-colon' + * ('#include semi-colon`;and-more.ahk') === 'semi-colon`;and-more.ahk' */ export const getIncludedPath = (ahkLine: string): string | undefined => ahkLine.match(/^\s*#include\s*,?\s*(.+?)( ;.*)?$/i)?.[1]; +/** + * Trims, escapes semi-colons, and resolves: + * - `A_ScriptDir` + * - `A_WorkingDir` + * - `A_LineFile` + */ const normalizeIncludedPath = ( includedPath: string, basePath: string, @@ -36,14 +43,13 @@ const normalizeIncludedPath = ( /** * Returns the absolute, normalized path included by a #include directive. - * Does not check if that path is a to a folder, or if that path exists. - * - * @param basePath - * The path to include from, usually the script's path. + * Does not check if that path is to a folder or if the path exists. + * Assumes A_WorkingDir === A_ScriptDir. * + * @param basePath The path to include from, usually the script's path. * This may be a different path if the including script has a preceding `#include dir` * - * @param includedPath The path that's included in the `#include` directive + * @returns The absolute included path */ export const resolveIncludedPath = ( /** From 9a2a8e68d69cfe5973f9d942923f3592a8155afb Mon Sep 17 00:00:00 2001 From: Mark Wiemer Date: Tue, 25 Feb 2025 06:45:52 -0800 Subject: [PATCH 5/9] (v1) Don't "build" non-AHK scripts --- src/parser/parser.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/parser/parser.ts b/src/parser/parser.ts index a8c10434..513c3b33 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -56,6 +56,14 @@ export class Parser { options: BuildScriptOptions = {}, ): Promise