Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve buildScript perf and overall docs #615

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"esbenp.prettier-vscode",
"simonsiefke.svg-preview",
"meganrogge.template-string-converter",
"tyriar.sort-lines"
"tyriar.sort-lines",
"wmaurer.change-case"
]
}
14 changes: 13 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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",
Expand Down
10 changes: 7 additions & 3 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
# Changelog

## 6.5.1 - unreleased

- Improve extension performance when loading AHK v1 scripts ([PR #615](https://github.com/mark-wiemer/ahkpp/pull/615))

## 6.5.0 - 2025-02-23 💞

### Debugger

- Add [docs/debugging.md](./docs/debugging.md) and [docs/\_welcome.md](./docs/_welcome.md)
- Fix `.vscode/launch.json` support for AHK v1 ([Issue #603](https://github.com/mark-wiemer/ahkpp/issues/603))
- Add `.vscode/launch.json` support for AHK v2 ([PR #606](https://github.com/mark-wiemer/ahkpp/issues/603))
- Change "AutoHotkey execute bin not found: ..." to "AutoHotkey interpreter not found" with a preceding message showing the interpreter path. ([PR #606](https://github.com/mark-wiemer/ahkpp/issues/603))
- Remove the `runtime` argument from `launch.json` for both AHK v1 and AHK v2 due to issues with cross-version debugging ([PR #606](https://github.com/mark-wiemer/ahkpp/issues/603))
- Add `.vscode/launch.json` support for AHK v2 ([Issue #603](https://github.com/mark-wiemer/ahkpp/issues/603))
- Change "AutoHotkey execute bin not found: ..." to "AutoHotkey interpreter not found" with a preceding message showing the interpreter path. ([PR #606](https://github.com/mark-wiemer/ahkpp/pull/606))
- Remove the `runtime` argument from `launch.json` for both AHK v1 and AHK v2 due to issues with cross-version debugging ([PR #606](https://github.com/mark-wiemer/ahkpp/pull/606))
- We are not considering this a breaking change as this behavior didn't work before. If you'd like to use different AHK interpreters across different workspaces, use IDE workspace settings. If you'd like to use different AHK interpreters within a single workspace, please [open a discussion](https://github.com/mark-wiemer/ahkpp/discussions/new/choose) and we'll be happy to help.

### IntelliSense
Expand Down
12 changes: 12 additions & 0 deletions src/parser/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, Script>();

Expand Down Expand Up @@ -49,11 +50,20 @@ 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 = {},
): Promise<Script> {
const funcName = 'buildScript';
const lang = document.languageId;
if (lang !== 'ahk' && lang !== 'ahk1') {
Out.debug(
`${funcName} skipping ${lang} doc at ${document.uri.path}`,
);
return undefined;
}

const cachedDocument = documentCache.get(document.uri.path);
if (options.usingCache && cachedDocument) {
return cachedDocument;
Expand Down Expand Up @@ -148,6 +158,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,
Expand All @@ -161,6 +172,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) {
Expand Down
50 changes: 37 additions & 13 deletions src/providers/ahkHoverProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Hover | null> {
const context = this.buildContext(document, position);
if (!context) {
return null;
Expand All @@ -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 += '()';
Expand All @@ -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) {
Expand Down Expand Up @@ -104,6 +124,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(
Expand Down
14 changes: 10 additions & 4 deletions src/providers/defProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 (not a directory or library include),
* 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 listed above
*/
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<vscode.Location | undefined> {
Expand Down
8 changes: 7 additions & 1 deletion src/providers/defProvider.utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <foo>'], '<foo>'],
];
Expand Down
38 changes: 22 additions & 16 deletions src/providers/defProvider.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <myLib>') === '<myLib>'
* 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 <myLib>') === '<myLib>'
* ('#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,
Expand All @@ -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 = (
/**
Expand All @@ -55,7 +61,7 @@ export const resolveIncludedPath = (
ahkLine: string,
): string | undefined => {
const includedPath = getIncludedPath(ahkLine);
if (!includedPath) return undefined;
if (!includedPath || includedPath.startsWith('<')) return undefined;

/** @example 'c:/path/to' */
const parentGoodPath = basePath.substring(1, basePath.lastIndexOf('/'));
Expand Down
2 changes: 1 addition & 1 deletion src/providers/symbolProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export class SymbolProvider implements vscode.DocumentSymbolProvider {
): Promise<vscode.DocumentSymbol[]> {
const result = [];

const script = await Parser.buildScript(document);
const script = await Parser.buildScript(document, { usingCache: true });

for (const method of script.methods) {
result.push(
Expand Down
Loading