├── img ├── ai_icon.png └── docs │ ├── CtrlShiftP.png │ ├── diagnostics.gif │ ├── symbolsearch.gif │ ├── signaturehelp.gif │ └── AutoItConfiguration.png ├── .gitignore ├── .vscodeignore ├── test ├── jsconfig.json ├── fixtures │ ├── helper.au3 │ ├── main.au3 │ └── au3check-params-test.au3 ├── __mocks__ │ └── vscode.js ├── ai_workspaceSymbols.test.js ├── security │ └── parameterValidation.test.js └── utils │ └── pathValidation.test.js ├── .vscode ├── tasks.json ├── AutoIt-VSCode.code-workspace ├── launch.json └── dev_snippets.code-snippets ├── .prettierrc.json ├── src ├── languageConfiguration.js ├── commandsList.js ├── registerCommands.js ├── ai_hover.js ├── commands │ ├── trace.js │ ├── commandUtils.js │ ├── debugRemove.js │ ├── functionTraceAdd.js │ └── DebugCommands.js ├── completions │ ├── constants_avi.js │ ├── constants_progress.js │ ├── constants_dir.js │ ├── udf_debug.js │ ├── constants_updown.js │ ├── constants_slider.js │ ├── constants_listbox.js │ ├── constantsInet.js │ ├── constants_datetime.js │ ├── constants_combo.js │ ├── constants_static.js │ ├── constants_frame.js │ ├── constants_buttonconstants.js │ ├── constants_tab.js │ ├── constants_string.js │ ├── constants_listview.js │ ├── constants_msgbox.js │ ├── constants_tray.js │ └── constants_statusbar.js ├── signatures │ ├── udf_process.js │ ├── udf_math.js │ ├── udf_sendmessage.js │ ├── udf_color.js │ ├── udf_timers.js │ ├── WinAPIEx │ │ └── WinAPICom.js │ ├── udf_visa.js │ ├── udf_string.js │ ├── udf_sound.js │ ├── udf_inet.js │ ├── index.js │ ├── udf_screencapture.js │ ├── udf_crypt.js │ └── udf_guictrlavi.js ├── ai_showMessage.js ├── command_constants.js ├── constants.js ├── utils │ ├── pathValidation.js │ ├── IncludeResolver.js │ └── parameterValidation.js ├── ai_workspaceSymbols.js └── services │ └── MapTrackingService.js ├── babel.config.json ├── tsconfig.json ├── LICENSE ├── syntaxes ├── autoit-language-configuration.json └── vscode-autoit-output.tmLanguage.json ├── webpack.config.js ├── scripts ├── quick-flaky-check.js ├── package-openvsx.js └── package-all.js ├── eslint.config.mjs └── docs └── map-support.md /img/ai_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loganch/AutoIt-VSCode/HEAD/img/ai_icon.png -------------------------------------------------------------------------------- /img/docs/CtrlShiftP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loganch/AutoIt-VSCode/HEAD/img/docs/CtrlShiftP.png -------------------------------------------------------------------------------- /img/docs/diagnostics.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loganch/AutoIt-VSCode/HEAD/img/docs/diagnostics.gif -------------------------------------------------------------------------------- /img/docs/symbolsearch.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loganch/AutoIt-VSCode/HEAD/img/docs/symbolsearch.gif -------------------------------------------------------------------------------- /img/docs/signaturehelp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loganch/AutoIt-VSCode/HEAD/img/docs/signaturehelp.gif -------------------------------------------------------------------------------- /img/docs/AutoItConfiguration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loganch/AutoIt-VSCode/HEAD/img/docs/AutoItConfiguration.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules 3 | jspm_packages 4 | dist/ 5 | .cache 6 | *.vsix 7 | .qodo 8 | .jest-cache -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | test/** 4 | .gitignore 5 | src/ 6 | node_modules 7 | .eslintrc.json 8 | .prettierrc.json 9 | .cache 10 | **/*.map 11 | -------------------------------------------------------------------------------- /test/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["jest", "node", "@types/jest"] 5 | }, 6 | "include": ["**/*.js"], 7 | "exclude": ["node_modules"] 8 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [{ "label": "npm: webpack", "type": "npm", "script": "webpack" }] 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/helper.au3: -------------------------------------------------------------------------------- 1 | ; nested include to test recursion (not existing to simulate missing readable) 2 | #include "missing.au3" 3 | Const $CONST_ONE = 1 4 | Local $helperVar = 2 5 | ; volatile before name 6 | Func volatile HelperFunc($v) 7 | Return $v 8 | EndFunc -------------------------------------------------------------------------------- /test/fixtures/main.au3: -------------------------------------------------------------------------------- 1 | #include "helper.au3" 2 | #include 3 | Local $a, $b = 1, _ 4 | $c 5 | Global $Mixed_Name123 = 0 6 | ; function with volatile after name 7 | Func DoWork volatile($x, $y) 8 | Return $x + $y 9 | EndFunc 10 | ; function normal 11 | Func NormalFunc($p) 12 | Return $p 13 | EndFunc -------------------------------------------------------------------------------- /.vscode/AutoIt-VSCode.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "..", 5 | "name": "extension" 6 | } 7 | ], 8 | "settings": { 9 | "editor.tabSize": 2, 10 | "jshint.options": 11 | { 12 | "-W014": true //Misleading line break before '?'; readers may interpret this as an expression boundary. 13 | } 14 | }, 15 | } -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "quoteProps": "as-needed", 8 | "trailingComma": "all", 9 | "bracketSpacing": true, 10 | "bracketSameLine": false, 11 | "arrowParens": "avoid", 12 | "endOfLine": "auto", 13 | "embeddedLanguageFormatting": "auto", 14 | "singleAttributePerLine": false 15 | } 16 | -------------------------------------------------------------------------------- /src/languageConfiguration.js: -------------------------------------------------------------------------------- 1 | const languageConfiguration = { 2 | indentationRules: { 3 | increaseIndentPattern: /^\s*(For|Func|If|ElseIf|Else|Select|While|Case|Switch|With)\b/i, 4 | decreaseIndentPattern: 5 | /^\s*(Next|EndFunc|EndIf|ElseIf|Else|EndSelect|EndSwitch|WEnd|EndWith)\b/i, 6 | unIndentedLinePattern: /^\s*((;|#include).*)?$/, 7 | }, 8 | }; 9 | 10 | export default languageConfiguration; 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Extension", 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "runtimeExecutable": "execPath", 9 | "args": ["--profile-temp", "--extensionDevelopmentPath=${workspaceFolder}"], 10 | "preLaunchTask": "npm: webpack", 11 | "smartStep": true, 12 | "sourceMaps": true, 13 | "outFiles": ["${workspaceFolder}/dist/**/*.js"] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/commandsList.js: -------------------------------------------------------------------------------- 1 | export const commandsPrefix = 'extension.'; 2 | // key = command, value = function name 3 | export const commandsList = [ 4 | 'runScript', 5 | 'launchHelp', 6 | 'launchInfo', 7 | 'debugMsgBox', 8 | 'debugConsole', 9 | 'compile', 10 | 'tidy', 11 | 'check', 12 | 'build', 13 | 'launchKoda', 14 | 'changeParams', 15 | 'killScript', 16 | 'killScriptOpened', 17 | 'openInclude', 18 | 'insertHeader', 19 | 'restartScript', 20 | 'debugRemove', 21 | 'functionTraceAdd', 22 | 'traceRemove', 23 | ]; 24 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "18.17.0" 8 | }, 9 | "modules": "commonjs", 10 | "useBuiltIns": false, 11 | "bugfixes": true, 12 | "shippedProposals": true 13 | } 14 | ] 15 | ], 16 | "env": { 17 | "development": { 18 | "sourceMaps": "inline", 19 | "retainLines": true 20 | }, 21 | "production": { 22 | "sourceMaps": false, 23 | "compact": true 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/registerCommands.js: -------------------------------------------------------------------------------- 1 | import { commands } from 'vscode'; 2 | import * as aiCommands from './ai_commands'; 3 | import { commandsList, commandsPrefix } from './commandsList'; 4 | 5 | export const registerCommands = ctx => { 6 | const aiCommandsMap = new Map(Object.entries(aiCommands)); 7 | 8 | for (const command of commandsList) { 9 | const commandFunc = aiCommandsMap.get(command); 10 | if (typeof commandFunc === 'function') { 11 | ctx.subscriptions.push(commands.registerCommand(commandsPrefix + command, commandFunc)); 12 | } 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /src/ai_hover.js: -------------------------------------------------------------------------------- 1 | import { Hover, languages } from 'vscode'; 2 | import hovers from './hovers'; 3 | import { AUTOIT_MODE } from './util'; 4 | 5 | const hoverFeature = languages.registerHoverProvider(AUTOIT_MODE, { 6 | provideHover(document, position) { 7 | const wordRange = document.getWordRangeAtPosition(position); 8 | 9 | const word = wordRange ? document.getText(wordRange).toLowerCase() : ''; 10 | 11 | if (word in hovers) { 12 | return new Hover(hovers[word]); 13 | } 14 | 15 | return null; 16 | }, 17 | }); 18 | 19 | export default hoverFeature; 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "node16", 6 | "allowJs": true, 7 | "checkJs": true, 8 | "isolatedModules": true, 9 | "strict": false, 10 | "esModuleInterop": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "resolveJsonModule": true, 13 | "skipLibCheck": true, 14 | "noEmit": true, 15 | "lib": ["ES2022"], 16 | "types": ["jest", "node", "vscode"] 17 | }, 18 | "include": ["src", "test"], 19 | "exclude": ["node_modules", "dist", "out"] 20 | } -------------------------------------------------------------------------------- /src/commands/trace.js: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import searchAndReplace from './commandUtils'; 3 | 4 | const functionTracePattern = /\s+?(;~?\s+)?ConsoleWrite\([^\r\n]+\)[ \t]*;### Trace[^\r\n]+/g; 5 | 6 | async function traceRemove() { 7 | const traceRemovalResult = await searchAndReplace(functionTracePattern, ''); 8 | 9 | if (traceRemovalResult) { 10 | vscode.window.showInformationMessage(`${traceRemovalResult} trace line(s) removed.`); 11 | } else { 12 | vscode.window.showInformationMessage('No trace lines found.'); 13 | } 14 | } 15 | 16 | export { traceRemove as default }; 17 | -------------------------------------------------------------------------------- /src/completions/constants_avi.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { completionToHover, fillCompletions } from '../util'; 3 | 4 | const items = [ 5 | { 6 | label: '$ACS_AUTOPLAY', 7 | documentation: 'Starts playing the animation as soon as the AVI clip is opened. ', 8 | }, 9 | { 10 | label: '$ACS_CENTER', 11 | documentation: "Centers the animation in the animation control's window. ", 12 | }, 13 | { 14 | label: '$ACS_TRANSPARENT', 15 | documentation: 16 | 'Allows you to match an animation\'s background color to that of the underlying window, creating a "transparent" background. (Default value) ', 17 | }, 18 | { 19 | label: '$ACS_NONTRANSPARENT', 20 | documentation: 'To override default ACS_TRANSPARENT ', 21 | }, 22 | ]; 23 | 24 | const completions = fillCompletions( 25 | items, 26 | CompletionItemKind.Constant, 27 | 'AVI Clip Style Constant', 28 | 'AVIConstants.au3', 29 | ); 30 | const hovers = completionToHover(completions); 31 | 32 | export { completions as default, hovers }; 33 | -------------------------------------------------------------------------------- /test/__mocks__/vscode.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Position: class Position { 3 | constructor(line, character) { 4 | this.line = line; 5 | this.character = character; 6 | } 7 | }, 8 | Range: class Range { 9 | constructor(start, end) { 10 | this.start = start; 11 | this.end = end; 12 | } 13 | }, 14 | Location: class Location { 15 | constructor(uri, range) { 16 | this.uri = uri; 17 | this.range = range; 18 | } 19 | }, 20 | Uri: { 21 | file: p => ({ fsPath: p, toString: () => p }), 22 | }, 23 | workspace: { 24 | getConfiguration: jest.fn(), 25 | findFiles: jest.fn(), 26 | openTextDocument: jest.fn(), 27 | createFileSystemWatcher: jest.fn(() => ({ 28 | onDidChange: jest.fn(), 29 | onDidCreate: jest.fn(), 30 | onDidDelete: jest.fn(), 31 | })), 32 | onDidChangeConfiguration: jest.fn(), 33 | }, 34 | languages: { 35 | registerWorkspaceSymbolProvider: jest.fn(), 36 | }, 37 | window: { 38 | showWarningMessage: jest.fn(), 39 | showErrorMessage: jest.fn(), 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Logan Hampton 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. -------------------------------------------------------------------------------- /src/signatures/udf_process.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { 3 | br, 4 | valueFirstHeader as header, 5 | opt, 6 | signatureToCompletion, 7 | signatureToHover, 8 | } from '../util'; 9 | 10 | const include = '(Requires: `#include `)'; 11 | 12 | const signatures = { 13 | _ProcessGetName: { 14 | documentation: 'Returns a string containing the process name that belongs to a given PID', 15 | label: '_ProcessGetName ( $iPID )', 16 | params: [ 17 | { 18 | label: '$iPID', 19 | documentation: 'The PID of a currently running process.', 20 | }, 21 | ], 22 | }, 23 | _ProcessGetPriority: { 24 | documentation: 'Get the priority of an open process', 25 | label: '_ProcessGetPriority ( $vProcess )', 26 | params: [ 27 | { 28 | label: '$vProcess', 29 | documentation: 'The name or PID of the process to be examined.', 30 | }, 31 | ], 32 | }, 33 | }; 34 | 35 | const hovers = signatureToHover(signatures); 36 | const completions = signatureToCompletion(signatures, CompletionItemKind.Function, include); 37 | 38 | export { signatures as default, hovers, completions }; 39 | -------------------------------------------------------------------------------- /src/completions/constants_progress.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { fillCompletions } from '../util'; 3 | 4 | const items = [ 5 | { 6 | label: '$PBS_MARQUEE', 7 | documentation: 'Displays progress status as a scrolling marquee.', 8 | }, 9 | { 10 | label: '$PBS_SMOOTH', 11 | documentation: 12 | "Displays progress status in a smooth scrolling bar instead of the default segmented bar.\nNote This style is supported only in the Windows Classic theme. All other themes won't visually change with or without this style.", 13 | }, 14 | { 15 | label: '$PBS_SMOOTHREVERSE', 16 | documentation: 17 | 'Displays progress status with a smooth backward transition when changing from a higher value to a lower value. By default, the control will instantly jump to the lower value.\nNote This style is supported only on Windows Vista or later. ', 18 | }, 19 | { 20 | label: '$PBS_VERTICAL', 21 | documentation: 'Displays progress status vertically, from bottom to top.', 22 | }, 23 | ]; 24 | 25 | export default fillCompletions( 26 | items, 27 | CompletionItemKind.Constant, 28 | 'Progress Bar Style Constant', 29 | 'ProgressConstants.au3', 30 | ); 31 | -------------------------------------------------------------------------------- /src/commands/commandUtils.js: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | /** 4 | * Searches for matches of a regular expression pattern in the active text editor's document 5 | * and replaces them with a replacement string. 6 | * @param {RegExp} regex - The regular expression pattern to search for. 7 | * @param {string} [replacement='\r\n'] - The string to replace the matched patterns with. 8 | * @returns {Promise} A promise that resolves to the number of replacements made. 9 | */ 10 | async function searchAndReplace(regex, replacement = '\r\n') { 11 | const editor = vscode.window.activeTextEditor; 12 | 13 | if (!editor) { 14 | vscode.window.showErrorMessage('No active editor'); 15 | return 0; 16 | } 17 | 18 | const { document } = editor; 19 | const text = document.getText(); 20 | 21 | const updatedText = text.replace(regex, replacement); 22 | 23 | if (updatedText === text) { 24 | return 0; 25 | } 26 | 27 | await editor.edit(editBuilder => { 28 | const fullRange = new vscode.Range(document.positionAt(0), document.positionAt(text.length)); 29 | editBuilder.replace(fullRange, updatedText); 30 | }); 31 | 32 | return (text.match(regex) || []).length; 33 | } 34 | 35 | export default searchAndReplace; 36 | -------------------------------------------------------------------------------- /src/completions/constants_dir.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { fillCompletions } from '../util'; 3 | 4 | const items = [ 5 | { 6 | label: '$DDL_ARCHIVE', 7 | documentation: 'Includes archived files\n\n`= 0x00000020`', 8 | }, 9 | { 10 | label: '$DDL_DIRECTORY', 11 | documentation: 'Includes directories\n\n`= 0x00000010`', 12 | }, 13 | { 14 | label: '$DDL_DRIVES', 15 | documentation: 'All mapped drives are added to the list\n\n`= 0x00004000`', 16 | }, 17 | { 18 | label: '$DDL_EXCLUSIVE', 19 | documentation: 'Includes only files with the specified attributes\n\n`= 0x00008000`', 20 | }, 21 | { 22 | label: '$DDL_HIDDEN', 23 | documentation: 'Includes hidden files\n\n`= 0x00000002`', 24 | }, 25 | { 26 | label: '$DDL_READONLY', 27 | documentation: 'Includes read-only files\n\n`= 0x00000001`', 28 | }, 29 | { 30 | label: '$DDL_READWRITE', 31 | documentation: 'Includes read-write files with no additional attributes\n\n`= 0x00000000`', 32 | }, 33 | { 34 | label: '$DDL_SYSTEM', 35 | documentation: 'Includes system files\n\n`= 0x00000004`', 36 | }, 37 | ]; 38 | 39 | export default fillCompletions( 40 | items, 41 | CompletionItemKind.Constant, 42 | 'Dir Constant', 43 | 'DirConstants.au3', 44 | ); 45 | -------------------------------------------------------------------------------- /.vscode/dev_snippets.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | // Place your AutoIt-VSCode workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and 3 | // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope 4 | // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is 5 | // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: 6 | // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. 7 | // Placeholders with the same ids are connected. 8 | // Example: 9 | // "Print to console": { 10 | // "scope": "javascript,typescript", 11 | // "prefix": "log", 12 | // "body": [ 13 | // "console.log('$1');", 14 | // "$2" 15 | // ], 16 | // "description": "Log output to console" 17 | // } 18 | "Signature Param": { 19 | "scope": "javascript", 20 | "prefix": "sigparam", 21 | "body": "{ label: '$1', documentation: `$2`},", 22 | "description": "Adds a signature new paramter" 23 | }, 24 | "Optional Signature Param": { 25 | "scope": "javascript", 26 | "prefix": "optsig", 27 | "body": "{ label: '$1', documentation: `\\${opt\\} $2`},", 28 | "description": "Adds an optional signature parameter" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/fixtures/au3check-params-test.au3: -------------------------------------------------------------------------------- 1 | ; Test script for #AutoIt3Wrapper_AU3Check_Parameters parsing 2 | ; Tests supported Au3Check parameters: -w, -q, -d 3 | ; NOTE: -v (verbosity) parameters are NOT supported to maintain consistent diagnostic parsing 4 | ; IMPORTANT: Extension no longer sets default warning params - Au3Check uses its own defaults 5 | #AutoIt3Wrapper_AU3Check_Parameters=-q -d -w- 3 -w- 5 6 | 7 | ; This script intentionally has issues that would trigger various warnings 8 | ; to verify that the directive correctly applies supported parameter types 9 | 10 | ; Warning 3: already declared var (DISABLED by -w- 3 in directive) 11 | Local $sTest = "First" 12 | Local $sTest = "Second" ; This should NOT trigger warning 3 13 | 14 | ; Warning 5: local var declared but not used (DISABLED by -w- 5 in directive) 15 | Local $sUnused = "Never used" ; This should NOT trigger warning 5 16 | 17 | ; -d flag: must declare vars (enabled by directive) 18 | ; This forces Opt("MustDeclareVars", 1) behavior 19 | 20 | ; -q flag: quiet mode (enabled by directive) 21 | ; This suppresses informational messages while keeping error/warning format 22 | 23 | MsgBox(0, "Test", $sTest) 24 | 25 | ; Additional test: Try using an undeclared variable 26 | ; This should trigger an error with -d flag from directive 27 | ; Uncomment the line below to test: 28 | ; $undeclared = "This should fail with -d flag" 29 | -------------------------------------------------------------------------------- /src/commands/debugRemove.js: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import searchAndReplace from './commandUtils'; 3 | 4 | /** 5 | * Removes debug lines from an AutoIt Script. 6 | * 7 | * This function uses regular expressions to find and replace the debug lines in the active text editor. 8 | * If any replacements are made, it displays a success message. 9 | * Otherwise, it displays a message indicating that no debug lines were found. 10 | * 11 | * @returns {Promise} A promise that resolves once the debug lines are removed. 12 | */ 13 | async function debugRemove() { 14 | const consoleWriteDebugPattern = 15 | /\s+?(;~?\s+)?;### Debug CONSOLE.*?\r\n\s?(;~?\s+)?ConsoleWrite\('@@ Debug\('.+\r\n/g; 16 | const msgBoxDebugPattern = 17 | /\s+?(;~?\s+)?;### Debug MSGBOX.*?\r\n\s?(;~?\s+)?MsgBox\(262144, 'Debug line ~'.+\r\n/g; 18 | 19 | const consoleWriteReplacementsMade = await searchAndReplace(consoleWriteDebugPattern); 20 | const msgBoxReplacementsMade = await searchAndReplace(msgBoxDebugPattern); 21 | 22 | if (consoleWriteReplacementsMade || msgBoxReplacementsMade) { 23 | vscode.window.showInformationMessage( 24 | `${consoleWriteReplacementsMade + msgBoxReplacementsMade} Debug line(s) removed successfully`, 25 | ); 26 | } else { 27 | vscode.window.showInformationMessage('No debug lines found'); 28 | } 29 | } 30 | 31 | export { debugRemove as default }; 32 | -------------------------------------------------------------------------------- /syntaxes/autoit-language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | // symbol used for single line comment. Remove this entry if your language does not support line comments 4 | "lineComment": ";~", 5 | // symbols used for start and end a block comment. Remove this entry if your language does not support block comments 6 | "blockComment": ["#cs\n", "\n#ce"] 7 | }, 8 | // symbols used as brackets 9 | "brackets": [ 10 | ["{", "}"], 11 | ["[", "]"], 12 | ["(", ")"] 13 | ], 14 | "surroundingPairs": [ 15 | ["(", ")"], 16 | ["[", "]"], 17 | ["{", "}"], 18 | ["\"", "\""], 19 | ["'", "'"] 20 | ], 21 | "autoClosingPairs": [ 22 | ["(", ")"], 23 | ["[", "]"], 24 | ["{", "}"], 25 | { "open": "\"", "close": "\"", "notIn": ["string", "comment"] }, 26 | { "open": "'", "close": "'", "notIn": ["string", "comment"] } 27 | ], 28 | "folding": { 29 | "offSide": true, 30 | "markers": { 31 | "start": "^\\s*\\s*((#?[Rr]egion\\b)|([Ff]unc\\b)|([iI]f\\b)|([sS]witch\\b)|([fF]or\\b)|([wW]hile\\b)|([wW]ith\\b)|(#c(?:omments\\-start|s)))\\b", 32 | "end": "^\\s*\\s*((#?[Ee]nd[Rr]egion\\b)|([Ee]nd[Ff]unc)|([eE]nd[iI]f)|([eE]nd[sS]witch)|([nN]ext)|([wW][eE]nd)|([eE]nd[wW]ith)|(#c(?:omments\\-end|e)))\\b" 33 | } 34 | }, 35 | "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)" 36 | } 37 | -------------------------------------------------------------------------------- /src/completions/udf_debug.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { fillCompletions } from '../util'; 3 | 4 | const items = [ 5 | { 6 | label: '_Assert', 7 | documentation: 'Display a message if assertion fails', 8 | }, 9 | { 10 | label: '_DebugArrayDisplay', 11 | documentation: 'Displays a 1D or 2D array in a ListView to aid debugging', 12 | }, 13 | { 14 | label: '_DebugBugReportEnv', 15 | documentation: 'Outputs a string containing information for Bug report submission', 16 | }, 17 | { 18 | label: '_DebugCOMError', 19 | documentation: 'Sets, resets or queries the debug level for COM errors', 20 | }, 21 | { 22 | label: '_DebugOut', 23 | documentation: 'Displays output on a debugging session started by _DebugSetup()', 24 | }, 25 | { 26 | label: '_DebugReport', 27 | documentation: 'Writes to a debugging session', 28 | }, 29 | { 30 | label: '_DebugReportEx', 31 | documentation: 'Writes to a debugging session a formatted message', 32 | }, 33 | { 34 | label: '_DebugReportVar', 35 | documentation: 'Writes to debugging session the content of a variable', 36 | }, 37 | { 38 | label: '_DebugSetup', 39 | documentation: 'Setup up a debug session using a specific reporting type ', 40 | }, 41 | ]; 42 | 43 | const functions = fillCompletions(items, CompletionItemKind.Function, 'Debug UDF', 'Debug.au3'); 44 | 45 | export default functions; 46 | -------------------------------------------------------------------------------- /src/completions/constants_updown.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { fillCompletions } from '../util'; 3 | 4 | const items = [ 5 | { 6 | label: '$UDS_ALIGNLEFT', 7 | documentation: 8 | 'Positions the up-down control next to the left edge of the buddy window. The buddy window is moved to the right and its width is decreased to accommodate the width of the up-down control.', 9 | }, 10 | { 11 | label: '$UDS_ALIGNRIGHT', 12 | documentation: 13 | 'Positions the up-down control next to the right edge of the buddy window. The width of the buddy window is decreased to accommodate the width of the up-down control.', 14 | }, 15 | { 16 | label: '$UDS_ARROWKEYS', 17 | documentation: 18 | 'Causes the up-down control to process the UP ARROW and DOWN ARROW keys on the keyboard.', 19 | }, 20 | { 21 | label: '$UDS_HORZ', 22 | documentation: 23 | "Causes the up-down control's arrows to point left and right instead of up and down.", 24 | }, 25 | { 26 | label: '$UDS_NOTHOUSANDS', 27 | documentation: 28 | 'Prevents insertion of a thousands separator between every three decimal positions.', 29 | }, 30 | { 31 | label: '$UDS_WRAP', 32 | documentation: 33 | 'Causes the position to wrap if it is incremented or decremented beyond the end or beginning of the range.', 34 | }, 35 | ]; 36 | 37 | export default fillCompletions( 38 | items, 39 | CompletionItemKind.Constant, 40 | 'Up-down Style Constant', 41 | 'UpDownConstants.au3', 42 | ); 43 | -------------------------------------------------------------------------------- /src/completions/constants_slider.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { fillCompletions } from '../util'; 3 | 4 | const items = [ 5 | { 6 | label: '$TBS_AUTOTICKS', 7 | documentation: 8 | 'Adds tick marks when you set the range on the trackbar by using the TBM_SETRANGE message.', 9 | }, 10 | { 11 | label: '$TBS_BOTH', 12 | documentation: 'Places ticks on both sides of the trackbar.', 13 | }, 14 | { 15 | label: '$TBS_BOTTOM', 16 | documentation: 'Places ticks on the bottom of a horizontal trackbar.', 17 | }, 18 | { 19 | label: '$TBS_HORZ', 20 | documentation: 'Specifies a horizontal trackbar. This is the default.', 21 | }, 22 | { 23 | label: '$TBS_VERT', 24 | documentation: 'Places ticks on the left side of a vertical trackbar.', 25 | }, 26 | { 27 | label: '$TBS_NOTHUMB', 28 | documentation: 'Specifies that the trackbar has no slider.', 29 | }, 30 | { 31 | label: '$TBS_NOTICKS', 32 | documentation: 'Specifies that no ticks are placed on the trackbar.', 33 | }, 34 | { 35 | label: '$TBS_LEFT', 36 | documentation: 'Places ticks on the left side of a vertical trackbar.', 37 | }, 38 | { 39 | label: '$TBS_RIGHT', 40 | documentation: 'Places ticks on the right side of a vertical trackbar.', 41 | }, 42 | { 43 | label: '$TBS_TOP', 44 | documentation: 'Places ticks on the top of a horizontal trackbar.', 45 | }, 46 | ]; 47 | 48 | export default fillCompletions( 49 | items, 50 | CompletionItemKind.Constant, 51 | 'Slider Style Constant', 52 | 'SliderConstants.au3', 53 | ); 54 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | /** @type {import('webpack').Configuration} */ 4 | const config = { 5 | target: 'node', 6 | mode: process.env.NODE_ENV === 'production' ? 'production' : 'development', 7 | entry: './src/extension.js', 8 | output: { 9 | path: path.resolve(__dirname, 'dist'), 10 | filename: 'extension.js', 11 | library: { type: 'commonjs2' }, 12 | devtoolModuleFilenameTemplate: '../[resource-path]', 13 | clean: true, 14 | }, 15 | // Use inline source maps in development (best for VS Code extension debugging), 16 | // and external source maps in production. 17 | devtool: process.env.NODE_ENV === 'production' ? 'source-map' : 'inline-source-map', 18 | externals: { 19 | vscode: 'commonjs vscode', 20 | 'jsonc-parser': 'commonjs jsonc-parser', 21 | }, 22 | resolve: { 23 | extensions: ['.ts', '.js', '.json'], 24 | mainFields: ['main', 'module'], 25 | }, 26 | module: { 27 | rules: [ 28 | { 29 | test: /.(js|ts)$/, 30 | exclude: /node_modules/, 31 | use: { 32 | loader: 'babel-loader', 33 | options: { 34 | configFile: path.resolve(__dirname, 'babel.config.json'), 35 | cacheDirectory: true, 36 | cacheCompression: false, 37 | }, 38 | }, 39 | }, 40 | ], 41 | }, 42 | optimization: { 43 | minimize: process.env.NODE_ENV === 'production', 44 | usedExports: true, 45 | sideEffects: false, 46 | splitChunks: false, 47 | concatenateModules: true, 48 | }, 49 | performance: { hints: false }, 50 | stats: { errorDetails: true, colors: true }, 51 | infrastructureLogging: { level: 'warn' }, 52 | }; 53 | 54 | module.exports = config; 55 | -------------------------------------------------------------------------------- /src/completions/constants_listbox.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { fillCompletions } from '../util'; 3 | 4 | const items = [ 5 | { 6 | label: '$LBS_DISABLENOSCROLL', 7 | documentation: 8 | 'Shows a disabled vertical scroll bar for the list box when the box does not contain enough items to scroll. If you do not specify this style, the scroll bar is hidden when the list box does not contain enough items.', 9 | }, 10 | { 11 | label: '$LBS_NOINTEGRALHEIGHT', 12 | documentation: 13 | 'Specifies that the list box will be exactly the size specified by the application when it created the list box.', 14 | }, 15 | { 16 | label: '$LBS_NOSEL', 17 | documentation: 'Specifies that the user can view list box strings but cannot select them.', 18 | }, 19 | { 20 | label: '$LBS_NOTIFY', 21 | documentation: 22 | 'Notifies the parent window when the user taps or double-taps a string in the list box.', 23 | }, 24 | { 25 | label: '$LBS_SORT', 26 | documentation: 'Sorts strings in the list box alphabetically.', 27 | }, 28 | { 29 | label: '$LBS_STANDARD', 30 | documentation: 31 | 'Sorts strings in the list box alphabetically. The parent window receives an input message when the user taps or double-taps a string. The list box has borders on all sides. (LBS_NOTIFY | LBS_SORT | WS_VSCROLL | WS_BORDER)', 32 | }, 33 | { 34 | label: '$LBS_USETABSTOPS', 35 | documentation: 36 | 'Enables a list box to recognize and expand tab characters when drawing its strings. The default tab positions are 32 dialog box units. A dialog box unit is equal to one-fourth of the current dialog box base-width unit.', 37 | }, 38 | ]; 39 | 40 | export default fillCompletions( 41 | items, 42 | CompletionItemKind.Constant, 43 | 'List Style Constant', 44 | 'ListBoxConstants.au3', 45 | ); 46 | -------------------------------------------------------------------------------- /src/ai_showMessage.js: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { performance } from 'node:perf_hooks'; 3 | 4 | let lastHide = 0; 5 | // accepts new option parameter in second argument: timeout 6 | const initMessage = type => { 7 | const timers = {}; 8 | const func = (...args) => { 9 | let timeout; 10 | const [message, options] = args; 11 | if (options && options instanceof Object && !(options instanceof Array)) { 12 | ({ timeout } = options); 13 | // not sure if we need to bother sanitize options object or not, seems to work as is 14 | // delete options.timeout; 15 | // if (!options.keys().length) 16 | // args.splice(1,1); 17 | } 18 | const clearTimeoutEx = () => { 19 | clearTimeout(timers[message]); 20 | delete timers[message]; 21 | }; 22 | clearTimeoutEx(); 23 | let isHidden = false; 24 | const callback = () => { 25 | clearTimeoutEx(); 26 | // https://github.com/microsoft/vscode/issues/153693 27 | for ( 28 | let i = 0; 29 | i < 4; 30 | i += 1 // showing rapidly 4 messages hides the message...an exploit? 31 | ) 32 | window[type].apply(window[type], args); 33 | 34 | isHidden = true; 35 | lastHide = performance.now(); 36 | }; 37 | timers[message] = timeout !== undefined && setTimeout(callback, timeout); 38 | // vscode doesn't display new message if previous message was forcibly hidden less then 1 sec ago 39 | const messageTimeout = 900 - (performance.now() - lastHide); 40 | return { 41 | get isHidden() { 42 | return isHidden; 43 | }, 44 | hide: callback, 45 | message: new Promise(resolve => 46 | setTimeout( 47 | () => resolve(window[type].apply(window[type], args).finally(clearTimeoutEx)), 48 | messageTimeout, 49 | ), 50 | ), 51 | }; 52 | }; 53 | return func; 54 | }; 55 | export const showInformationMessage = initMessage('showInformationMessage'); 56 | export const showErrorMessage = initMessage('showErrorMessage'); 57 | export const showWarningMessage = initMessage('showWarningMessage'); 58 | export const messages = { error: {}, info: {} }; 59 | -------------------------------------------------------------------------------- /src/commands/functionTraceAdd.js: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import searchAndReplace from './commandUtils'; 3 | 4 | /** 5 | * Adds a trace statement to a given match in a text. 6 | * If the match already contains a trace statement or includes the comment '; FunctionTraceSkip', 7 | * the match is returned as is. 8 | * Otherwise, a new trace statement is constructed based on the match and appended to it. 9 | * @param {string} match - The matched string from the regular expression. 10 | * @param {string} p1 - The first capturing group from the regular expression. 11 | * @param {string} p2 - The second capturing group from the regular expression. 12 | * @param {string} functionName - The third capturing group from the regular expression. 13 | * @returns {string} - The modified match with the added trace statement. 14 | */ 15 | function appendTrace(match, p1, p2, functionName) { 16 | // Check for skipping comments 17 | if (p1.includes('; FunctionTraceSkip')) { 18 | return match; 19 | } 20 | 21 | const traceStatement = `ConsoleWrite('@@ (' & (@ScriptLineNumber - 1) & ') :(' & @MIN & ':' & @SEC & ') ${functionName}()' & @CR) \t;### Trace Function'`; 22 | 23 | return `${match}\r\t${traceStatement}`; 24 | } 25 | 26 | async function functionTraceAdd() { 27 | const sPattern = /()(\bfunc\b\s+([^)\s]+)\(.*\))/gi; 28 | 29 | // Remove existing trace statements 30 | const traceStatementPattern = /\s*ConsoleWrite\('@@ \(.+;### Trace Function'/; 31 | await searchAndReplace(traceStatementPattern); 32 | 33 | // Perform replacement using regular expressions 34 | const editor = vscode.window.activeTextEditor; 35 | 36 | if (!editor) { 37 | vscode.window.showErrorMessage('No active editor'); 38 | return; 39 | } 40 | 41 | const { document } = editor; 42 | const text = document.getText(); 43 | 44 | const updatedText = text.replaceAll(sPattern, appendTrace); 45 | 46 | if (!updatedText) { 47 | return; 48 | } 49 | 50 | await editor.edit(editBuilder => { 51 | const fullRange = new vscode.Range(document.positionAt(0), document.positionAt(text.length)); 52 | editBuilder.replace(fullRange, updatedText); 53 | }); 54 | } 55 | 56 | export { functionTraceAdd as default }; 57 | -------------------------------------------------------------------------------- /src/completions/constantsInet.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { completionToHover, fillCompletions } from '../util'; 3 | 4 | const InetConstants = [ 5 | { 6 | label: '$INET_LOCALCACHE', 7 | documentation: '(0) = Get the file from local cache if available (default).', 8 | }, 9 | { 10 | label: '$INET_FORCERELOAD', 11 | documentation: '(1) = Forces a reload from the remote site.', 12 | }, 13 | { 14 | label: '$INET_IGNORESSL', 15 | documentation: '(2) = Ignore all SSL errors (with HTTPS connections).', 16 | }, 17 | { 18 | label: '$INET_ASCIITRANSFER', 19 | documentation: 20 | 'Use ASCII when transferring files with the FTP protocol (Can not be combined with flag `$INET_BINARYTRANSFER` (8)).', 21 | }, 22 | { 23 | label: '$INET_BINARYTRANSFER', 24 | documentation: 25 | '(8) = Use BINARY when transferring files with the FTP protocol (Can not be combined with flag `$INET_ASCIITRANSFER` (4)). This is the default transfer mode if none are provided.', 26 | }, 27 | { 28 | label: '$INET_FORCEBYPASS', 29 | documentation: '(16) = By-pass forcing the connection online (See remarks).', 30 | }, 31 | { 32 | label: '$INET_DOWNLOADWAIT', 33 | documentation: '(0) = Wait until the download is complete before continuing (default).', 34 | }, 35 | { 36 | label: '$INET_DOWNLOADBACKGROUND', 37 | documentation: '(1) = return immediately and download in the background (see remarks).', 38 | }, 39 | { 40 | label: '$INET_DOWNLOADREAD', 41 | documentation: '0', 42 | }, 43 | { 44 | label: '$INET_DOWNLOADSIZE', 45 | documentation: '1', 46 | }, 47 | { 48 | label: '$INET_DOWNLOADCOMPLETE', 49 | documentation: '2', 50 | }, 51 | { 52 | label: '$INET_DOWNLOADSUCCESS', 53 | documentation: '3', 54 | }, 55 | { 56 | label: '$INET_DOWNLOADERROR', 57 | documentation: '4', 58 | }, 59 | { 60 | label: '$INET_DOWNLOADEXTENDED', 61 | documentation: '5', 62 | }, 63 | ]; 64 | 65 | const items = fillCompletions( 66 | InetConstants, 67 | CompletionItemKind.Constant, 68 | 'InetGet Constant', 69 | 'InetConstants.au3', 70 | ); 71 | const hovers = completionToHover(items); 72 | 73 | export { items as default, hovers }; 74 | -------------------------------------------------------------------------------- /src/signatures/udf_math.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { 3 | br, 4 | valueFirstHeader as header, 5 | opt, 6 | signatureToCompletion, 7 | signatureToHover, 8 | } from '../util'; 9 | 10 | const include = '(Requires: `#include `)'; 11 | 12 | const signatures = { 13 | _Degree: { 14 | documentation: 'Converts radians to degrees', 15 | label: '_Degree ( $iRadians )', 16 | params: [ 17 | { 18 | label: '$iRadians', 19 | documentation: 'Radians to be converted into degrees.', 20 | }, 21 | ], 22 | }, 23 | _MathCheckDiv: { 24 | documentation: 'Checks if first number is divisible by the second number', 25 | label: '_MathCheckDiv ( $iNum1 [, $iNum2 = 2] )', 26 | params: [ 27 | { 28 | label: '$iNum1', 29 | documentation: 'Integer value to check', 30 | }, 31 | { 32 | label: '$iNum2', 33 | documentation: '**[optional]** Integer value to divide by (default = 2)', 34 | }, 35 | ], 36 | }, 37 | _Max: { 38 | documentation: 'Evaluates which of the two numbers is higher', 39 | label: '_Max ( $iNum1, $iNum2 )', 40 | params: [ 41 | { 42 | label: '$iNum1', 43 | documentation: 'First number.', 44 | }, 45 | { 46 | label: '$iNum2', 47 | documentation: 'Second number.', 48 | }, 49 | ], 50 | }, 51 | _Min: { 52 | documentation: 'Evaluates which of the two numbers is lower', 53 | label: '_Min ( $iNum1, $iNum2 )', 54 | params: [ 55 | { 56 | label: '$iNum1', 57 | documentation: 'First number.', 58 | }, 59 | { 60 | label: '$iNum2', 61 | documentation: 'Second number.', 62 | }, 63 | ], 64 | }, 65 | _Radian: { 66 | documentation: 'Converts degrees to radians', 67 | label: '_Radian ( $iDegrees )', 68 | params: [ 69 | { 70 | label: '$iDegrees', 71 | documentation: 'Degrees to be converted into radians.', 72 | }, 73 | ], 74 | }, 75 | }; 76 | 77 | const hovers = signatureToHover(signatures); 78 | const completions = signatureToCompletion(signatures, CompletionItemKind.Function, include); 79 | 80 | export { signatures as default, hovers, completions }; 81 | -------------------------------------------------------------------------------- /scripts/quick-flaky-check.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable no-console */ 3 | 4 | /** 5 | * Quick Flaky Test Check Script 6 | * Lightweight check for obvious flaky behavior patterns 7 | */ 8 | 9 | const { execSync } = require('child_process'); 10 | 11 | class QuickFlakyCheck { 12 | constructor() { 13 | this.runs = 3; // Quick check with only 3 runs 14 | this.timeout = 15000; // Shorter timeout for quick checks 15 | } 16 | 17 | log(message) { 18 | console.log(`[QuickFlaky] ${message}`); 19 | } 20 | 21 | async runQuickCheck() { 22 | this.log('Running quick flaky test check...'); 23 | 24 | const results = []; 25 | 26 | for (let i = 1; i <= this.runs; i++) { 27 | try { 28 | const startTime = Date.now(); 29 | execSync(`npx jest --testTimeout=${this.timeout} --runInBand --silent --no-coverage`, { 30 | encoding: 'utf8', 31 | timeout: this.timeout + 2000, 32 | }); 33 | const endTime = Date.now(); 34 | 35 | results.push({ 36 | run: i, 37 | status: 'PASSED', 38 | executionTime: endTime - startTime, 39 | }); 40 | } catch (error) { 41 | results.push({ 42 | run: i, 43 | status: 'FAILED', 44 | error: error.message, 45 | }); 46 | } 47 | } 48 | 49 | const passedRuns = results.filter(r => r.status === 'PASSED').length; 50 | const failedRuns = results.filter(r => r.status === 'FAILED').length; 51 | 52 | if (failedRuns > 0 && passedRuns > 0) { 53 | this.log(`⚠️ Potential flaky behavior detected: ${passedRuns}/${this.runs} runs passed`); 54 | this.log('Consider running full flaky test detection before committing'); 55 | return false; 56 | } else if (failedRuns > 0) { 57 | this.log('❌ All runs failed - this appears to be a consistent test failure'); 58 | return false; 59 | } else { 60 | this.log('✅ No obvious flaky behavior detected in quick check'); 61 | return true; 62 | } 63 | } 64 | } 65 | 66 | // CLI execution 67 | if (require.main === module) { 68 | const checker = new QuickFlakyCheck(); 69 | checker 70 | .runQuickCheck() 71 | .then(success => { 72 | process.exit(success ? 0 : 1); 73 | }) 74 | .catch(error => { 75 | console.error('Quick flaky check failed:', error); 76 | process.exit(1); 77 | }); 78 | } 79 | 80 | module.exports = { QuickFlakyCheck }; 81 | -------------------------------------------------------------------------------- /src/completions/constants_datetime.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { fillCompletions } from '../util'; 3 | 4 | const items = [ 5 | { 6 | label: '$DTS_UPDOWN', 7 | documentation: 8 | 'Places an up-down control to the right of a DTP control to modify time values. This style can be used instead of the drop-down month calendar, which is the default style. ', 9 | detail: 'Date Style Constant', 10 | }, 11 | { 12 | label: '$DTS_SHOWNONE', 13 | documentation: 'Enables the control to accept "no date" as a valid selection state.', 14 | detail: 'Date Style Constant', 15 | }, 16 | { 17 | label: '$DTS_LONGDATEFORMAT', 18 | documentation: 19 | 'Displays the date in long format. The default format string for this style is defined by LOCALE_SLONGDATEFORMAT, which produces output like "Friday, April 19, 1998."', 20 | detail: 'Date Style Constant', 21 | }, 22 | { 23 | label: '$DTS_TIMEFORMAT', 24 | documentation: 25 | 'Displays the time. The default format string for this style is defined by LOCALE_STIMEFORMAT, which produces output like "5:31:42 PM."', 26 | detail: 'Date Style Constant', 27 | }, 28 | { 29 | label: '$DTS_RIGHTALIGN', 30 | documentation: 31 | 'The drop-down month calendar will be right-aligned with the control instead of left-aligned, which is the default.', 32 | detail: 'Date Style Constant', 33 | }, 34 | { 35 | label: '$DTS_SHORTDATEFORMAT', 36 | documentation: 37 | 'Displays the date in short format. The default format string for this style is defined by LOCALE_SSHORTDATE, which produces output like "4/19/96".', 38 | detail: 'Date Style Constant', 39 | }, 40 | { 41 | label: '$MCS_NOTODAY', 42 | documentation: 43 | 'The month calendar control will not display the "today" date at the bottom of the control.', 44 | detail: 'MonthCal Style Constant', 45 | }, 46 | { 47 | label: '$MCS_NOTODAYCIRCLE', 48 | documentation: 'The month calendar control will not circle the "today" date.', 49 | detail: 'MonthCal Style Constant', 50 | }, 51 | { 52 | label: '$MCS_WEEKNUMBERS', 53 | documentation: 54 | 'The month calendar control will display week numbers (1-52) to the left of each row of days. Week 1 is defined as the first week that contains at least four days.', 55 | detail: 'MonthCal Style Constant', 56 | }, 57 | ]; 58 | 59 | export default fillCompletions(items, CompletionItemKind.Constant, '', 'DateTimeConstants.au3'); 60 | -------------------------------------------------------------------------------- /src/command_constants.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Constants extracted from ai_commands.js to improve maintainability and readability. 3 | * This module contains magic numbers and hardcoded values used throughout the application. 4 | */ 5 | 6 | /** 7 | * Delay for incomplete line processing in milliseconds. 8 | * Used to buffer incomplete output lines before displaying them. 9 | * @type {number} 10 | */ 11 | const HOTKEY_LINE_DELAY_MS = 100; 12 | 13 | /** 14 | * Debounce time for keybinding file reads in milliseconds. 15 | * Prevents excessive file system operations when keybindings are updated. 16 | * @type {number} 17 | */ 18 | const KEYBINDING_DEBOUNCE_MS = 200; 19 | 20 | /** 21 | * Timeout for settings initialization in milliseconds. 22 | * Allows time for settings to be properly loaded and applied. 23 | * @type {number} 24 | */ 25 | const SETTINGS_TIMEOUT_MS = 2000; 26 | 27 | /** 28 | * Fallback timeout for hotkey reset in milliseconds. 29 | * Ensures hotkeys are reset even if the normal reset process fails. 30 | * @type {number} 31 | */ 32 | const HOTKEY_RESET_TIMEOUT_MS = 10000; 33 | 34 | /** 35 | * Duration for status bar messages in milliseconds. 36 | * Controls how long informational messages are displayed in the status bar. 37 | * @type {number} 38 | */ 39 | const STATUS_MESSAGE_DURATION_MS = 1500; 40 | 41 | /** 42 | * Timeout for error messages in milliseconds. 43 | * Determines how long error notifications remain visible to the user. 44 | * @type {number} 45 | */ 46 | const ERROR_MESSAGE_TIMEOUT_MS = 30000; 47 | 48 | /** 49 | * Timeout for kill script info messages in milliseconds. 50 | * Sets the duration for informational messages related to script termination. 51 | * @type {number} 52 | */ 53 | const KILL_SCRIPT_INFO_TIMEOUT_MS = 10000; 54 | 55 | /** 56 | * Unicode character for non-breaking space. 57 | * Used for spacing in output formatting to avoid unwanted line breaks. 58 | * @type {string} 59 | */ 60 | const NO_BREAK_SPACE = '\u00A0'; 61 | 62 | /** 63 | * Template for output channel naming. 64 | * Used to generate unique names for output channels based on publisher and extension name. 65 | * @type {string} 66 | */ 67 | const OUTPUT_NAME_TEMPLATE = 'extension-output-${publisher}.${name}-#'; 68 | 69 | module.exports = { 70 | HOTKEY_LINE_DELAY_MS, 71 | KEYBINDING_DEBOUNCE_MS, 72 | SETTINGS_TIMEOUT_MS, 73 | HOTKEY_RESET_TIMEOUT_MS, 74 | STATUS_MESSAGE_DURATION_MS, 75 | ERROR_MESSAGE_TIMEOUT_MS, 76 | KILL_SCRIPT_INFO_TIMEOUT_MS, 77 | NO_BREAK_SPACE, 78 | OUTPUT_NAME_TEMPLATE, 79 | }; 80 | -------------------------------------------------------------------------------- /src/completions/constants_combo.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { fillCompletions } from '../util'; 3 | 4 | const items = [ 5 | { 6 | label: '$CBS_AUTOHSCROLL', 7 | documentation: 8 | 'Automatically scrolls the text in an edit control to the right when the user types a character at the end of the line. If this style is not set, only text that fits within the rectangular boundary is enabled. ', 9 | }, 10 | { 11 | label: '$CBS_DISABLENOSCROLL', 12 | documentation: 13 | 'Shows a disabled vertical scroll bar in the list box when the box does not contain enough items to scroll. Without this style, the scroll bar is hidden when the list box does not contain enough items. ', 14 | }, 15 | { 16 | label: '$CBS_DROPDOWN', 17 | documentation: 18 | 'Displays only the edit control by default. The user can display the list box by selecting an icon next to the edit control. ', 19 | }, 20 | { 21 | label: '$CBS_DROPDOWNLIST', 22 | documentation: 23 | 'Displays a static text field that displays the current selection in the list box. ', 24 | }, 25 | { 26 | label: '$CBS_LOWERCASE', 27 | documentation: 28 | 'Converts to lowercase any uppercase characters that are typed into the edit control of a combo box. ', 29 | }, 30 | { 31 | label: '$CBS_NOINTEGRALHEIGHT', 32 | documentation: 33 | 'Specifies that the combo box will be exactly the size specified by the application when it created the combo box. Usually, Windows CE sizes a combo box so that it does not display partial items. ', 34 | }, 35 | { 36 | label: '$CBS_OEMCONVERT', 37 | documentation: 38 | 'Converts text typed in the combo box edit control from the Windows CE character set to the OEM character set and then back to the Windows CE set. This style is most useful for combo boxes that contain file names. It applies only to combo boxes created with the CBS_DROPDOWN style. ', 39 | }, 40 | { 41 | label: '$CBS_SIMPLE', 42 | documentation: 43 | 'Displays the list box at all times. The current selection in the list box is displayed in the edit control. ', 44 | }, 45 | { 46 | label: '$CBS_SORT', 47 | documentation: 'Sorts strings that are typed into the list box. ', 48 | }, 49 | { 50 | label: '$CBS_UPPERCASE', 51 | documentation: 52 | 'Converts to uppercase any lowercase characters that are typed into the edit control of a combo box. ', 53 | }, 54 | ]; 55 | 56 | export default fillCompletions( 57 | items, 58 | CompletionItemKind.Constant, 59 | 'Combo Style Constant', 60 | 'ComboConstants.au3', 61 | ); 62 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | const DEFAULT_UDFS = [ 2 | 'APIComConstants', 3 | 'APIConstants', 4 | 'APIDiagConstants', 5 | 'APIDlgConstants', 6 | 'APIErrorsConstants', 7 | 'APIFilesConstants', 8 | 'APIGdiConstants', 9 | 'APILocaleConstants', 10 | 'APIMiscConstants', 11 | 'APIProcConstants', 12 | 'APIRegConstants', 13 | 'APIResConstants', 14 | 'APIShellExConstants', 15 | 'APIShPathConstants', 16 | 'APISysConstants', 17 | 'APIThemeConstants', 18 | 'Array', 19 | 'AutoItConstants', 20 | 'AVIConstants', 21 | 'BorderConstants', 22 | 'ButtonConstants', 23 | 'Clipboard', 24 | 'Color', 25 | 'ColorConstants', 26 | 'ComboConstants', 27 | 'Constants', 28 | 'Crypt', 29 | 'Date', 30 | 'DateTimeConstants', 31 | 'Debug', 32 | 'DirConstants', 33 | 'EditConstants', 34 | 'EventLog', 35 | 'Excel', 36 | 'ExcelConstants', 37 | 'File', 38 | 'FileConstants', 39 | 'FontConstants', 40 | 'FrameConstants', 41 | 'FTPEx', 42 | 'GDIPlus', 43 | 'GDIPlusConstants', 44 | 'GuiAVI', 45 | 'GuiButton', 46 | 'GuiComboBox', 47 | 'GuiComboBoxEx', 48 | 'GUIConstants', 49 | 'GUIConstantsEx', 50 | 'GuiDateTimePicker', 51 | 'GuiEdit', 52 | 'GuiHeader', 53 | 'GuiImageList', 54 | 'GuiIPAddress', 55 | 'GuiListBox', 56 | 'GuiListView', 57 | 'GuiMenu', 58 | 'GuiMonthCal', 59 | 'GuiReBar', 60 | 'GuiRichEdit', 61 | 'GuiScrollBars', 62 | 'GuiSlider', 63 | 'GuiStatusBar', 64 | 'GuiTab', 65 | 'GuiToolbar', 66 | 'GuiToolTip', 67 | 'GuiTreeView', 68 | 'HeaderConstants', 69 | 'IE', 70 | 'ImageListConstants', 71 | 'Inet', 72 | 'InetConstants', 73 | 'IPAddressConstants', 74 | 'ListBoxConstants', 75 | 'ListViewConstants', 76 | 'Math', 77 | 'MathConstants', 78 | 'Memory', 79 | 'MemoryConstants', 80 | 'MenuConstants', 81 | 'Misc', 82 | 'MsgBoxConstants', 83 | 'NamedPipes', 84 | 'NetShare', 85 | 'NTSTATUSConstants', 86 | 'Process', 87 | 'ProcessConstants', 88 | 'ProgressConstants', 89 | 'RebarConstants', 90 | 'RichEditConstants', 91 | 'ScreenCapture', 92 | 'ScrollBarConstants', 93 | 'ScrollBarsConstants', 94 | 'Security', 95 | 'SecurityConstants', 96 | 'SendMessage', 97 | 'SliderConstants', 98 | 'Sound', 99 | 'SQLite', 100 | 'SQLite.dll', 101 | 'StaticConstants', 102 | 'StatusBarConstants', 103 | 'String', 104 | 'StringConstants', 105 | 'StructureConstants', 106 | 'TabConstants', 107 | 'Timers', 108 | 'ToolbarConstants', 109 | 'ToolTipConstants', 110 | 'TrayConstants', 111 | 'TreeViewConstants', 112 | 'UDFGlobalID', 113 | 'UpDownConstants', 114 | 'Visa', 115 | 'WinAPI', 116 | 'WinAPICom', 117 | 'WinAPIConstants', 118 | 'WinAPIDiag', 119 | 'WinAPIDlg', 120 | 'WinAPIError', 121 | 'WinAPIEx', 122 | 'WinAPIFiles', 123 | 'WinAPIGdi', 124 | 'WinAPIInternals', 125 | 'WinAPIlangConstants', 126 | 'WinAPILocale', 127 | 'WinAPIMisc', 128 | 'WinAPIProc', 129 | 'WinAPIReg', 130 | 'WinAPIRes', 131 | 'WinAPIShellEx', 132 | 'WinAPIShPath', 133 | 'WinAPISys', 134 | 'WinAPIsysinfoConstants', 135 | 'WinAPITheme', 136 | 'WinAPIvkeysConstants', 137 | 'WindowsConstants', 138 | 'WinNet', 139 | 'Word', 140 | 'WordConstants', 141 | ]; 142 | 143 | export default DEFAULT_UDFS; 144 | -------------------------------------------------------------------------------- /src/signatures/udf_sendmessage.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { 3 | br, 4 | valueFirstHeader as header, 5 | opt, 6 | signatureToCompletion, 7 | signatureToHover, 8 | } from '../util'; 9 | 10 | const include = '(Requires: `#include `)'; 11 | 12 | const signatures = { 13 | _SendMessage: { 14 | documentation: 'Wrapper for commonly used DLL Call', 15 | label: 16 | '_SendMessage ( $hWnd, $iMsg [, $wParam = 0 [, $lParam = 0 [, $iReturn = 0 [, $wParamType = "wparam" [, $lParamType = "lparam" [, $sReturnType = "lresult"]]]]]] )', 17 | params: [ 18 | { 19 | label: '$hWnd', 20 | documentation: 'Window/control handle', 21 | }, 22 | { 23 | label: '$iMsg', 24 | documentation: 'Message to send to control (number)', 25 | }, 26 | { 27 | label: '$wParam', 28 | documentation: '**[optional]** Specifies additional message-specific information', 29 | }, 30 | { 31 | label: '$lParam', 32 | documentation: '**[optional]** Specifies additional message-specific information', 33 | }, 34 | { 35 | label: '$iReturn', 36 | documentation: 37 | '**[optional]** What to return:    0 - Return value from DLL call    1 - $ihWnd    2 - $iMsg    3 - $wParam    4 - $lParam    4 - array same as DllCall()', 38 | }, 39 | { 40 | label: '$wParamType', 41 | documentation: '**[optional]** See DllCall in Related', 42 | }, 43 | { 44 | label: '$lParamType', 45 | documentation: '**[optional]** See DllCall in Related', 46 | }, 47 | { 48 | label: '$sReturnType', 49 | documentation: '**[optional]** See DllCall in Related', 50 | }, 51 | ], 52 | }, 53 | _SendMessageA: { 54 | documentation: 'Send a Message to a Window/Control (Force Ansi Call)', 55 | label: 56 | '_SendMessageA ( $hWnd, $iMsg [, $wParam = 0 [, $lParam = 0 [, $iReturn = 0 [, $wParamType = "wparam" [, $lParamType = "lparam" [, $sReturnType = "lresult"]]]]]] )', 57 | params: [ 58 | { 59 | label: '$hWnd', 60 | documentation: 'Window/control handle', 61 | }, 62 | { 63 | label: '$iMsg', 64 | documentation: 'Message to send to control (number)', 65 | }, 66 | { 67 | label: '$wParam', 68 | documentation: '**[optional]** Specifies additional message-specific information', 69 | }, 70 | { 71 | label: '$lParam', 72 | documentation: '**[optional]** Specifies additional message-specific information', 73 | }, 74 | { 75 | label: '$iReturn', 76 | documentation: 77 | '**[optional]** What to return:    0 - Return value from DLL call    1 - $ihWnd    2 - $iMsg    3 - $wParam    4 - $lParam    4 - array same as DllCall', 78 | }, 79 | { 80 | label: '$wParamType', 81 | documentation: '**[optional]** See DllCall in Related', 82 | }, 83 | { 84 | label: '$lParamType', 85 | documentation: '**[optional]** See DllCall in Related', 86 | }, 87 | { 88 | label: '$sReturnType', 89 | documentation: '**[optional]** See DllCall in Related', 90 | }, 91 | ], 92 | }, 93 | }; 94 | 95 | const hovers = signatureToHover(signatures); 96 | const completions = signatureToCompletion(signatures, CompletionItemKind.Function, include); 97 | 98 | export { signatures as default, hovers, completions }; 99 | -------------------------------------------------------------------------------- /src/signatures/udf_color.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { 3 | br, 4 | valueFirstHeader as header, 5 | opt, 6 | signatureToCompletion, 7 | signatureToHover, 8 | } from '../util'; 9 | 10 | const include = '(Requires: `#include `)'; 11 | 12 | const signatures = { 13 | _ColorConvertHSLtoRGB: { 14 | documentation: 'Converts HSL to RGB', 15 | label: '_ColorConvertHSLtoRGB ( $aArray )', 16 | params: [ 17 | { 18 | label: '$aArray', 19 | documentation: 'An array containing HSL values in their respective positions', 20 | }, 21 | ], 22 | }, 23 | _ColorConvertRGBtoHSL: { 24 | documentation: 'Converts RGB to HSL', 25 | label: '_ColorConvertRGBtoHSL ( $aArray )', 26 | params: [ 27 | { 28 | label: '$aArray', 29 | documentation: 'An array containing RGB values in their respective positions', 30 | }, 31 | ], 32 | }, 33 | _ColorGetBlue: { 34 | documentation: 'Returns the blue component of a given color', 35 | label: '_ColorGetBlue ( $iColor )', 36 | params: [ 37 | { 38 | label: '$iColor', 39 | documentation: 'The RGB color to work with (0x00RRGGBB).', 40 | }, 41 | ], 42 | }, 43 | _ColorGetCOLORREF: { 44 | documentation: 'Returns the COLORREF color', 45 | label: '_ColorGetCOLORREF ( $iColor )', 46 | params: [ 47 | { 48 | label: '$iColor', 49 | documentation: 'the COLORREF color to work with (0x00BBGGRR)', 50 | }, 51 | ], 52 | }, 53 | _ColorGetGreen: { 54 | documentation: 'Returns the green component of a given color', 55 | label: '_ColorGetGreen ( $iColor )', 56 | params: [ 57 | { 58 | label: '$iColor', 59 | documentation: 'The RGB color to work with (0x00RRGGBB).', 60 | }, 61 | ], 62 | }, 63 | _ColorGetRed: { 64 | documentation: 'Returns the red component of a given color', 65 | label: '_ColorGetRed ( $iColor )', 66 | params: [ 67 | { 68 | label: '$iColor', 69 | documentation: 'The RGB color to work with (0x00RRGGBB).', 70 | }, 71 | ], 72 | }, 73 | _ColorGetRGB: { 74 | documentation: 'Returns an array containing RGB values in their respective positions', 75 | label: '_ColorGetRGB ( $iColor )', 76 | params: [ 77 | { 78 | label: '$iColor', 79 | documentation: 'The RGB color to work with (0x00RRGGBB).', 80 | }, 81 | ], 82 | }, 83 | _ColorSetCOLORREF: { 84 | documentation: 'Returns the COLORREF color', 85 | label: '_ColorSetCOLORREF ( $aColor )', 86 | params: [ 87 | { 88 | label: '$aColor', 89 | documentation: 90 | 'an array of values in the range 0-255: \n[0] Red component color \n[1] Green component color \n[2] Blue component color', 91 | }, 92 | ], 93 | }, 94 | _ColorSetRGB: { 95 | documentation: 'Returns the RGB color', 96 | label: '_ColorSetRGB ( $aColor )', 97 | params: [ 98 | { 99 | label: '$aColor', 100 | documentation: 101 | 'an array of values in the range 0-255: \n[0] Red component color \n[1] Green component color \n[2] Blue component color', 102 | }, 103 | ], 104 | }, 105 | }; 106 | 107 | const hovers = signatureToHover(signatures); 108 | const completions = signatureToCompletion(signatures, CompletionItemKind.Function, include); 109 | 110 | export { signatures as default, hovers, completions }; 111 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import prettierConfig from 'eslint-config-prettier'; 3 | import importPlugin from 'eslint-plugin-import'; 4 | import prettier from 'eslint-plugin-prettier'; 5 | import globals from 'globals'; 6 | 7 | export default [ 8 | js.configs.recommended, 9 | prettierConfig, 10 | { 11 | files: ['**/*.js', '**/*.mjs', '**/*.cjs'], 12 | languageOptions: { 13 | ecmaVersion: 2024, 14 | sourceType: 'module', 15 | globals: { 16 | ...globals.node, 17 | ...globals.jest, 18 | }, 19 | }, 20 | plugins: { 21 | prettier, 22 | import: importPlugin, 23 | }, 24 | rules: { 25 | // Prettier 26 | 'prettier/prettier': ['error', { endOfLine: 'auto' }], 27 | 28 | // Code style 29 | 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], 30 | 'prefer-const': 'error', 31 | 'no-var': 'error', 32 | 'object-shorthand': 'error', 33 | 'prefer-arrow-callback': 'error', 34 | 35 | // Import rules 36 | 'import/no-unresolved': 'error', 37 | 'import/named': 'error', 38 | 'import/default': 'error', 39 | 'import/namespace': 'error', 40 | 'import/no-absolute-path': 'error', 41 | 'import/no-dynamic-require': 'warn', 42 | 'import/no-self-import': 'error', 43 | 'import/no-cycle': 'error', 44 | 'import/no-useless-path-segments': 'error', 45 | 46 | // Variables 47 | 'no-shadow': 'error', 48 | 'no-duplicate-imports': 'error', 49 | 'no-unused-expressions': 'error', 50 | 51 | // Async/Await 52 | 'no-return-await': 'error', 53 | 'require-await': 'error', 54 | 55 | // Best practices 56 | 'no-new': 'warn', 57 | 'no-alert': 'warn', 58 | 'no-eq-null': 'error', 59 | eqeqeq: ['error', 'always'], 60 | 'no-empty': ['error', { allowEmptyCatch: true }], 61 | 'no-empty-function': ['error', { allow: ['arrowFunctions', 'functions', 'methods'] }], 62 | 'no-implicit-globals': 'error', 63 | 'no-invalid-this': 'error', 64 | 'no-loop-func': 'error', 65 | 'no-magic-numbers': ['warn', { ignore: [0, 1, -1], ignoreArrayIndexes: true }], 66 | 'no-multi-assign': 'error', 67 | 'no-nested-ternary': 'error', 68 | 'no-return-assign': 'error', 69 | 'no-throw-literal': 'error', 70 | 'no-unmodified-loop-condition': 'error', 71 | 'no-unneeded-ternary': 'error', 72 | 'no-useless-call': 'error', 73 | 'no-useless-concat': 'error', 74 | 'no-useless-return': 'error', 75 | 76 | // ES6+ features 77 | 'prefer-destructuring': ['error', { object: true, array: false }], 78 | 'prefer-object-spread': 'error', 79 | 'prefer-promise-reject-errors': 'error', 80 | 'prefer-regex-literals': 'error', 81 | 'prefer-rest-params': 'error', 82 | 'prefer-spread': 'error', 83 | 84 | // Strict mode 85 | strict: ['error', 'global'], 86 | 87 | // Symbols 88 | 'symbol-description': 'error', 89 | 90 | // Variables declaration 91 | 'vars-on-top': 'error', 92 | 93 | // Comparisons 94 | yoda: 'error', 95 | }, 96 | settings: { 97 | 'import/core-modules': ['vscode'], 98 | 'import/resolver': { 99 | node: { 100 | extensions: ['.js', '.mjs', '.cjs'], 101 | }, 102 | }, 103 | }, 104 | }, 105 | { 106 | ignores: ['dist/', 'node_modules/', '*.min.js'], 107 | }, 108 | ]; 109 | -------------------------------------------------------------------------------- /src/signatures/udf_timers.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { 3 | br, 4 | valueFirstHeader as header, 5 | opt, 6 | signatureToCompletion, 7 | signatureToHover, 8 | } from '../util'; 9 | 10 | const include = '(Requires: `#include `)'; 11 | 12 | const signatures = { 13 | _Timer_Diff: { 14 | documentation: 'Returns the difference in time from a previous call to _Timer_Init', 15 | label: '_Timer_Diff ( $iTimeStamp )', 16 | params: [ 17 | { 18 | label: '$iTimeStamp', 19 | documentation: 'Timestamp returned from a previous call to _Timer_Init().', 20 | }, 21 | ], 22 | }, 23 | _Timer_GetIdleTime: { 24 | documentation: 'Returns the number of ticks since last user activity (i.e. KYBD/Mouse)', 25 | label: '_Timer_GetIdleTime ( )', 26 | params: [], 27 | }, 28 | _Timer_GetTimerID: { 29 | documentation: 'Returns the Timer ID from $wParam', 30 | label: '_Timer_GetTimerID ( $wParam )', 31 | params: [ 32 | { 33 | label: '$wParam', 34 | documentation: 'Specifies the timer identifier event.', 35 | }, 36 | ], 37 | }, 38 | _Timer_Init: { 39 | documentation: 'Returns a timestamp (in milliseconds)', 40 | label: '_Timer_Init ( )', 41 | params: [], 42 | }, 43 | _Timer_KillAllTimers: { 44 | documentation: 'Destroys all the timers', 45 | label: '_Timer_KillAllTimers ( $hWnd )', 46 | params: [ 47 | { 48 | label: '$hWnd', 49 | documentation: 50 | 'Handle to the window associated with the timers.This value must be the same as the $hWnd value passed to the _Timer_SetTimer() function that created the timer', 51 | }, 52 | ], 53 | }, 54 | _Timer_KillTimer: { 55 | documentation: 'Destroys the specified timer', 56 | label: '_Timer_KillTimer ( $hWnd, $iTimerID )', 57 | params: [ 58 | { 59 | label: '$hWnd', 60 | documentation: 61 | 'Handle to the window associated with the specified timer.This value must be the same as the $hWnd value passed to the _Timer_SetTimer() function that created the timer', 62 | }, 63 | { 64 | label: '$iTimerID', 65 | documentation: 'Specifies the timer to be destroyed', 66 | }, 67 | ], 68 | }, 69 | _Timer_SetTimer: { 70 | documentation: 'Creates a timer with the specified time-out value', 71 | label: '_Timer_SetTimer ( $hWnd [, $iElapse = 250 [, $sTimerFunc = "" [, $iTimerID = -1]]] )', 72 | params: [ 73 | { 74 | label: '$hWnd', 75 | documentation: 76 | 'Handle to the window to be associated with the timer.This window must be owned by the calling thread', 77 | }, 78 | { 79 | label: '$iElapse', 80 | documentation: '**[optional]** Specifies the time-out value, in milliseconds', 81 | }, 82 | { 83 | label: '$sTimerFunc', 84 | documentation: 85 | '**[optional]** Function name to be notified when the time-out value elapses', 86 | }, 87 | { 88 | label: '$iTimerID', 89 | documentation: 90 | '**[optional]** Specifies a timer identifier.If $iTimerID = -1 then a new timer is createdIf $iTimerID matches an existing timer then the timer is replacedIf $iTimerID = -1 and $sTimerFunc = "" then timer will use WM_TIMER events', 91 | }, 92 | ], 93 | }, 94 | }; 95 | 96 | const hovers = signatureToHover(signatures); 97 | const completions = signatureToCompletion(signatures, CompletionItemKind.Function, include); 98 | 99 | export { signatures as default, hovers, completions }; 100 | -------------------------------------------------------------------------------- /src/utils/pathValidation.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | /** 5 | * Validates that a file path is safe and doesn't contain path traversal attempts. 6 | * This prevents malicious paths like "../../sensitive/file" from being executed. 7 | * 8 | * @param {string} filePath - The file path to validate 9 | * @param {string} [workspaceRoot] - Optional workspace root to ensure file is within workspace 10 | * @returns {{valid: boolean, normalized: string, error?: string}} Validation result 11 | */ 12 | function validateFilePath(filePath, workspaceRoot = null) { 13 | if (!filePath || typeof filePath !== 'string') { 14 | return { 15 | valid: false, 16 | normalized: '', 17 | error: 'Invalid file path: path is empty or not a string', 18 | }; 19 | } 20 | 21 | // Normalize the path to resolve any .. or . segments 22 | const normalized = path.normalize(filePath); 23 | 24 | // Check for null bytes (common in path traversal attacks) 25 | if (normalized.includes('\0')) { 26 | return { valid: false, normalized, error: 'Invalid file path: contains null bytes' }; 27 | } 28 | 29 | // If workspace root is provided, ensure the file is within the workspace 30 | if (workspaceRoot) { 31 | const normalizedRoot = path.normalize(workspaceRoot); 32 | const resolved = path.resolve(normalizedRoot, normalized); 33 | 34 | // Ensure the resolved path is within the workspace 35 | if (!resolved.startsWith(normalizedRoot)) { 36 | return { 37 | valid: false, 38 | normalized, 39 | error: 'Invalid file path: path traversal detected (outside workspace)', 40 | }; 41 | } 42 | } 43 | 44 | // Additional check: ensure no upward traversal in the original path 45 | // that would escape the working directory 46 | if (filePath.includes('..') && workspaceRoot) { 47 | const absolutePath = path.isAbsolute(filePath) 48 | ? filePath 49 | : path.resolve(workspaceRoot, filePath); 50 | const normalizedAbsolute = path.normalize(absolutePath); 51 | 52 | if (!normalizedAbsolute.startsWith(path.normalize(workspaceRoot))) { 53 | return { 54 | valid: false, 55 | normalized, 56 | error: 'Invalid file path: path traversal detected (upward directory traversal)', 57 | }; 58 | } 59 | } 60 | 61 | return { valid: true, normalized }; 62 | } 63 | 64 | /** 65 | * Validates that a file exists and is accessible 66 | * @param {string} filePath - The file path to check 67 | * @returns {boolean} True if file exists and is accessible 68 | */ 69 | function fileExists(filePath) { 70 | try { 71 | return fs.existsSync(filePath) && fs.statSync(filePath).isFile(); 72 | } catch { 73 | return false; 74 | } 75 | } 76 | 77 | /** 78 | * Validates that an executable path is safe to run 79 | * Checks for path traversal and ensures the file exists 80 | * 81 | * @param {string} execPath - The executable path to validate 82 | * @param {string} [allowedDir] - Optional directory where executable must reside 83 | * @returns {{valid: boolean, normalized: string, error?: string}} Validation result 84 | */ 85 | function validateExecutablePath(execPath, allowedDir = null) { 86 | const validation = validateFilePath(execPath, allowedDir); 87 | 88 | if (!validation.valid) { 89 | return validation; 90 | } 91 | 92 | // Check if executable exists 93 | if (!fileExists(validation.normalized)) { 94 | return { 95 | valid: false, 96 | normalized: validation.normalized, 97 | error: 'Invalid executable path: file does not exist', 98 | }; 99 | } 100 | 101 | return validation; 102 | } 103 | 104 | module.exports = { 105 | validateFilePath, 106 | validateExecutablePath, 107 | fileExists, 108 | }; 109 | -------------------------------------------------------------------------------- /scripts/package-openvsx.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Package AutoIt-VSCode extension for OpenVSX registry 5 | * 6 | * This script: 7 | * 1. Backs up the original package.json 8 | * 2. Temporarily changes the publisher to "loganch" 9 | * 3. Packages the extension with a custom filename 10 | * 4. Restores the original package.json 11 | */ 12 | 13 | import { readFileSync, writeFileSync, copyFileSync, unlinkSync } from 'fs'; 14 | import { join, dirname } from 'path'; 15 | import { fileURLToPath } from 'url'; 16 | import { execSync } from 'child_process'; 17 | 18 | const __filename = fileURLToPath(import.meta.url); 19 | const __dirname = dirname(__filename); 20 | const rootDir = join(__dirname, '..'); 21 | const packageJsonPath = join(rootDir, 'package.json'); 22 | const backupPath = join(rootDir, 'package.json.backup'); 23 | const JSON_INDENTATION = 2; 24 | 25 | /** 26 | * Read and parse package.json 27 | */ 28 | function readPackageJson() { 29 | const content = readFileSync(packageJsonPath, 'utf8'); 30 | return JSON.parse(content); 31 | } 32 | 33 | /** 34 | * Write package.json with formatting 35 | */ 36 | function writePackageJson(data) { 37 | writeFileSync(packageJsonPath, JSON.stringify(data, null, JSON_INDENTATION) + '\n', 'utf8'); 38 | } 39 | 40 | /** 41 | * Restore package.json from backup 42 | */ 43 | function restorePackageJson() { 44 | try { 45 | copyFileSync(backupPath, packageJsonPath); 46 | unlinkSync(backupPath); 47 | console.log('✓ Restored original package.json'); 48 | } catch (error) { 49 | console.error('Error restoring package.json:', error.message); 50 | throw error; 51 | } 52 | } 53 | 54 | /** 55 | * Main packaging function 56 | */ 57 | function packageForOpenVSX() { 58 | let backupCreated = false; 59 | 60 | try { 61 | // Read original package.json 62 | const originalPackage = readPackageJson(); 63 | const { name, version, publisher } = originalPackage; 64 | 65 | console.log(`\n📦 Packaging ${name} v${version} for OpenVSX`); 66 | console.log(` Current publisher: ${publisher}`); 67 | console.log(` OpenVSX publisher: loganch\n`); 68 | 69 | // Create backup 70 | copyFileSync(packageJsonPath, backupPath); 71 | backupCreated = true; 72 | console.log('✓ Created package.json backup'); 73 | 74 | // Modify publisher 75 | const modifiedPackage = { ...originalPackage, publisher: 'loganch' }; 76 | writePackageJson(modifiedPackage); 77 | console.log('✓ Updated publisher to "loganch"'); 78 | 79 | // Build extension first 80 | console.log('\n🔨 Building extension...'); 81 | execSync('npm run vscode:prepublish', { 82 | cwd: rootDir, 83 | stdio: 'inherit', 84 | }); 85 | console.log('✓ Build completed'); 86 | 87 | // Package with vsce 88 | console.log('\n📦 Creating .vsix package...'); 89 | const vsixName = `${name}-${version}-openvsx.vsix`; 90 | 91 | try { 92 | execSync(`npx @vscode/vsce package --out ${vsixName}`, { 93 | cwd: rootDir, 94 | stdio: 'inherit', 95 | }); 96 | } catch (error) { 97 | console.error('\n❌ Packaging failed. Make sure @vscode/vsce is installed:'); 98 | console.error(' npm install --save-dev @vscode/vsce\n'); 99 | throw error; 100 | } 101 | 102 | console.log(`\n✅ Successfully created: ${vsixName}`); 103 | console.log(`\n📤 To publish to OpenVSX, run:`); 104 | console.log(` npx ovsx publish ${vsixName} -p \n`); 105 | } catch (error) { 106 | console.error('\n❌ Error during packaging:', error.message); 107 | process.exitCode = 1; 108 | } finally { 109 | // Always restore the original package.json 110 | if (backupCreated) { 111 | restorePackageJson(); 112 | } 113 | } 114 | } 115 | 116 | // Run the script 117 | packageForOpenVSX().catch(error => { 118 | console.error('Fatal error:', error); 119 | process.exit(1); 120 | }); 121 | -------------------------------------------------------------------------------- /src/completions/constants_static.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { fillCompletions } from '../util'; 3 | 4 | const labelPicIcon = 'Label/Pic/Icon Constant'; 5 | const controlDefault = 'Control Default Style Constant'; 6 | const message = 'Message Constant'; 7 | 8 | const items = [ 9 | { 10 | label: '$SS_LEFT', 11 | documentation: '`= 0x0`', 12 | detail: labelPicIcon, 13 | }, 14 | { 15 | label: '$SS_CENTER', 16 | documentation: '`= 0x1`', 17 | detail: labelPicIcon, 18 | }, 19 | { 20 | label: '$SS_RIGHT', 21 | documentation: '`= 0x2`', 22 | detail: labelPicIcon, 23 | }, 24 | { 25 | label: '$SS_ICON', 26 | documentation: '`= 0x3`', 27 | detail: labelPicIcon, 28 | }, 29 | { 30 | label: '$SS_BLACKRECT', 31 | documentation: '`= 0x4`', 32 | detail: labelPicIcon, 33 | }, 34 | { 35 | label: '$SS_GRAYRECT', 36 | documentation: '`= 0x5`', 37 | detail: labelPicIcon, 38 | }, 39 | { 40 | label: '$SS_WHITERECT', 41 | documentation: '`= 0x6`', 42 | detail: labelPicIcon, 43 | }, 44 | { 45 | label: '$SS_BLACKFRAME', 46 | documentation: '`= 0x7`', 47 | detail: labelPicIcon, 48 | }, 49 | { 50 | label: '$SS_GRAYFRAME', 51 | documentation: '`= 0x8`', 52 | detail: labelPicIcon, 53 | }, 54 | { 55 | label: '$SS_WHITEFRAME', 56 | documentation: '`= 0x9`', 57 | detail: labelPicIcon, 58 | }, 59 | { 60 | label: '$SS_SIMPLE', 61 | documentation: '`= 0xB`', 62 | detail: labelPicIcon, 63 | }, 64 | { 65 | label: '$SS_LEFTNOWORDWRAP', 66 | documentation: '`= 0xC`', 67 | detail: labelPicIcon, 68 | }, 69 | { 70 | label: '$SS_BITMAP', 71 | documentation: '`= 0xE`', 72 | detail: labelPicIcon, 73 | }, 74 | { 75 | label: '$SS_ENHMETAFILE', 76 | documentation: '`= 0xF`', 77 | detail: labelPicIcon, 78 | }, 79 | { 80 | label: '$SS_ETCHEDHORZ', 81 | documentation: '`= 0x10`', 82 | detail: labelPicIcon, 83 | }, 84 | { 85 | label: '$SS_ETCHEDVERT', 86 | documentation: '`= 0x11`', 87 | detail: labelPicIcon, 88 | }, 89 | { 90 | label: '$SS_ETCHEDFRAME', 91 | documentation: '`= 0x12`', 92 | detail: labelPicIcon, 93 | }, 94 | { 95 | label: '$SS_REALSIZECONTROL', 96 | documentation: '`= 0x40`', 97 | detail: labelPicIcon, 98 | }, 99 | { 100 | label: '$SS_NOPREFIX', 101 | documentation: '`= 0x0080`', 102 | detail: labelPicIcon, 103 | }, 104 | { 105 | label: '$SS_NOTIFY', 106 | documentation: '`= 0x0100`', 107 | detail: labelPicIcon, 108 | }, 109 | { 110 | label: '$SS_CENTERIMAGE', 111 | documentation: '`= 0x0200`', 112 | detail: labelPicIcon, 113 | }, 114 | { 115 | label: '$SS_RIGHTJUST', 116 | documentation: '`= 0x0400`', 117 | detail: labelPicIcon, 118 | }, 119 | { 120 | label: '$SS_SUNKEN', 121 | documentation: '`= 0x1000`', 122 | detail: labelPicIcon, 123 | }, 124 | { 125 | label: '$GUI_SS_DEFAULT_LABEL', 126 | documentation: '`= 0`', 127 | detail: controlDefault, 128 | }, 129 | { 130 | label: '$GUI_SS_DEFAULT_GRAPHIC', 131 | documentation: '`= 0`', 132 | detail: controlDefault, 133 | }, 134 | { 135 | label: '$GUI_SS_DEFAULT_ICON', 136 | documentation: '`= $SS_NOTIFY`', 137 | detail: controlDefault, 138 | }, 139 | { 140 | label: '$GUI_SS_DEFAULT_PIC', 141 | documentation: '`= $SS_NOTIFY`', 142 | detail: controlDefault, 143 | }, 144 | { 145 | label: '$STM_SETICON', 146 | documentation: '`= 0x0170`', 147 | detail: message, 148 | }, 149 | { 150 | label: '$STM_GETICON', 151 | documentation: '`= 0x0171`', 152 | detail: message, 153 | }, 154 | { 155 | label: '$STM_SETIMAGE', 156 | documentation: '`= 0x0172`', 157 | detail: message, 158 | }, 159 | { 160 | label: '$STM_GETIMAGE', 161 | documentation: '`= 0x0173`', 162 | detail: message, 163 | }, 164 | ]; 165 | 166 | export default fillCompletions(items, CompletionItemKind.Constant, '', 'StaticConstants.au3'); 167 | -------------------------------------------------------------------------------- /src/signatures/WinAPIEx/WinAPICom.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { signatureToCompletion, signatureToHover } from '../../util'; 3 | 4 | const include = '(Requires: `#include `)'; 5 | 6 | const signatures = { 7 | _WinAPI_CLSIDFromProgID: { 8 | documentation: 'Looks up a CLSID in the registry, given a ProgID', 9 | label: '_WinAPI_CLSIDFromProgID ( $sProgID )', 10 | params: [ 11 | { 12 | label: '$sProgID', 13 | documentation: 'The string containing the ProgID whose CLSID is requested.', 14 | }, 15 | ], 16 | }, 17 | _WinAPI_CoInitialize: { 18 | documentation: 'Initializes the COM library for use by the calling process', 19 | label: '_WinAPI_CoInitialize ( [$iFlags = 0] )', 20 | params: [ 21 | { 22 | label: '$iFlags', 23 | documentation: '**[optional]** Initialization flags. Default is 0.', 24 | }, 25 | ], 26 | }, 27 | _WinAPI_CoTaskMemAlloc: { 28 | documentation: 'Allocates a block of task memory', 29 | label: '_WinAPI_CoTaskMemAlloc ( $iSize )', 30 | params: [ 31 | { 32 | label: '$iSize', 33 | documentation: 'Size of the memory block to allocate, in bytes', 34 | }, 35 | ], 36 | }, 37 | _WinAPI_CoTaskMemFree: { 38 | documentation: 'Frees a block of task memory', 39 | label: '_WinAPI_CoTaskMemFree ( $pMemory )', 40 | params: [ 41 | { 42 | label: '$pMemory', 43 | documentation: 'Pointer to the memory block to be freed', 44 | }, 45 | ], 46 | }, 47 | _WinAPI_CoTaskMemRealloc: { 48 | documentation: 'Changes the size of a previously allocated block of task memory', 49 | label: '_WinAPI_CoTaskMemRealloc ( $pMemory, $iSize )', 50 | params: [ 51 | { 52 | label: '$pMemory', 53 | documentation: 'Pointer to the memory block to be reallocated', 54 | }, 55 | { 56 | label: '$iSize', 57 | documentation: 'New size of the memory block, in bytes', 58 | }, 59 | ], 60 | }, 61 | _WinAPI_CoUninitialize: { 62 | documentation: 'Closes the COM library on the current process', 63 | label: '_WinAPI_CoUninitialize ( )', 64 | params: [], 65 | }, 66 | _WinAPI_CreateGUID: { 67 | documentation: 'Creates a globally unique identifier (GUID)', 68 | label: '_WinAPI_CreateGUID ( )', 69 | params: [], 70 | }, 71 | _WinAPI_CreateStreamOnHGlobal: { 72 | documentation: 'Creates a stream object that uses a memory handle to store the stream contents', 73 | label: '_WinAPI_CreateStreamOnHGlobal ( [$hGlobal = 0 [, $bDeleteOnRelease = True]] )', 74 | params: [ 75 | { 76 | label: '$hGlobal', 77 | documentation: '**[optional]** Handle to the global memory block. Default is 0.', 78 | }, 79 | { 80 | label: '$bDeleteOnRelease', 81 | documentation: 82 | '**[optional]** Indicates whether the underlying memory should be freed when the stream object is released. Default is True.', 83 | }, 84 | ], 85 | }, 86 | _WinAPI_GetHGlobalFromStream: { 87 | documentation: 'Retrieves the global memory handle to a stream', 88 | label: '_WinAPI_GetHGlobalFromStream ( $pStream )', 89 | params: [ 90 | { 91 | label: '$pStream', 92 | documentation: 'Pointer to the stream object', 93 | }, 94 | ], 95 | }, 96 | _WinAPI_ProgIDFromCLSID: { 97 | documentation: 'Retrieves the ProgID for a given CLSID', 98 | label: '_WinAPI_ProgIDFromCLSID ( $sCLSID )', 99 | params: [ 100 | { 101 | label: '$sCLSID', 102 | documentation: 'The CLSID to look up', 103 | }, 104 | ], 105 | }, 106 | _WinAPI_ReleaseStream: { 107 | documentation: 'Releases a stream object', 108 | label: '_WinAPI_ReleaseStream ( $pStream )', 109 | params: [ 110 | { 111 | label: '$pStream', 112 | documentation: 'Pointer to the stream object to be released', 113 | }, 114 | ], 115 | }, 116 | }; 117 | 118 | const hovers = signatureToHover(signatures); 119 | const completions = signatureToCompletion(signatures, CompletionItemKind.Function, include); 120 | 121 | export { signatures as default, hovers, completions }; 122 | -------------------------------------------------------------------------------- /src/completions/constants_frame.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { fillCompletions } from '../util'; 3 | 4 | const type = 'Frame Type Constant'; 5 | const initial = 'Initial Frame State Constant'; 6 | const setState = 'Set State Constant'; 7 | 8 | const items = [ 9 | { 10 | label: '$DFC_BUTTON', 11 | documentation: '`= 4`', 12 | detail: type, 13 | }, 14 | { 15 | label: '$DFC_CAPTION', 16 | documentation: '`= 1`', 17 | detail: type, 18 | }, 19 | { 20 | label: '$DFC_MENU', 21 | documentation: '`= 2`', 22 | detail: type, 23 | }, 24 | { 25 | label: '$DFC_POPUPMENU', 26 | documentation: '`= 5`', 27 | detail: type, 28 | }, 29 | { 30 | label: '$DFC_SCROLL', 31 | documentation: '`= 3`', 32 | detail: type, 33 | }, 34 | { 35 | label: '$DFCS_BUTTON3STATE', 36 | documentation: '`= 0x8`', 37 | detail: initial, 38 | }, 39 | { 40 | label: '$DFCS_BUTTONCHECK', 41 | documentation: '`= 0x0`', 42 | detail: initial, 43 | }, 44 | { 45 | label: '$DFCS_BUTTONPUSH', 46 | documentation: '`= 0x10`', 47 | detail: initial, 48 | }, 49 | { 50 | label: '$DFCS_BUTTONRADIO', 51 | documentation: '`= 0x4`', 52 | detail: initial, 53 | }, 54 | { 55 | label: '$DFCS_BUTTONRADIOIMAGE', 56 | documentation: '`= 0x1`', 57 | detail: initial, 58 | }, 59 | { 60 | label: '$DFCS_BUTTONRADIOMASK', 61 | documentation: '`= 0x2`', 62 | detail: initial, 63 | }, 64 | { 65 | label: '$DFCS_CAPTIONCLOSE', 66 | documentation: '`= 0x0`', 67 | detail: initial, 68 | }, 69 | { 70 | label: '$DFCS_CAPTIONHELP', 71 | documentation: '`= 0x4`', 72 | detail: initial, 73 | }, 74 | { 75 | label: '$DFCS_CAPTIONMAX', 76 | documentation: '`= 0x2`', 77 | detail: initial, 78 | }, 79 | { 80 | label: '$DFCS_CAPTIONMIN', 81 | documentation: '`= 0x1`', 82 | detail: initial, 83 | }, 84 | { 85 | label: '$DFCS_CAPTIONRESTORE', 86 | documentation: '`= 0x3`', 87 | detail: initial, 88 | }, 89 | { 90 | label: '$DFCS_MENUARROW', 91 | documentation: '`= 0x0`', 92 | detail: initial, 93 | }, 94 | { 95 | label: '$DFCS_MENUARROWRIGHT', 96 | documentation: '`= 0x4`', 97 | detail: initial, 98 | }, 99 | { 100 | label: '$DFCS_MENUBULLET', 101 | documentation: '`= 0x2`', 102 | detail: initial, 103 | }, 104 | { 105 | label: '$DFCS_MENUCHECK', 106 | documentation: '`= 0x1`', 107 | detail: initial, 108 | }, 109 | { 110 | label: '$DFCS_SCROLLCOMBOBOX', 111 | documentation: '`= 0x5`', 112 | detail: initial, 113 | }, 114 | { 115 | label: '$DFCS_SCROLLDOWN', 116 | documentation: '`= 0x1`', 117 | detail: initial, 118 | }, 119 | { 120 | label: '$DFCS_SCROLLLEFT', 121 | documentation: '`= 0x2`', 122 | detail: initial, 123 | }, 124 | { 125 | label: '$DFCS_SCROLLRIGHT', 126 | documentation: '`= 0x3`', 127 | detail: initial, 128 | }, 129 | { 130 | label: '$DFCS_SCROLLSIZEGRIP', 131 | documentation: '`= 0x8`', 132 | detail: initial, 133 | }, 134 | { 135 | label: '$DFCS_SCROLLSIZEGRIPRIGHT', 136 | documentation: '`= 0x10`', 137 | detail: initial, 138 | }, 139 | { 140 | label: '$DFCS_SCROLLUP', 141 | documentation: '`= 0x0`', 142 | detail: initial, 143 | }, 144 | { 145 | label: '$DFCS_ADJUSTRECT', 146 | documentation: '`= 0x2000`', 147 | detail: initial, 148 | }, 149 | { 150 | label: '$DFCS_CHECKED', 151 | documentation: '`= 0x400`', 152 | detail: setState, 153 | }, 154 | { 155 | label: '$DFCS_FLAT', 156 | documentation: '`= 0x4000`', 157 | detail: setState, 158 | }, 159 | { 160 | label: '$DFCS_HOT', 161 | documentation: '`= 0x1000`', 162 | detail: setState, 163 | }, 164 | { 165 | label: '$DFCS_INACTIVE', 166 | documentation: '`= 0x100`', 167 | detail: setState, 168 | }, 169 | { 170 | label: '$DFCS_PUSHED', 171 | documentation: '`= 0x200`', 172 | detail: setState, 173 | }, 174 | { 175 | label: '$DFCS_TRANSPARENT', 176 | documentation: '`= 0x800`', 177 | detail: setState, 178 | }, 179 | ]; 180 | 181 | export default fillCompletions(items, CompletionItemKind.Constant, '', 'FrameConstants.au3'); 182 | -------------------------------------------------------------------------------- /src/utils/IncludeResolver.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | 4 | /** 5 | * Resolves AutoIt #include directives to file paths 6 | */ 7 | export default class IncludeResolver { 8 | constructor(workspaceRoot, autoitIncludePaths = [], maxDepth = 3) { 9 | this.workspaceRoot = workspaceRoot; 10 | this.autoitIncludePaths = autoitIncludePaths; 11 | this.maxDepth = maxDepth; 12 | } 13 | 14 | /** 15 | * Parse include directives from source 16 | * @param {string} source - AutoIt source code 17 | * @param {string} currentFile - Path to current file 18 | * @returns {Array<{type: string, path: string, line: number}>} 19 | */ 20 | parseIncludes(source, currentFile) { 21 | const includes = []; 22 | const lines = source.split('\n'); 23 | 24 | // Matches: #include "file.au3" or #include 25 | const includePattern = /^\s*#include\s+([<"])([^>"]+)[>"]/i; 26 | 27 | lines.forEach((line, index) => { 28 | // Skip comments 29 | if (line.trim().startsWith(';')) return; 30 | 31 | const match = line.match(includePattern); 32 | if (match) { 33 | const bracket = match[1]; 34 | const includePath = match[2]; 35 | 36 | includes.push({ 37 | type: bracket === '<' ? 'library' : 'relative', 38 | path: includePath, 39 | line: index 40 | }); 41 | } 42 | }); 43 | 44 | return includes; 45 | } 46 | 47 | /** 48 | * Resolve include directive to absolute file path 49 | * @param {object} include - Include object from parseIncludes 50 | * @param {string} currentFile - Path to current file 51 | * @returns {string|null} Resolved absolute path or null if not found 52 | */ 53 | resolveIncludePath(include, currentFile) { 54 | if (include.type === 'relative') { 55 | // Resolve relative to current file's directory 56 | const currentDir = path.dirname(currentFile); 57 | const absolutePath = path.resolve(currentDir, include.path); 58 | 59 | if (fs.existsSync(absolutePath)) { 60 | return absolutePath; 61 | } 62 | return null; 63 | } 64 | 65 | if (include.type === 'library') { 66 | // Try each AutoIt include path 67 | for (const includePath of this.autoitIncludePaths) { 68 | const absolutePath = path.join(includePath, include.path); 69 | if (fs.existsSync(absolutePath)) { 70 | return absolutePath; 71 | } 72 | } 73 | return null; 74 | } 75 | 76 | return null; 77 | } 78 | 79 | /** 80 | * Resolve all includes recursively with circular detection 81 | * @param {string} filePath - Starting file path 82 | * @param {Set} visited - Set of already visited files (for circular detection) 83 | * @param {number} depth - Current recursion depth 84 | * @returns {string[]} Array of resolved file paths 85 | */ 86 | resolveAllIncludes(filePath, visited = new Set(), depth = 0) { 87 | // Resolve to absolute path for consistent comparison 88 | const absolutePath = path.resolve(filePath); 89 | 90 | if (visited.has(absolutePath)) { 91 | return []; // Circular include detected 92 | } 93 | 94 | visited.add(absolutePath); 95 | const resolvedFiles = []; 96 | 97 | try { 98 | if (!fs.existsSync(filePath)) { 99 | return []; 100 | } 101 | 102 | const source = fs.readFileSync(filePath, 'utf8'); 103 | const includes = this.parseIncludes(source, filePath); 104 | 105 | for (const include of includes) { 106 | const resolved = this.resolveIncludePath(include, filePath); 107 | if (resolved) { 108 | const absoluteResolved = path.resolve(resolved); 109 | if (!visited.has(absoluteResolved)) { 110 | // Check depth limit before adding 111 | if (depth < this.maxDepth) { 112 | resolvedFiles.push(resolved); 113 | 114 | // Recursively resolve includes in the included file 115 | const nested = this.resolveAllIncludes(resolved, visited, depth + 1); 116 | resolvedFiles.push(...nested); 117 | } 118 | } 119 | } 120 | } 121 | } catch (error) { 122 | // Gracefully handle file read errors 123 | console.warn(`Failed to resolve includes for ${filePath}:`, error.message); 124 | } 125 | 126 | return resolvedFiles; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/completions/constants_buttonconstants.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { fillCompletions } from '../util'; 3 | 4 | const items = [ 5 | { 6 | label: '$BS_3STATE', 7 | documentation: 8 | 'Creates a check box in which the box can be unavailable as well as selected or cleared. Use the unavailable state to show that the state of the check box is not determined.', 9 | }, 10 | { 11 | label: '$BS_AUTO3STATE', 12 | documentation: 13 | 'Creates a three-state check box in which the state cycles through selected, unavailable, and cleared each time the user selects the check box.', 14 | }, 15 | { 16 | label: '$BS_AUTOCHECKBOX', 17 | documentation: 18 | 'Creates a check box in which the check state switches between selected and cleared each time the user selects the check box.', 19 | }, 20 | { 21 | label: '$BS_CHECKBOX', 22 | documentation: 23 | 'Creates a small, empty check box with a label displayed to the right of it. To display the text to the left of the check box, combine this flag with the BS_RIGHTBUTTON style.', 24 | }, 25 | { 26 | label: '$BS_LEFT', 27 | documentation: 28 | 'Left-aligns the text in the button rectangle on the right side of the check box.', 29 | }, 30 | { 31 | label: '$BS_PUSHLIKE', 32 | documentation: 33 | "Makes a button (such as a check box, three-state check box, or radio button) look and act like a push button. The button looks raised when it isn't pushed or checked, and sunken when it is pushed or checked.", 34 | }, 35 | { 36 | label: '$BS_RIGHT', 37 | documentation: 'Right-aligns text in the button rectangle on the right side of the check box.', 38 | }, 39 | { 40 | label: '$BS_RIGHTBUTTON', 41 | documentation: 'Positions a check box square on the right side of the button rectangle.', 42 | }, 43 | { 44 | label: '$BS_GROUPBOX', 45 | documentation: 46 | "Creates a rectangle in which other buttons can be grouped. Any text associated with this style is displayed in the rectangle's upper-left corner.", 47 | }, 48 | { 49 | label: '$BS_AUTORADIOBUTTON', 50 | documentation: 51 | 'Same as a radio button, except that when the user selects it, the button automatically highlights itself and removes the selection from any other radio buttons with the same style in the same group.', 52 | }, 53 | { 54 | label: '$BS_BOTTOM', 55 | documentation: 'Places the text at the bottom of the button rectangle.', 56 | }, 57 | { 58 | label: '$BS_CENTER', 59 | documentation: 'Centers the text horizontally in the button rectangle.', 60 | }, 61 | { 62 | label: '$BS_DEFPUSHBUTTON', 63 | documentation: 64 | 'Creates a push button with a heavy black border. If the button is in a dialog box, the user can select the button by pressing the ENTER key, even when the button does not have the input focus. This style is useful for enabling the user to quickly select the most likely option, or default.', 65 | }, 66 | { 67 | label: '$BS_MULTILINE', 68 | documentation: 69 | 'Wraps the button text to multiple lines if the text string is too long to fit on a single line in the button rectangle.', 70 | }, 71 | { 72 | label: '$BS_TOP', 73 | documentation: 'Places text at the top of the button rectangle.', 74 | }, 75 | { 76 | label: '$BS_VCENTER', 77 | documentation: 'Vertically centers text in the button rectangle.', 78 | }, 79 | { 80 | label: '$BS_ICON', 81 | documentation: 'Specifies that the button displays an icon.', 82 | }, 83 | { 84 | label: '$BS_BITMAP', 85 | documentation: 'Specifies that the button displays a bitmap.', 86 | }, 87 | { 88 | label: '$BS_FLAT', 89 | documentation: 90 | 'Specifies that the button is two-dimensional; it does not use the default shading to create a 3-D image.', 91 | }, 92 | { 93 | label: '$BS_NOTIFY', 94 | documentation: 95 | 'Enables a button to send BN_KILLFOCUS and BN_SETFOCUS notification messages to its parent window. Note that buttons send the BN_CLICKED notification message regardless of whether it has this style. To get BN_DBLCLK notification messages, the button must have the BS_RADIOBUTTON or BS_OWNERDRAW style.', 96 | }, 97 | ]; 98 | 99 | export default fillCompletions( 100 | items, 101 | CompletionItemKind.Constant, 102 | 'Button Constant', 103 | 'ButtonConstant.au3', 104 | ); 105 | -------------------------------------------------------------------------------- /src/completions/constants_tab.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { fillCompletions } from '../util'; 3 | 4 | const items = [ 5 | { 6 | label: '$TCS_SCROLLOPPOSITE', 7 | documentation: 8 | 'Unneeded tabs scroll to the opposite side of the control when a tab is selected. ', 9 | }, 10 | { 11 | label: '$TCS_BOTTOM', 12 | documentation: 13 | 'Tabs appear at the bottom of the control. This value equals TCS_RIGHT. This style is not supported if you use comctl32.dll version 6. ', 14 | }, 15 | { 16 | label: '$TCS_RIGHT', 17 | documentation: 18 | 'Tabs appear vertically on the right side of controls that use the TCS_VERTICAL style. This value equals TCS_BOTTOM. This style is not supported if you use visual styles. ', 19 | }, 20 | { 21 | label: '$TCS_MULTISELECT', 22 | documentation: 23 | 'Multiple tabs can be selected by holding down CTRL when clicking. This style must be used with the TCS_BUTTONS style. ', 24 | }, 25 | { 26 | label: '$TCS_FLATBUTTONS', 27 | documentation: 28 | 'Selected tabs appear as being indented into the background while other tabs appear as being on the same plane as the background. This style only affects tab controls with the TCS_BUTTONS style. ', 29 | }, 30 | { 31 | label: '$TCS_FORCEICONLEFT', 32 | documentation: 33 | 'Icons are aligned with the left edge of each fixed-width tab. This style can only be used with the TCS_FIXEDWIDTH style. ', 34 | }, 35 | { 36 | label: '$TCS_FORCELABELLEFT', 37 | documentation: 38 | 'Labels are aligned with the left edge of each fixed-width tab; that is, the label is displayed immediately to the right of the icon instead of being centered.\n\nThis style can only be used with the TCS_FIXEDWIDTH style, and it implies the TCS_FORCEICONLEFT style.', 39 | }, 40 | { 41 | label: '$TCS_HOTTRACK', 42 | documentation: 'Items under the pointer are automatically highlighted ', 43 | }, 44 | { 45 | label: '$TCS_VERTICAL', 46 | documentation: 47 | 'Tabs appear at the left side of the control, with tab text displayed vertically. This style is valid only when used with the TCS_MULTILINE style. To make tabs appear on the right side of the control, also use the TCS_RIGHT style. This style is not supported if you use comctl32.dll version 6. ', 48 | }, 49 | { 50 | label: '$TCS_TABS', 51 | documentation: 52 | 'Tabs appear as tabs, and a border is drawn around the display area. This style is the default. ', 53 | }, 54 | { 55 | label: '$TCS_BUTTONS', 56 | documentation: 'Tabs appear as buttons, and no border is drawn around the display area. ', 57 | }, 58 | { 59 | label: '$TCS_SINGLELINE', 60 | documentation: 61 | 'Only one row of tabs is displayed. The user can scroll to see more tabs, if necessary. This style is the default. ', 62 | }, 63 | { 64 | label: '$TCS_MULTILINE', 65 | documentation: 66 | 'Multiple rows of tabs are displayed, if necessary, so all tabs are visible at once. ', 67 | }, 68 | { 69 | label: '$TCS_RIGHTJUSTIFY', 70 | documentation: 71 | 'The width of each tab is increased, if necessary, so that each row of tabs fills the entire width of the tab control.\n\nThis window style is ignored unless the TCS_MULTILINE style is also specified.', 72 | }, 73 | { 74 | label: '$TCS_FIXEDWIDTH', 75 | documentation: 76 | 'All tabs are the same width. This style cannot be combined with the TCS_RIGHTJUSTIFY style. ', 77 | }, 78 | { 79 | label: '$TCS_RAGGEDRIGHT', 80 | documentation: 81 | 'Rows of tabs will not be stretched to fill the entire width of the control. This style is the default. ', 82 | }, 83 | { 84 | label: '$TCS_FOCUSONBUTTONDOWN', 85 | documentation: 'The tab control receives the input focus when clicked. ', 86 | }, 87 | { 88 | label: '$TCS_OWNERDRAWFIXED', 89 | documentation: 'The parent window is responsible for drawing tabs. ', 90 | }, 91 | { 92 | label: '$TCS_TOOLTIPS', 93 | documentation: 'The tab control has a tooltip control associated with it. ', 94 | }, 95 | { 96 | label: '$TCS_FOCUSNEVER', 97 | documentation: 'The tab control does not receive the input focus when clicked. ', 98 | }, 99 | ]; 100 | 101 | export default fillCompletions( 102 | items, 103 | CompletionItemKind.Constant, 104 | 'Tab Style Constant', 105 | 'TabConstants.au3', 106 | ); 107 | -------------------------------------------------------------------------------- /src/signatures/udf_visa.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { signatureToCompletion, signatureToHover } from '../util'; 3 | 4 | const include = '(Requires: `#include `)'; 5 | 6 | const signatures = { 7 | _viClose: { 8 | documentation: `Closes a VISA connection to an Instrument/Device\n\n${include}`, 9 | label: '_viClose ( $hSession )', 10 | params: [{ label: '$hSession', documentation: 'VISA session handle' }], 11 | }, 12 | 13 | _viExecCommand: { 14 | documentation: `Send a Command/Query to an Instrument/Device through the VISA interface (GPIB / TCP)\n\n${include}`, 15 | label: '_viExecCommand ( $hSession, $sCommand [, $iTimeoutMS = -1 [, $sMode = @LF]] )', 16 | params: [ 17 | { label: '$hSession', documentation: 'VISA session handle' }, 18 | { label: '$sCommand', documentation: 'Command/Query string to send' }, 19 | { 20 | label: '$iTimeoutMS', 21 | documentation: '**[optional]** Timeout in milliseconds (default: -1)', 22 | }, 23 | { label: '$sMode', documentation: '**[optional]** Line termination mode (default: @LF)' }, 24 | ], 25 | }, 26 | 27 | _viFindGpib: { 28 | documentation: `Find and enumerate GPIB devices on the VISA interface\n\n${include}`, 29 | label: '_viFindGpib ( ByRef $aDescriptorList, ByRef $aIDNList [, $iShow_Search_Results = 0] )', 30 | params: [ 31 | { label: '$aDescriptorList', documentation: 'Array to receive descriptor list' }, 32 | { label: '$aIDNList', documentation: 'Array to receive IDN list' }, 33 | { 34 | label: '$iShow_Search_Results', 35 | documentation: '**[optional]** Show search results (default: 0)', 36 | }, 37 | ], 38 | }, 39 | _viGpibBusReset: { 40 | documentation: `GPIB BUS "reset": Use this function when the GPIB BUS gets stuck for some reason. You might be lucky and resolve the problem by calling this function\n\n${include}`, 41 | label: '_viGpibBusReset ( )', 42 | params: [], 43 | }, 44 | 45 | _viGTL: { 46 | documentation: `Go To Local mode: Instruments that accept this command will exit the "Remote Control mode" and go to "Local mode". If the instrument is already in "Local mode" this is simply ignored. Normally, if an instrument does not support this command it will simply stay in the "Remote Control mode"\n\n${include}`, 47 | label: '_viGTL ( $hSession )', 48 | params: [{ label: '$hSession', documentation: 'VISA session handle' }], 49 | }, 50 | 51 | _viInteractiveControl: { 52 | documentation: `Interactive VISA control to test your SCPI commands\n\n${include}`, 53 | label: '_viInteractiveControl ( [$sCommand_Save_FilePath = ""] )', 54 | params: [ 55 | { 56 | label: '$sCommand_Save_FilePath', 57 | documentation: '**[optional]** File path to save commands (default: "")', 58 | }, 59 | ], 60 | }, 61 | 62 | _viOpen: { 63 | documentation: `Opens a VISA connection to an Instrument/Device\n\n${include}`, 64 | label: '_viOpen ( $sVisa_Address [, $sVisa_Secondary_Address = 0] )', 65 | params: [ 66 | { label: '$sVisa_Address', documentation: 'VISA address string' }, 67 | { 68 | label: '$sVisa_Secondary_Address', 69 | documentation: '**[optional]** Secondary VISA address (default: 0)', 70 | }, 71 | ], 72 | }, 73 | 74 | _viSetAttribute: { 75 | documentation: `Set any VISA attribute. This function, which is called by _viSetTimeout, can ALSO be used to set the other VISA specific attributes. Read the VISA documentation for more information and a list of VISA attributes and their corresponding values\n\n${include}`, 76 | label: '_viSetAttribute ( $hSession, $iAttribute, $iValue )', 77 | params: [ 78 | { label: '$hSession', documentation: 'VISA session handle' }, 79 | { label: '$iAttribute', documentation: 'VISA attribute to set' }, 80 | { label: '$iValue', documentation: 'Value to set for the attribute' }, 81 | ], 82 | }, 83 | _viSetTimeout: { 84 | documentation: `Sets the VISA timeout in MILLISECONDS\n\n${include}`, 85 | label: '_viSetTimeout ( $hSession, $iTimeoutMS )', 86 | params: [ 87 | { label: '$hSession', documentation: 'VISA session handle' }, 88 | { label: '$iTimeoutMS', documentation: 'Timeout value in milliseconds' }, 89 | ], 90 | }, 91 | }; 92 | 93 | const hovers = signatureToHover(signatures); 94 | const completions = signatureToCompletion(signatures, CompletionItemKind.Function, include); 95 | 96 | export { signatures as default, hovers, completions }; 97 | -------------------------------------------------------------------------------- /test/ai_workspaceSymbols.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ai_workspaceSymbols.test.js 3 | * Tests for workspace symbol provider performance optimizations 4 | */ 5 | 6 | describe('Workspace Symbols Performance', () => { 7 | describe('Batch Processing Logic', () => { 8 | it('should process files in batches', async () => { 9 | const files = Array.from({ length: 50 }, (_, i) => ({ id: i })); 10 | const batchSize = 10; 11 | const batches = []; 12 | 13 | // Simulate batch processing 14 | for (let i = 0; i < files.length; i += batchSize) { 15 | const batch = files.slice(i, i + batchSize); 16 | batches.push(batch); 17 | } 18 | 19 | expect(batches.length).toBe(5); 20 | expect(batches[0].length).toBe(10); 21 | expect(batches[4].length).toBe(10); 22 | }); 23 | 24 | it('should respect maxFiles limit', async () => { 25 | const allFiles = Array.from({ length: 1000 }, (_, i) => ({ id: i })); 26 | const maxFiles = 500; 27 | 28 | // Simulate limiting files 29 | const filesToProcess = allFiles.slice(0, maxFiles); 30 | 31 | expect(allFiles.length).toBe(1000); 32 | expect(filesToProcess.length).toBe(500); 33 | }); 34 | 35 | it('should handle uneven batch sizes', async () => { 36 | const files = Array.from({ length: 47 }, (_, i) => ({ id: i })); 37 | const batchSize = 10; 38 | const batches = []; 39 | 40 | for (let i = 0; i < files.length; i += batchSize) { 41 | const batch = files.slice(i, i + batchSize); 42 | batches.push(batch); 43 | } 44 | 45 | expect(batches.length).toBe(5); 46 | expect(batches[4].length).toBe(7); // Last batch is smaller 47 | }); 48 | }); 49 | 50 | describe('Cancellation Support', () => { 51 | it('should stop processing when cancellation is requested', async () => { 52 | const mockToken = { 53 | isCancellationRequested: false, 54 | }; 55 | 56 | const files = Array.from({ length: 100 }, (_, i) => ({ id: i })); 57 | const batchSize = 10; 58 | const processed = []; 59 | 60 | // Simulate cancellation after 2 batches 61 | for (let i = 0; i < files.length; i += batchSize) { 62 | if (mockToken.isCancellationRequested) { 63 | break; 64 | } 65 | 66 | const batch = files.slice(i, i + batchSize); 67 | processed.push(...batch); 68 | 69 | // Cancel after 2 batches 70 | if (i >= batchSize) { 71 | mockToken.isCancellationRequested = true; 72 | } 73 | } 74 | 75 | expect(processed.length).toBe(20); // Only 2 batches processed 76 | }); 77 | }); 78 | 79 | describe('Cache Management', () => { 80 | it('should use Map for incremental updates', () => { 81 | const cache = new Map(); 82 | 83 | // Add entries 84 | cache.set('file1', ['symbol1', 'symbol2']); 85 | cache.set('file2', ['symbol3']); 86 | 87 | expect(cache.size).toBe(2); 88 | 89 | // Update single entry 90 | cache.set('file1', ['symbol1', 'symbol2', 'symbol4']); 91 | 92 | expect(cache.size).toBe(2); 93 | expect(cache.get('file1').length).toBe(3); 94 | }); 95 | 96 | it('should remove individual files from cache', () => { 97 | const cache = new Map(); 98 | 99 | cache.set('file1', ['symbol1']); 100 | cache.set('file2', ['symbol2']); 101 | cache.set('file3', ['symbol3']); 102 | 103 | expect(cache.size).toBe(3); 104 | 105 | // Remove single file 106 | cache.delete('file2'); 107 | 108 | expect(cache.size).toBe(2); 109 | expect(cache.has('file2')).toBe(false); 110 | }); 111 | }); 112 | 113 | describe('Query Filtering', () => { 114 | it('should filter symbols by query string', () => { 115 | const symbols = [ 116 | { name: 'TestFunc', kind: 1 }, 117 | { name: 'HelperFunc', kind: 1 }, 118 | { name: 'TestVariable', kind: 2 }, 119 | ]; 120 | 121 | const query = 'test'; 122 | const filtered = symbols.filter(s => s.name.toLowerCase().includes(query)); 123 | 124 | expect(filtered).toHaveLength(2); 125 | expect(filtered[0].name).toBe('TestFunc'); 126 | expect(filtered[1].name).toBe('TestVariable'); 127 | }); 128 | 129 | it('should be case-insensitive', () => { 130 | const symbols = [{ name: 'MyFunction' }, { name: 'myVariable' }, { name: 'OTHER' }]; 131 | 132 | const query = 'my'; 133 | const filtered = symbols.filter(s => s.name.toLowerCase().includes(query.toLowerCase())); 134 | 135 | expect(filtered).toHaveLength(2); 136 | }); 137 | }); 138 | }); 139 | -------------------------------------------------------------------------------- /src/completions/constants_string.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { fillCompletions } from '../util'; 3 | 4 | const multi = 'StringCompare/StringInStr/StringReplace Constant'; 5 | const stringStripWS = 'StringStripWS Constant'; 6 | const stringSplit = 'StringSplit Constant'; 7 | const stringRegExp = 'StringRegExp Constant'; 8 | const stringBetween = '_StringBetween Constant'; 9 | const stringBinary = 'BinaryToString/StringToBinary Constant'; 10 | const stringASCII = 'StringFromASCIIArray Constant'; 11 | const stringReverse = 'StringReverse Constant'; 12 | 13 | const items = [ 14 | { 15 | label: '$STR_NOCASESENSE', 16 | documentation: 'Not case sensitive (default)\n\n`= 0`', 17 | detail: multi, 18 | }, 19 | { 20 | label: '$STR_CASESENSE', 21 | documentation: 'Case sensitive\n\n`= 1`', 22 | detail: multi, 23 | }, 24 | { 25 | label: '$STR_NOCASESENSEBASIC', 26 | documentation: 'Not case sensitive, using a basic comparison\n\n`= 2`', 27 | detail: multi, 28 | }, 29 | { 30 | label: '$STR_STRIPLEADING', 31 | documentation: 'Strip leading whitespace\n\n`= 1`', 32 | detail: stringStripWS, 33 | }, 34 | { 35 | label: '$STR_STRIPTRAILING', 36 | documentation: 'Strip trailing whitespace\n\n`= 2`', 37 | detail: stringStripWS, 38 | }, 39 | { 40 | label: '$STR_STRIPSPACES', 41 | documentation: 'Strip double (or more) spaces between words\n\n`= 4`', 42 | detail: stringStripWS, 43 | }, 44 | { 45 | label: '$STR_STRIPALL', 46 | documentation: 'Strip all spaces (over-rides all other flags)\n\n`= 8`', 47 | detail: stringStripWS, 48 | }, 49 | { 50 | label: '$STR_CHRSPLIT', 51 | documentation: 'Each character in the delimiter string will mark the split\n\n`= 0`', 52 | detail: stringSplit, 53 | }, 54 | { 55 | label: '$STR_ENTIRESPLIT', 56 | documentation: 'Entire delimiter marks the split\n\n`= 1`', 57 | detail: stringSplit, 58 | }, 59 | { 60 | label: '$STR_NOCOUNT', 61 | documentation: 'Disable the return count\n\n`= 2`', 62 | detail: stringSplit, 63 | }, 64 | { 65 | label: '$STR_REGEXPMATCH', 66 | documentation: 'Return 1 if match.\n\n`= 0`', 67 | detail: stringRegExp, 68 | }, 69 | { 70 | label: '$STR_REGEXPARRAYMATCH', 71 | documentation: 'Return array of matches.\n\n`= 1`', 72 | detail: stringRegExp, 73 | }, 74 | { 75 | label: '$STR_REGEXPARRAYFULLMATCH', 76 | documentation: 'Return array of matches including the full match (Perl / PHP style).\n\n`= 2`', 77 | detail: stringRegExp, 78 | }, 79 | { 80 | label: '$STR_REGEXPARRAYGLOBALMATCH', 81 | documentation: 'Return array of global matches.\n\n`= 3`', 82 | detail: stringRegExp, 83 | }, 84 | { 85 | label: '$STR_REGEXPARRAYGLOBALFULLMATCH', 86 | documentation: 87 | 'Return an array of arrays containing global matches including the full match (Perl / PHP style).\n\n`= 4`', 88 | detail: stringRegExp, 89 | }, 90 | { 91 | label: '$STR_ENDISSTART', 92 | documentation: 'End acts as next start when end = start\n\n`= 0`', 93 | detail: stringBetween, 94 | }, 95 | { 96 | label: '$STR_ENDNOTSTART', 97 | documentation: 'End does not act as new start when end = start\n\n`= 1`', 98 | detail: stringBetween, 99 | }, 100 | { 101 | label: '$SB_ANSI', 102 | documentation: 'String data is ANSI (default)\n`= 1`', 103 | detail: stringBinary, 104 | }, 105 | { 106 | label: '$SB_UTF16LE', 107 | documentation: 'String data is UTF16 Little Endian\n`= 2`', 108 | detail: stringBinary, 109 | }, 110 | { 111 | label: '$SB_UTF16BE', 112 | documentation: 'String data is UTF16 Big Endian\n`= 3`', 113 | detail: stringBinary, 114 | }, 115 | { 116 | label: '$SB_UTF8', 117 | documentation: 'String data is UTF8\n`= 4`', 118 | detail: stringBinary, 119 | }, 120 | { 121 | label: '$SE_UTF16', 122 | documentation: '`= 0`', 123 | detail: stringASCII, 124 | }, 125 | { 126 | label: '$SE_ANSI', 127 | documentation: '`= 1`', 128 | detail: stringASCII, 129 | }, 130 | { 131 | label: '$SE_UTF8', 132 | documentation: '`= 2`', 133 | detail: stringASCII, 134 | }, 135 | { 136 | label: '$STR_UTF16', 137 | documentation: 'Reversed in full UTF-16 mode. (default)\n\n`= 0`', 138 | detail: stringReverse, 139 | }, 140 | { 141 | label: '$STR_UCS2', 142 | documentation: 'A much faster method - only use if using UCS-2 text.\n\n`= 1`', 143 | detail: stringReverse, 144 | }, 145 | ]; 146 | 147 | export default fillCompletions(items, CompletionItemKind.Constant, '', 'StringConstants.au3'); 148 | -------------------------------------------------------------------------------- /src/signatures/udf_string.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { 3 | br, 4 | valueFirstHeader as header, 5 | opt, 6 | signatureToCompletion, 7 | signatureToHover, 8 | } from '../util'; 9 | 10 | const include = '(Requires: `#include `)'; 11 | 12 | const signatures = { 13 | _HexToString: { 14 | documentation: 'Convert a hex string to a string', 15 | label: '_HexToString ( $sHex )', 16 | params: [ 17 | { 18 | label: '$sHex', 19 | documentation: 'A hexadecimal string', 20 | }, 21 | ], 22 | }, 23 | _StringBetween: { 24 | documentation: 'Find strings between two string delimiters', 25 | label: 26 | '_StringBetween ( $sString, $sStart, $sEnd [, $iMode = $STR_ENDISSTART [, $bCase = False]] )', 27 | params: [ 28 | { 29 | label: '$sString', 30 | documentation: 'The string to search.', 31 | }, 32 | { 33 | label: '$sStart', 34 | documentation: 35 | 'The beginning of the string to find. Passing an empty string starts at the beginning', 36 | }, 37 | { 38 | label: '$sEnd', 39 | documentation: 40 | 'The end of the string to find. Passing an empty string searches from $sStart to end of string', 41 | }, 42 | { 43 | label: '$iMode', 44 | documentation: 45 | '**[optional]** Search mode when $sStart = $sEnd\n$STR_ENDISSTART (0) the $sEnd string at the end of a match starts the next possible match (default)\n$STR_ENDNOTSTART (1) a further instance of the $sStart starts the next match', 46 | }, 47 | { 48 | label: '$bCase', 49 | documentation: 50 | '**[optional]** False (default setting) = case-insensitive. True = case-sensitive.', 51 | }, 52 | ], 53 | }, 54 | _StringExplode: { 55 | documentation: 56 | 'Splits up a string into substrings depending on the given delimiters as PHP Explode v5', 57 | label: '_StringExplode ( $sString, $sDelimiter [, $iLimit = 0] )', 58 | params: [ 59 | { 60 | label: '$sString', 61 | documentation: 'String to be split', 62 | }, 63 | { 64 | label: '$sDelimiter', 65 | documentation: 66 | 'Delimiter to split on (split is performed on entire string, not individual characters)', 67 | }, 68 | { 69 | label: '$iLimit', 70 | documentation: 71 | '**[optional]** Maximum elements to be returned    =0 : (default) Split on every instance of the delimiter    >0 : Split until limit, last element will contain remaining portion of the string    ', 72 | }, 73 | ], 74 | }, 75 | _StringInsert: { 76 | documentation: 'Inserts a string within another string', 77 | label: '_StringInsert ( $sString, $sInsertString, $iPosition )', 78 | params: [ 79 | { 80 | label: '$sString', 81 | documentation: 'Original string', 82 | }, 83 | { 84 | label: '$sInsertString', 85 | documentation: 'String to be inserted', 86 | }, 87 | { 88 | label: '$iPosition', 89 | documentation: 'Position to insert string (negatives values count from right hand side)', 90 | }, 91 | ], 92 | }, 93 | _StringProper: { 94 | documentation: 'Changes a string to proper case, same as the =Proper function in Excel', 95 | label: '_StringProper ( $sString )', 96 | params: [ 97 | { 98 | label: '$sString', 99 | documentation: 'Input string', 100 | }, 101 | ], 102 | }, 103 | _StringRepeat: { 104 | documentation: 'Repeats a string a specified number of times', 105 | label: '_StringRepeat ( $sString, $iRepeatCount )', 106 | params: [ 107 | { 108 | label: '$sString', 109 | documentation: 'String to repeat', 110 | }, 111 | { 112 | label: '$iRepeatCount', 113 | documentation: 'Number of times to repeat the string', 114 | }, 115 | ], 116 | }, 117 | _StringTitleCase: { 118 | documentation: 'Changes a string to a title case string', 119 | label: '_StringTitleCase ( $sString )', 120 | params: [ 121 | { 122 | label: '$sString', 123 | documentation: 'Input string', 124 | }, 125 | ], 126 | }, 127 | _StringToHex: { 128 | documentation: 'Convert a string to a hex string', 129 | label: '_StringToHex ( $sString )', 130 | params: [ 131 | { 132 | label: '$sString', 133 | documentation: 'String to be converted.', 134 | }, 135 | ], 136 | }, 137 | }; 138 | 139 | const hovers = signatureToHover(signatures); 140 | const completions = signatureToCompletion(signatures, CompletionItemKind.Function, include); 141 | 142 | export { signatures as default, hovers, completions }; 143 | -------------------------------------------------------------------------------- /src/signatures/udf_sound.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { 3 | br, 4 | valueFirstHeader as header, 5 | opt, 6 | signatureToCompletion, 7 | signatureToHover, 8 | } from '../util'; 9 | 10 | const include = '(Requires: `#include `)'; 11 | 12 | const signatures = { 13 | _SoundClose: { 14 | documentation: 'Closes a sound previously opened with _SoundOpen', 15 | label: '_SoundClose ( $aSndID )', 16 | params: [ 17 | { 18 | label: '$aSndID', 19 | documentation: 'Sound ID array as returned by _SoundOpen()', 20 | }, 21 | ], 22 | }, 23 | _SoundLength: { 24 | documentation: 'Returns the length of the soundfile', 25 | label: '_SoundLength ( $aSndID [, $iMode = 1] )', 26 | params: [ 27 | { 28 | label: '$aSndID', 29 | documentation: 'Sound ID array as returned by _SoundOpen() or a file name', 30 | }, 31 | { 32 | label: '$iMode', 33 | documentation: 34 | '**[optional]** This flag determines the format of the returned sound length    1 = (by default) hh:mm:ss where h = hours, m = minutes and s = seconds (default)    2 = milliseconds', 35 | }, 36 | ], 37 | }, 38 | _SoundOpen: { 39 | documentation: 'Opens a sound file for use with other _Sound functions', 40 | label: '_SoundOpen ( $sFilePath )', 41 | params: [ 42 | { 43 | label: '$sFilePath', 44 | documentation: 'Path to sound file', 45 | }, 46 | ], 47 | }, 48 | _SoundPause: { 49 | documentation: 'Pause a playing sound', 50 | label: '_SoundPause ( $aSndID )', 51 | params: [ 52 | { 53 | label: '$aSndID', 54 | documentation: 'Sound ID array as returned by _SoundOpen() or a file name', 55 | }, 56 | ], 57 | }, 58 | _SoundPlay: { 59 | documentation: 'Play a sound file', 60 | label: '_SoundPlay ( $aSndID [, $iWait = 0] )', 61 | params: [ 62 | { 63 | label: '$aSndID', 64 | documentation: 'Sound ID array as returned by _SoundOpen() or a file name', 65 | }, 66 | { 67 | label: '$iWait', 68 | documentation: 69 | '**[optional]** This flag determines if the script should wait for the sound to finish before continuing:    0 = continue script while sound is playing (default)    1 = wait until sound has finished', 70 | }, 71 | ], 72 | }, 73 | _SoundPos: { 74 | documentation: 'Returns the current position of the sound', 75 | label: '_SoundPos ( $aSndID [, $iMode = 1] )', 76 | params: [ 77 | { 78 | label: '$aSndID', 79 | documentation: 'Sound ID array as returned by _SoundOpen() or a file name', 80 | }, 81 | { 82 | label: '$iMode', 83 | documentation: 84 | '**[optional]** This flag determines which format the position of the sound is returned in    1 = (by default) hh:mm:ss where h = hours, m = minutes and s = seconds (default)    2 = milliseconds', 85 | }, 86 | ], 87 | }, 88 | _SoundResume: { 89 | documentation: 'Resume a paused sound', 90 | label: '_SoundResume ( $aSndID )', 91 | params: [ 92 | { 93 | label: '$aSndID', 94 | documentation: 'Sound ID array as returned by _SoundOpen() or a file name', 95 | }, 96 | ], 97 | }, 98 | _SoundSeek: { 99 | documentation: 'Seeks the sound to the specified position', 100 | label: '_SoundSeek ( ByRef $aSndID, $iHour, $iMin, $iSec )', 101 | params: [ 102 | { 103 | label: '$aSndID', 104 | documentation: 'Sound ID array as returned by _SoundOpen()', 105 | }, 106 | { 107 | label: '$iHour', 108 | documentation: 'Hour to seek to', 109 | }, 110 | { 111 | label: '$iMin', 112 | documentation: 'Minute to seek to', 113 | }, 114 | { 115 | label: '$iSec', 116 | documentation: 'Second to seek to', 117 | }, 118 | ], 119 | }, 120 | _SoundStatus: { 121 | documentation: 'Returns the status of the sound', 122 | label: '_SoundStatus ( $aSndID )', 123 | params: [ 124 | { 125 | label: '$aSndID', 126 | documentation: 'Sound ID array as returned by _SoundOpen() or a file name', 127 | }, 128 | ], 129 | }, 130 | _SoundStop: { 131 | documentation: 'Stop a playing sound', 132 | label: '_SoundStop ( ByRef $aSndID )', 133 | params: [ 134 | { 135 | label: '$aSndID', 136 | documentation: 137 | 'Sound ID array as returned by _SoundOpen() or a file name (must be a variable)', 138 | }, 139 | ], 140 | }, 141 | }; 142 | 143 | const hovers = signatureToHover(signatures); 144 | const completions = signatureToCompletion(signatures, CompletionItemKind.Function, include); 145 | 146 | export { signatures as default, hovers, completions }; 147 | -------------------------------------------------------------------------------- /src/signatures/udf_inet.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { 3 | br, 4 | valueFirstHeader as header, 5 | opt, 6 | signatureToCompletion, 7 | signatureToHover, 8 | } from '../util'; 9 | 10 | const include = '(Requires: `#include `)'; 11 | 12 | const signatures = { 13 | _GetIP: { 14 | documentation: 'Get public IP address of a network/computer', 15 | label: '_GetIP ( )', 16 | params: [], 17 | }, 18 | _INetExplorerCapable: { 19 | documentation: 'Converts a string to IE(Internet Explorer) capable line', 20 | label: '_INetExplorerCapable ( $sIEString )', 21 | params: [ 22 | { 23 | label: '$sIEString', 24 | documentation: 'String to be converted', 25 | }, 26 | ], 27 | }, 28 | _INetGetSource: { 29 | documentation: 'Gets the source from an URL without writing a temp file', 30 | label: '_INetGetSource ( $sURL [, $bString = True] )', 31 | params: [ 32 | { 33 | label: '$sURL', 34 | documentation: "(The URL of the site.) eg 'http://www.autoitscript.com'", 35 | }, 36 | { 37 | label: '$bString', 38 | documentation: 39 | '**[optional]** If True the data is returned in string format, otherwise binary format.', 40 | }, 41 | ], 42 | }, 43 | _INetMail: { 44 | documentation: "Opens default user's mail client with given address, subject, and body", 45 | label: '_INetMail ( $sMailTo, $sMailSubject, $sMailBody )', 46 | params: [ 47 | { 48 | label: '$sMailTo', 49 | documentation: 'Address for the E-Mail', 50 | }, 51 | { 52 | label: '$sMailSubject', 53 | documentation: 'Subject for the E-Mail', 54 | }, 55 | { 56 | label: '$sMailBody', 57 | documentation: 'Body for the E-Mail', 58 | }, 59 | ], 60 | }, 61 | _INetSmtpMail: { 62 | documentation: 'Sends an email without using an external email program', 63 | label: 64 | '_INetSmtpMail ( $sSMTPServer, $sFromName, $sFromAddress, $sToAddress [, $sSubject = "" [, $aBody = "" [, $sEHLO = "" [, $sFirst = "" [, $bTrace = 0]]]]] )', 65 | params: [ 66 | { 67 | label: '$sSMTPServer', 68 | documentation: 69 | 'Smtp server the eMail is to be sent though May be either alpha or a numeric IP address. In order to fight spam, many ISPs require this to be their server.eg "smtp.ispdomain.com", "mail.ispdomain.com" or "192.168.1.1"', 70 | }, 71 | { 72 | label: '$sFromName', 73 | documentation: 'The name you wish the message to appear to be sent from.eg "Bob Smith"', 74 | }, 75 | { 76 | label: '$sFromAddress', 77 | documentation: 78 | 'The email address you wish the message to appear to be sent from.eg "bob.smith@mydomain.com".', 79 | }, 80 | { 81 | label: '$sToAddress', 82 | documentation: 'The email address the message is to go to.eg "jane.brown@yourdomain.com"', 83 | }, 84 | { 85 | label: '$sSubject', 86 | documentation: '**[optional]** The subject of the email.', 87 | }, 88 | { 89 | label: '$aBody', 90 | documentation: 91 | '**[optional]** The body of the email as a single dimensional array of strings. Each value in the array will be terminated with a @CRLF in the email.', 92 | }, 93 | { 94 | label: '$sEHLO', 95 | documentation: 96 | '**[optional]** identifier for the smtp server connection (by default @ComputerName). If Smtp server require a "EHLO" string just set the string to "EHLO " & @ComputerName.', 97 | }, 98 | { 99 | label: '$sFirst', 100 | documentation: 101 | '**[optional]** string sent before helo for the smtp server connection (by default {SPACE}). To not send any character this parameter must equal -1, some SMTP server required it.', 102 | }, 103 | { 104 | label: '$bTrace', 105 | documentation: '**[optional]** trace the dialog in a splash window', 106 | }, 107 | ], 108 | }, 109 | _TCPIpToName: { 110 | documentation: 'Resolves IP address to Hostname(s)', 111 | label: '_TCPIpToName ( $sIp [, $iOption = 0 [, $hDll = "Ws2_32.dll"]] )', 112 | params: [ 113 | { 114 | label: '$sIp', 115 | documentation: 'Ip Adress in dotted (v4) Format', 116 | }, 117 | { 118 | label: '$iOption', 119 | documentation: 120 | '**[optional]** Default = 00 = Return String Hostname1 = Return Array (see Remarks)', 121 | }, 122 | { 123 | label: '$hDll', 124 | documentation: '**[optional]** Handle to Ws2_32.dll', 125 | }, 126 | ], 127 | }, 128 | }; 129 | 130 | const hovers = signatureToHover(signatures); 131 | const completions = signatureToCompletion(signatures, CompletionItemKind.Function, include); 132 | 133 | export { signatures as default, hovers, completions }; 134 | -------------------------------------------------------------------------------- /test/security/parameterValidation.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Security tests for parameter validation warnings. 3 | * 4 | * These tests ensure that potentially dangerous parameter patterns are detected 5 | * and warned about, while still allowing developers to proceed with their testing. 6 | */ 7 | 8 | const { 9 | validateParameter, 10 | validateParameterString, 11 | } = require('../../src/utils/parameterValidation'); 12 | 13 | describe('Parameter Validation - Security Warning Tests', () => { 14 | describe('validateParameter', () => { 15 | describe('Safe parameters (no warnings)', () => { 16 | test('should accept simple alphanumeric parameter', () => { 17 | const result = validateParameter('param123'); 18 | expect(result.hasWarnings).toBe(false); 19 | expect(result.sanitized).toBe('param123'); 20 | expect(result.warnings).toEqual([]); 21 | }); 22 | 23 | test('should accept parameter with hyphens', () => { 24 | const result = validateParameter('my-parameter-name'); 25 | expect(result.hasWarnings).toBe(false); 26 | expect(result.sanitized).toBe('my-parameter-name'); 27 | }); 28 | 29 | test('should accept parameter with spaces', () => { 30 | const result = validateParameter('my param value'); 31 | expect(result.hasWarnings).toBe(false); 32 | expect(result.sanitized).toBe('my param value'); 33 | }); 34 | 35 | test('should accept negative numbers', () => { 36 | const result = validateParameter('-123'); 37 | expect(result.hasWarnings).toBe(false); 38 | expect(result.sanitized).toBe('-123'); 39 | }); 40 | 41 | test('should accept flags', () => { 42 | const result = validateParameter('-q'); 43 | expect(result.hasWarnings).toBe(false); 44 | expect(result.sanitized).toBe('-q'); 45 | }); 46 | 47 | test('should accept file paths', () => { 48 | const result = validateParameter('C:/path/to/file.txt'); 49 | expect(result.hasWarnings).toBe(false); 50 | expect(result.sanitized).toBe('C:/path/to/file.txt'); 51 | }); 52 | }); 53 | 54 | describe('Potentially dangerous patterns (warnings)', () => { 55 | test('should warn about semicolon', () => { 56 | const result = validateParameter('param; calc.exe'); 57 | expect(result.hasWarnings).toBe(true); 58 | expect(result.warnings[0]).toContain('shell metacharacters'); 59 | }); 60 | 61 | test('should warn about pipe', () => { 62 | const result = validateParameter('param | calc.exe'); 63 | expect(result.hasWarnings).toBe(true); 64 | expect(result.warnings[0]).toContain('shell metacharacters'); 65 | }); 66 | 67 | test('should warn about path traversal', () => { 68 | const result = validateParameter('../../../etc/passwd'); 69 | expect(result.hasWarnings).toBe(true); 70 | expect(result.warnings).toContainEqual(expect.stringContaining('path traversal')); 71 | }); 72 | 73 | test('should warn about null byte', () => { 74 | const result = validateParameter('param\0malicious'); 75 | expect(result.hasWarnings).toBe(true); 76 | expect(result.warnings).toContainEqual(expect.stringContaining('null bytes')); 77 | }); 78 | }); 79 | }); 80 | 81 | describe('validateParameterString', () => { 82 | describe('Safe parameter strings (no warnings)', () => { 83 | test('should accept AutoIt3Wrapper-style flags', () => { 84 | const result = validateParameterString('-q -d -w 1 -w 2 --verbose /run'); 85 | expect(result.hasWarnings).toBe(false); 86 | expect(result.sanitized).toEqual(['-q', '-d', '-w', '1', '-w', '2', '--verbose', '/run']); 87 | }); 88 | 89 | test('should accept quoted parameters', () => { 90 | const result = validateParameterString('"first param" "second param"'); 91 | expect(result.hasWarnings).toBe(false); 92 | expect(result.sanitized).toEqual(['first param', 'second param']); 93 | }); 94 | }); 95 | 96 | describe('Potentially dangerous parameter strings (warnings)', () => { 97 | test('should warn about shell metacharacters', () => { 98 | const result = validateParameterString('param1; calc.exe'); 99 | expect(result.hasWarnings).toBe(true); 100 | expect(result.warnings.length).toBeGreaterThan(0); 101 | }); 102 | 103 | test('should warn about command chaining', () => { 104 | const result = validateParameterString('normal_param && calc.exe'); 105 | expect(result.hasWarnings).toBe(true); 106 | expect(result.warnings[0]).toContain('shell metacharacters'); 107 | }); 108 | 109 | test('should still return sanitized parameters with warnings', () => { 110 | const result = validateParameterString('param1 "test; ls"'); 111 | expect(result.hasWarnings).toBe(true); 112 | expect(result.sanitized).toEqual(['param1', 'test; ls']); 113 | }); 114 | }); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /src/signatures/index.js: -------------------------------------------------------------------------------- 1 | import mainFunctions from './functions'; 2 | import keywords from './keywords'; 3 | import macros from './macros'; 4 | import debug from './udf_debug'; 5 | import word from './udf_word'; 6 | import winnet from './udf_winnet'; 7 | import WinAPITheme from './WinAPIEx/WinAPITheme'; 8 | import WinAPICom from './WinAPIEx/WinAPICom'; 9 | import WinAPIDiag from './WinAPIEx/WinAPIDiag'; 10 | import WinAPIDlg from './WinAPIEx/WinAPIDlg'; 11 | import WinAPIFiles from './WinAPIEx/WinAPIFiles'; 12 | import WinAPIGdi from './WinAPIEx/WinAPIGdi'; 13 | import WinAPILocale from './WinAPIEx/WinAPILocale'; 14 | import WinAPIMisc from './WinAPIEx/WinAPIMisc'; 15 | import WinAPIProc from './WinAPIEx/WinAPIProc'; 16 | import WinAPIReg from './WinAPIEx/WinAPIReg'; 17 | import WinAPIRes from './WinAPIEx/WinAPIRes'; 18 | import WinAPIShellEx from './WinAPIEx/WinAPIShellEx'; 19 | import WinAPIShPath from './WinAPIEx/WinAPIShPath'; 20 | import WinAPISys from './WinAPIEx/WinAPISys'; 21 | import udf_array from './udf_array'; 22 | import udf_clipboard from './udf_clipboard'; 23 | import udf_color from './udf_color'; 24 | import udf_crypt from './udf_crypt'; 25 | import udf_date from './udf_date'; 26 | import udf_eventlog from './udf_eventlog'; 27 | import udf_excel from './udf_excel'; 28 | import udf_file from './udf_file'; 29 | import udf_ftp from './udf_ftp'; 30 | import udf_gdiplus from './udf_gdiplus'; 31 | import udf_guictrlavi from './udf_guictrlavi'; 32 | import udf_guictrlbutton from './udf_guictrlbutton'; 33 | import udf_guictrlcombobox from './udf_guictrlcombobox'; 34 | import udf_guictrlcomboboxex from './udf_guictrlcomboboxex'; 35 | import udf_guictrldtp from './udf_guictrldtp'; 36 | import udf_guictrledit from './udf_guictrledit'; 37 | import udf_guictrlheader from './udf_guictrlheader'; 38 | import udf_guictrlipaddress from './udf_guictrlipaddress'; 39 | import udf_guictrllistbox from './udf_guictrllistbox'; 40 | import udf_guictrllistview from './udf_guictrllistview'; 41 | import udf_guictrlmenu from './udf_guictrlmenu'; 42 | import udf_guictrlmonthcal from './udf_guictrlmonthcal'; 43 | import udf_guictrlrebar from './udf_guictrlrebar'; 44 | import udf_guictrlrichedit from './udf_guictrlrichedit'; 45 | import udf_guictrlslider from './udf_guictrlslider'; 46 | import udf_guictrlstatusbar from './udf_guictrlstatusbar'; 47 | import udf_guictrltab from './udf_guictrltab'; 48 | import udf_guictrltoolbar from './udf_guictrltoolbar'; 49 | import udf_guictrltreeview from './udf_guictrltreeview'; 50 | import udf_guiimagelist from './udf_guiimagelist'; 51 | import udf_guiscrollbars from './udf_guiscrollbars'; 52 | import udf_guitooltip from './udf_guitooltip'; 53 | import udf_ie from './udf_ie'; 54 | import udf_inet from './udf_inet'; 55 | import udf_math from './udf_math'; 56 | import udf_memory from './udf_memory'; 57 | import udf_misc from './udf_misc'; 58 | import udf_namedpipes from './udf_namedpipes'; 59 | import udf_netshare from './udf_netshare'; 60 | import udf_process from './udf_process'; 61 | import udf_screencapture from './udf_screencapture'; 62 | import udf_security from './udf_security'; 63 | import udf_sendmessage from './udf_sendmessage'; 64 | import udf_sound from './udf_sound'; 65 | import udf_sqlite from './udf_sqlite'; 66 | import udf_string from './udf_string'; 67 | import udf_timers from './udf_timers'; 68 | import udf_visa from './udf_visa'; 69 | import udf_winapi from './udf_winapi'; 70 | 71 | const signatures = { 72 | ...mainFunctions, 73 | ...keywords, 74 | ...macros, 75 | ...debug, 76 | ...word, 77 | ...winnet, 78 | ...WinAPITheme, 79 | ...WinAPICom, 80 | ...WinAPIDiag, 81 | ...WinAPIDlg, 82 | ...WinAPIFiles, 83 | ...WinAPIGdi, 84 | ...WinAPILocale, 85 | ...WinAPIMisc, 86 | ...WinAPIProc, 87 | ...WinAPIReg, 88 | ...WinAPIRes, 89 | ...WinAPIShellEx, 90 | ...WinAPIShPath, 91 | ...WinAPISys, 92 | ...udf_array, 93 | ...udf_clipboard, 94 | ...udf_color, 95 | ...udf_crypt, 96 | ...udf_date, 97 | ...udf_eventlog, 98 | ...udf_excel, 99 | ...udf_file, 100 | ...udf_ftp, 101 | ...udf_gdiplus, 102 | ...udf_guictrlavi, 103 | ...udf_guictrlbutton, 104 | ...udf_guictrlcombobox, 105 | ...udf_guictrlcomboboxex, 106 | ...udf_guictrldtp, 107 | ...udf_guictrledit, 108 | ...udf_guictrlheader, 109 | ...udf_guictrlipaddress, 110 | ...udf_guictrllistbox, 111 | ...udf_guictrllistview, 112 | ...udf_guictrlmenu, 113 | ...udf_guictrlmonthcal, 114 | ...udf_guictrlrebar, 115 | ...udf_guictrlrichedit, 116 | ...udf_guictrlslider, 117 | ...udf_guictrlstatusbar, 118 | ...udf_guictrltab, 119 | ...udf_guictrltoolbar, 120 | ...udf_guictrltreeview, 121 | ...udf_guiimagelist, 122 | ...udf_guiscrollbars, 123 | ...udf_guitooltip, 124 | ...udf_ie, 125 | ...udf_inet, 126 | ...udf_math, 127 | ...udf_memory, 128 | ...udf_misc, 129 | ...udf_namedpipes, 130 | ...udf_netshare, 131 | ...udf_process, 132 | ...udf_screencapture, 133 | ...udf_security, 134 | ...udf_sendmessage, 135 | ...udf_sound, 136 | ...udf_sqlite, 137 | ...udf_string, 138 | ...udf_timers, 139 | ...udf_visa, 140 | ...udf_winapi, 141 | }; 142 | 143 | export default signatures; 144 | -------------------------------------------------------------------------------- /test/utils/pathValidation.test.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { 3 | validateFilePath, 4 | validateExecutablePath, 5 | fileExists, 6 | } = require('../../src/utils/pathValidation'); 7 | 8 | describe('pathValidation', () => { 9 | describe('validateFilePath', () => { 10 | it('should validate a normal file path', () => { 11 | const result = validateFilePath('test.au3'); 12 | expect(result.valid).toBe(true); 13 | expect(result.normalized).toBe('test.au3'); 14 | expect(result.error).toBeUndefined(); 15 | }); 16 | 17 | it('should reject null or empty paths', () => { 18 | expect(validateFilePath(null).valid).toBe(false); 19 | expect(validateFilePath('').valid).toBe(false); 20 | expect(validateFilePath(undefined).valid).toBe(false); 21 | }); 22 | 23 | it('should reject paths with null bytes', () => { 24 | const result = validateFilePath('test\0.au3'); 25 | expect(result.valid).toBe(false); 26 | expect(result.error).toContain('null bytes'); 27 | }); 28 | 29 | it('should normalize paths with . and ..', () => { 30 | const result = validateFilePath('./test/../test.au3'); 31 | expect(result.valid).toBe(true); 32 | expect(result.normalized).toBe('test.au3'); 33 | }); 34 | 35 | it('should detect path traversal outside workspace', () => { 36 | const workspaceRoot = path.resolve('/workspace'); 37 | const result = validateFilePath('../../etc/passwd', workspaceRoot); 38 | expect(result.valid).toBe(false); 39 | expect(result.error).toContain('path traversal'); 40 | }); 41 | 42 | it('should allow paths within workspace', () => { 43 | const workspaceRoot = path.resolve('/workspace'); 44 | const result = validateFilePath('subfolder/test.au3', workspaceRoot); 45 | expect(result.valid).toBe(true); 46 | }); 47 | 48 | it('should detect upward directory traversal', () => { 49 | const workspaceRoot = path.resolve('/workspace'); 50 | const result = validateFilePath('../../../sensitive/file.au3', workspaceRoot); 51 | expect(result.valid).toBe(false); 52 | expect(result.error).toContain('path traversal'); 53 | }); 54 | 55 | it('should handle absolute paths correctly', () => { 56 | const absolutePath = path.resolve('/workspace/test.au3'); 57 | const result = validateFilePath(absolutePath); 58 | expect(result.valid).toBe(true); 59 | }); 60 | 61 | it('should reject non-string paths', () => { 62 | // @ts-ignore 63 | // eslint-disable-next-line no-magic-numbers 64 | expect(validateFilePath(123).valid).toBe(false); 65 | // @ts-ignore 66 | expect(validateFilePath({}).valid).toBe(false); 67 | // @ts-ignore 68 | expect(validateFilePath([]).valid).toBe(false); 69 | }); 70 | }); 71 | 72 | describe('validateExecutablePath', () => { 73 | it('should reject non-existent executables', () => { 74 | const result = validateExecutablePath('/path/to/nonexistent.exe'); 75 | expect(result.valid).toBe(false); 76 | expect(result.error).toContain('does not exist'); 77 | }); 78 | 79 | it('should validate path structure even if file does not exist', () => { 80 | const result = validateExecutablePath('test\0.exe'); 81 | expect(result.valid).toBe(false); 82 | expect(result.error).toContain('null bytes'); 83 | }); 84 | }); 85 | 86 | describe('fileExists', () => { 87 | it('should return false for non-existent files', () => { 88 | expect(fileExists('/path/to/nonexistent.file')).toBe(false); 89 | }); 90 | 91 | it('should handle invalid paths gracefully', () => { 92 | expect(fileExists(null)).toBe(false); 93 | expect(fileExists('')).toBe(false); 94 | }); 95 | }); 96 | 97 | describe('path traversal attack scenarios', () => { 98 | const workspaceRoot = path.resolve('/workspace'); 99 | 100 | it('should block ../ traversal', () => { 101 | const result = validateFilePath('../../../etc/passwd', workspaceRoot); 102 | expect(result.valid).toBe(false); 103 | }); 104 | 105 | it('should block ..\\ traversal on Windows', () => { 106 | const result = validateFilePath('..\\..\\..\\windows\\system32\\config', workspaceRoot); 107 | expect(result.valid).toBe(false); 108 | }); 109 | 110 | it('should block mixed separator traversal', () => { 111 | const result = validateFilePath('../../../etc/passwd', workspaceRoot); 112 | expect(result.valid).toBe(false); 113 | }); 114 | 115 | it('should block encoded traversal attempts', () => { 116 | const result = validateFilePath('test\0poison.au3'); 117 | expect(result.valid).toBe(false); 118 | }); 119 | 120 | it('should allow relative paths within workspace', () => { 121 | const result = validateFilePath('./scripts/test.au3', workspaceRoot); 122 | expect(result.valid).toBe(true); 123 | }); 124 | 125 | it('should allow subdirectory paths', () => { 126 | const result = validateFilePath('subfolder/nested/test.au3', workspaceRoot); 127 | expect(result.valid).toBe(true); 128 | }); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /src/completions/constants_listview.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { fillCompletions } from '../util'; 3 | 4 | const items = [ 5 | { 6 | label: '$LVS_ICON', 7 | documentation: 'This style specifies icon view.', 8 | detail: 'ListView Style Constant', 9 | }, 10 | { 11 | label: '$LVS_REPORT', 12 | documentation: 'This style specifies report view.', 13 | detail: 'ListView Style Constant', 14 | }, 15 | { 16 | label: '$LVS_SMALLICON', 17 | documentation: 'This style specifies small icon view.', 18 | detail: 'ListView Style Constant', 19 | }, 20 | { 21 | label: '$LVS_LIST', 22 | documentation: 'This style specifies list view.', 23 | detail: 'ListView Style Constant', 24 | }, 25 | { 26 | label: '$LVS_EDITLABELS', 27 | documentation: 'Item text can be edited in place.', 28 | detail: 'ListView Style Constant', 29 | }, 30 | { 31 | label: '$LVS_NOCOLUMNHEADER', 32 | documentation: 33 | 'Column headers are not displayed in report view. By default, columns have headers in report view.', 34 | detail: 'ListView Style Constant', 35 | }, 36 | { 37 | label: '$LVS_NOSORTHEADER', 38 | documentation: 39 | 'Column headers do not work like buttons. This style can be used if clicking a column header in report view does not carry out an action, such as sorting.', 40 | detail: 'ListView Style Constant', 41 | }, 42 | { 43 | label: '$LVS_SINGLESEL', 44 | documentation: 'Only one item at a time can be selected.', 45 | detail: 'ListView Style Constant', 46 | }, 47 | { 48 | label: '$LVS_SHOWSELALWAYS', 49 | documentation: 50 | 'The selection, if any, is always shown, even if the control does not have the focus.', 51 | detail: 'ListView Style Constant', 52 | }, 53 | { 54 | label: '$LVS_SORTASCENDING', 55 | documentation: 'Item indices are sorted based on item text in ascending order.', 56 | detail: 'ListView Style Constant', 57 | }, 58 | { 59 | label: '$LVS_SORTDESCENDING', 60 | documentation: 'Item indices are sorted based on item text in descending order.', 61 | detail: 'ListView Style Constant', 62 | }, 63 | { 64 | label: '$LVS_NOLABELWRAP', 65 | documentation: 66 | 'Item text is displayed on a single line in icon view. By default, item text may wrap in icon view.', 67 | detail: 'ListView Style Constant', 68 | }, 69 | { 70 | label: '$LVS_EX_FULLROWSELECT', 71 | documentation: 'When an item is selected, the item and all its subitems are highlighted.', 72 | detail: 'ListView Extended Style Constant', 73 | }, 74 | { 75 | label: '$LVS_EX_GRIDLINES', 76 | documentation: 'Displays gridlines around items and subitems.', 77 | detail: 'ListView Extended Style Constant', 78 | }, 79 | { 80 | label: '$LVS_EX_HEADERDRAGDROP', 81 | documentation: 'Enables drag-and-drop reordering of columns in a list view control.', 82 | detail: 'ListView Extended Style Constant', 83 | }, 84 | { 85 | label: '$LVS_EX_TRACKSELECT', 86 | documentation: 87 | 'Enables hot-track selection in a list view control. Hot track selection means that an item is automatically selected when the cursor remains over the item for a certain period of time', 88 | detail: 'ListView Extended Style Constant', 89 | }, 90 | { 91 | label: '$LVS_EX_CHECKBOXES', 92 | documentation: 'Enables check boxes for items in a list view control.', 93 | detail: 'ListView Extended Style Constant', 94 | }, 95 | { 96 | label: '$LVS_EX_BORDERSELECT', 97 | documentation: 98 | 'If this style is set, when an item is selected the border color of the item changes rather than the item being highlighted.', 99 | detail: 'ListView Extended Style Constant', 100 | }, 101 | { 102 | label: '$LVS_EX_DOUBLEBUFFER', 103 | documentation: 104 | 'Paints via double-buffering, which reduces flicker. This extended style also enables alpha-blended marquee selection on systems where it is supported.', 105 | detail: 'ListView Extended Style Constant', 106 | }, 107 | { 108 | label: '$LVS_EX_FLATSB', 109 | documentation: 'Enables flat scroll bars in the list view.', 110 | detail: 'ListView Extended Style Constant', 111 | }, 112 | { 113 | label: '$LVS_EX_MULTIWORKAREAS', 114 | documentation: 115 | 'The control will not autoarrange its icons until one or more work areas are defined.', 116 | detail: 'ListView Extended Style Constant', 117 | }, 118 | { 119 | label: '$LVS_EX_SNAPTOGRID', 120 | documentation: 'In icon view, icons automatically snap into a grid.', 121 | detail: 'ListView Extended Style Constant', 122 | }, 123 | { 124 | label: '$LVS_EX_SUBITEMIMAGES', 125 | documentation: 'Allows images to be displayed for subitems.', 126 | detail: 'ListView Extended Style Constant', 127 | }, 128 | { 129 | label: '$LVS_EX_INFOTIP', 130 | documentation: 131 | 'Displays a tooltip when the item is not fully visible. Sends a notification message to $LVN_GETINFOTIP', 132 | detail: 'ListView Extended Style Constant', 133 | }, 134 | ]; 135 | 136 | export default fillCompletions(items, CompletionItemKind.Constant, '', 'ListViewConstants.au3'); 137 | -------------------------------------------------------------------------------- /docs/map-support.md: -------------------------------------------------------------------------------- 1 | # AutoIt Map Variable Support 2 | 3 | This document describes the Map variable intelligence features in the AutoIt-VSCode extension. 4 | 5 | ## Overview 6 | 7 | AutoIt Maps are associative arrays that use key-value pairs. This extension provides intelligent IntelliSense for Map keys based on static analysis of your code. 8 | 9 | ## Features 10 | 11 | ### 1. Key Completion 12 | 13 | When you type `$mapVariable.`, the extension analyzes your code to suggest available keys. 14 | 15 | **Example:** 16 | 17 | ```autoit 18 | Local $mUser[] 19 | $mUser.name = "John" 20 | $mUser.email = "john@example.com" 21 | 22 | ; Type: $mUser. 23 | ; Suggestions: name, email 24 | ``` 25 | 26 | ### 2. Scope Awareness 27 | 28 | The extension respects variable scoping rules and provides suggestions based on the closest declaration. 29 | 30 | **Example:** 31 | 32 | ```autoit 33 | Global $mConfig[] 34 | $mConfig.apiKey = "global-key" 35 | 36 | Func DoWork() 37 | Local $mConfig[] ; Shadows global 38 | $mConfig.tempData = "local-data" 39 | 40 | ; Type: $mConfig. 41 | ; Suggestions: tempData (only local scope) 42 | EndFunc 43 | ``` 44 | 45 | ### 3. Cross-File Tracking 46 | 47 | The extension follows `#include` directives to merge Map definitions from multiple files. 48 | 49 | **Example:** 50 | 51 | `config.au3`: 52 | 53 | ```autoit 54 | Global $mApp[] 55 | $mApp.name = "MyApp" 56 | $mApp.version = "1.0" 57 | ``` 58 | 59 | `main.au3`: 60 | 61 | ```autoit 62 | #include "config.au3" 63 | 64 | $mApp.debugMode = True 65 | 66 | ; Type: $mApp. 67 | ; Suggestions: name, version, debugMode 68 | ``` 69 | 70 | ### 4. Function Parameter Tracking 71 | 72 | Keys added to Map parameters within functions are tracked and suggested with lower priority. 73 | 74 | **Example:** 75 | 76 | ```autoit 77 | Func AddUserData($userMap) 78 | $userMap.name = "Default" 79 | $userMap.id = 0 80 | EndFunc 81 | 82 | Local $mUser[] 83 | AddUserData($mUser) 84 | 85 | ; Type: $mUser. 86 | ; Suggestions: name, id (marked as "added in function") 87 | ``` 88 | 89 | ## Configuration 90 | 91 | ### `autoit.maps.enableIntelligence` 92 | 93 | **Type:** `boolean` 94 | **Default:** `true` 95 | 96 | Enable or disable Map key completions. 97 | 98 | ### `autoit.maps.includeDepth` 99 | 100 | **Type:** `number` 101 | **Default:** `3` 102 | 103 | Maximum depth for resolving `#include` files. Higher values allow deeper include chains but may impact performance. 104 | 105 | ### `autoit.maps.showFunctionKeys` 106 | 107 | **Type:** `boolean` 108 | **Default:** `true` 109 | 110 | Show keys that are added when Maps are passed to function parameters. These are marked with lower confidence. 111 | 112 | ### Recommendations 113 | 114 | When tuning these settings, consider the trade-offs between feature completeness and editor performance: 115 | 116 | • **includeDepth**: For large repositories or remote workspaces (WSL, SSH), reduce to 1–2 to improve responsiveness by limiting parsed files during include resolution. 117 | 118 | • **enableIntelligence**: In very large projects or CI environments where IntelliSense isn't critical, consider disabling to minimize resource usage. 119 | 120 | • **showFunctionKeys**: Keep enabled for more accurate completions, but can be disabled if you prefer fewer low-confidence suggestions. 121 | 122 | • **Monitoring**: Watch editor memory usage and response time, then adjust settings iteratively based on your workspace characteristics. 123 | 124 | What works well for a small local project may need tuning for large multi-file codebases. 125 | 126 | ## How It Works 127 | 128 | The extension uses static analysis to: 129 | 130 | 1. **Parse Map declarations**: Detect `Local/Global/Dim/Static $var[]` patterns 131 | 2. **Track key assignments**: Find `$map.key = value` and `$map["key"] = value` patterns 132 | 3. **Analyze function boundaries**: Determine scope and track function parameters 133 | 4. **Resolve includes**: Follow `#include` directives to merge definitions 134 | 5. **Provide completions**: Suggest keys based on scope and position 135 | 136 | ## Limitations 137 | 138 | - **Single-level Maps only**: Nested Map keys (e.g., `$map.user.name`) are not yet supported 139 | - **Static analysis**: Only detects keys assigned via direct syntax, not dynamic keys 140 | - **Function tracking**: Limited to same-file functions initially 141 | - **Performance**: Very large workspaces may experience delays (see configuration) 142 | 143 | ## Future Enhancements 144 | 145 | - Nested Map support (`$map.level1.level2`) 146 | - Cross-file function parameter tracking 147 | - Dynamic key detection from MapAppend calls 148 | - Map type inference and validation 149 | 150 | ## Troubleshooting 151 | 152 | **No completions appearing:** 153 | 154 | - Check `autoit.maps.enableIntelligence` is `true` 155 | - Ensure Map is declared with `[]` syntax: `Local $mVar[]` 156 | - Verify file is recognized as AutoIt (check language mode) 157 | 158 | **Missing keys from included files:** 159 | 160 | - Check `autoit.maps.includeDepth` setting 161 | - Verify include paths are correct 162 | - Check `autoit.includePaths` setting for library includes 163 | 164 | **Performance issues:** 165 | 166 | - Reduce `autoit.maps.includeDepth` 167 | - Disable `autoit.maps.showFunctionKeys` if not needed 168 | -------------------------------------------------------------------------------- /src/ai_workspaceSymbols.js: -------------------------------------------------------------------------------- 1 | import { languages, window, workspace } from 'vscode'; 2 | import { provideDocumentSymbols } from './ai_symbols'; 3 | 4 | // Map of file URI to symbols for incremental updates 5 | const symbolsCache = new Map(); 6 | 7 | /** 8 | * Process files in batches to prevent UI freezing on large workspaces. 9 | * @param {Array} files - Array of file URIs to process 10 | * @param {number} batchSize - Number of files to process per batch 11 | * @param {CancellationToken} token - Cancellation token to abort processing 12 | * @returns {Promise} Map of file URI to symbols 13 | */ 14 | async function processBatch(files, batchSize, token) { 15 | const results = new Map(); 16 | 17 | for (let i = 0; i < files.length; i += batchSize) { 18 | // Check for cancellation 19 | if (token?.isCancellationRequested) { 20 | break; 21 | } 22 | 23 | const batch = files.slice(i, i + batchSize); 24 | 25 | // Process batch in parallel 26 | const batchResults = await Promise.all( 27 | batch.map(async file => { 28 | try { 29 | const document = await workspace.openTextDocument(file); 30 | const symbols = provideDocumentSymbols(document); 31 | return { uri: file.toString(), symbols }; 32 | } catch { 33 | // Skip files that can't be opened 34 | return null; 35 | } 36 | }), 37 | ); 38 | 39 | // Add batch results to map 40 | batchResults.forEach(result => { 41 | if (result && result.symbols) { 42 | results.set(result.uri, result.symbols); 43 | } 44 | }); 45 | 46 | // Yield control to prevent UI freezing 47 | await new Promise(resolve => setImmediate(resolve)); 48 | } 49 | 50 | return results; 51 | } 52 | 53 | /** 54 | * Fetches symbols for all AutoIt scripts in the workspace. 55 | * Uses batch processing to prevent UI freezing on large projects. 56 | * 57 | * @param {CancellationToken} token - Cancellation token 58 | * @returns {Promise} A promise that resolves to a map of file URI to symbols. 59 | */ 60 | async function getWorkspaceSymbols(token) { 61 | try { 62 | const config = workspace.getConfiguration('autoit'); 63 | const maxFiles = config.get('workspaceSymbolMaxFiles', 500); 64 | const batchSize = config.get('workspaceSymbolBatchSize', 10); 65 | 66 | const workspaceScripts = await workspace.findFiles('**/*.{au3,a3x}'); 67 | 68 | // Limit number of files to process 69 | const filesToProcess = workspaceScripts.slice(0, maxFiles); 70 | 71 | if (workspaceScripts.length > maxFiles) { 72 | window.showWarningMessage( 73 | `AutoIt: Processing ${maxFiles} of ${workspaceScripts.length} files. Increase 'autoit.workspaceSymbolMaxFiles' to index more files.`, 74 | ); 75 | } 76 | 77 | return await processBatch(filesToProcess, batchSize, token); 78 | } catch (error) { 79 | window.showErrorMessage(error.message || 'Error fetching workspace symbols'); 80 | return new Map(); 81 | } 82 | } 83 | 84 | /** 85 | * Provides symbols for the entire workspace, using a cached version if available. 86 | * Supports cancellation and query-based filtering. 87 | * 88 | * @param {string} query - The search query (optional) 89 | * @param {CancellationToken} token - Cancellation token 90 | * @returns {Promise} A promise that resolves to an array of workspace symbols. 91 | */ 92 | async function provideWorkspaceSymbols(query, token) { 93 | // Build cache if empty 94 | if (symbolsCache.size === 0) { 95 | const symbols = await getWorkspaceSymbols(token); 96 | 97 | // Only update cache if not cancelled 98 | if (!token?.isCancellationRequested) { 99 | symbols.forEach((value, key) => { 100 | symbolsCache.set(key, value); 101 | }); 102 | } 103 | } 104 | 105 | // Return early if cancelled 106 | if (token?.isCancellationRequested) { 107 | return []; 108 | } 109 | 110 | // Flatten cache into array 111 | const allSymbols = Array.from(symbolsCache.values()).flat(); 112 | 113 | // Filter by query if provided 114 | if (query && query.length > 0) { 115 | const lowerQuery = query.toLowerCase(); 116 | return allSymbols.filter(symbol => symbol.name.toLowerCase().includes(lowerQuery)); 117 | } 118 | 119 | return allSymbols; 120 | } 121 | 122 | const watcher = workspace.createFileSystemWatcher('**/*.{au3,a3x}'); 123 | 124 | /** 125 | * Update symbols for a specific file instead of clearing entire cache. 126 | * @param {Uri} uri - The file URI that changed 127 | */ 128 | async function updateFileSymbols(uri) { 129 | try { 130 | const document = await workspace.openTextDocument(uri); 131 | const symbols = provideDocumentSymbols(document); 132 | symbolsCache.set(uri.toString(), symbols); 133 | } catch { 134 | // If file can't be opened, remove from cache 135 | symbolsCache.delete(uri.toString()); 136 | } 137 | } 138 | 139 | /** 140 | * Remove a file from the cache when deleted. 141 | * @param {Uri} uri - The file URI that was deleted 142 | */ 143 | function removeFileSymbols(uri) { 144 | symbolsCache.delete(uri.toString()); 145 | } 146 | 147 | // Incremental cache updates instead of full invalidation 148 | watcher.onDidChange(updateFileSymbols); 149 | watcher.onDidCreate(updateFileSymbols); 150 | watcher.onDidDelete(removeFileSymbols); 151 | 152 | const workspaceSymbolProvider = languages.registerWorkspaceSymbolProvider({ 153 | provideWorkspaceSymbols, 154 | }); 155 | 156 | export default workspaceSymbolProvider; 157 | -------------------------------------------------------------------------------- /src/completions/constants_msgbox.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { fillCompletions } from '../util'; 3 | 4 | const flagParam = 'MsgBox Constant Flag Parameter'; 5 | const returnValue = 'MsgBox Constant Return Value'; 6 | 7 | const items = [ 8 | { 9 | label: '$MB_OK', 10 | documentation: 'One push button: OK', 11 | detail: flagParam, 12 | }, 13 | { 14 | label: '$MB_OKCANCEL', 15 | documentation: 'Two push buttons: OK and Cancel', 16 | detail: flagParam, 17 | }, 18 | { 19 | label: '$MB_ABORTRETRYIGNORE', 20 | documentation: 'Three push buttons: Abort, Retry, and Ignore', 21 | detail: flagParam, 22 | }, 23 | { 24 | label: '$MB_YESNOCANCEL', 25 | documentation: 'Three push buttons: Yes, No, and Cancel', 26 | detail: flagParam, 27 | }, 28 | { 29 | label: '$MB_YESNO', 30 | documentation: 'Two push buttons: Yes and No', 31 | detail: flagParam, 32 | }, 33 | { 34 | label: '$MB_RETRYCANCEL', 35 | documentation: 'Two push buttons: Retry and Cancel', 36 | detail: flagParam, 37 | }, 38 | { 39 | label: '$MB_CANCELTRYCONTINUE', 40 | documentation: 'Three buttons: Cancel, Try Again and Continue', 41 | detail: flagParam, 42 | }, 43 | { 44 | label: '$MB_HELP', 45 | documentation: 46 | 'Adds a Help button to the message box. When the user clicks the Help button or presses F1, the system sends a WM_HELP message to the owner.', 47 | detail: flagParam, 48 | }, 49 | { 50 | label: '$MB_ICONERROR', 51 | documentation: 'Stop-sign icon.', 52 | detail: flagParam, 53 | }, 54 | { 55 | label: '$MB_ICONQUESTION', 56 | documentation: 'Question-mark icon', 57 | detail: flagParam, 58 | }, 59 | { 60 | label: '$MB_ICONWARNING', 61 | documentation: 'Exclamation-point icon', 62 | detail: flagParam, 63 | }, 64 | { 65 | label: '$MB_ICONINFORMATION', 66 | documentation: "Information-sign icon consisting of an 'i' in a circle", 67 | detail: flagParam, 68 | }, 69 | { 70 | label: '$MB_DEFBUTTON1', 71 | documentation: 'First button is default button', 72 | detail: flagParam, 73 | }, 74 | { 75 | label: '$MB_DEFBUTTON2', 76 | documentation: 'Second button is default button', 77 | detail: flagParam, 78 | }, 79 | { 80 | label: '$MB_DEFBUTTON3', 81 | documentation: 'Third button is default button', 82 | detail: flagParam, 83 | }, 84 | { 85 | label: '$MB_DEFBUTTON4', 86 | documentation: 'Fourth button is default button', 87 | detail: flagParam, 88 | }, 89 | { 90 | label: '$MB_APPLMODAL', 91 | documentation: 'Application modal', 92 | detail: flagParam, 93 | }, 94 | { 95 | label: '$MB_SYSTEMMODAL', 96 | documentation: 'System modal (dialog has an icon)', 97 | detail: flagParam, 98 | }, 99 | { 100 | label: '$MB_TASKMODAL', 101 | documentation: 'Task modal', 102 | detail: flagParam, 103 | }, 104 | { 105 | label: '$MB_DEFAULT_DESKTOP_ONLY', 106 | documentation: 'MsgBox() shows on the desktop of the interactive window station.', 107 | detail: flagParam, 108 | }, 109 | { 110 | label: '$MB_RIGHT', 111 | documentation: 'Title and text are right-justified', 112 | detail: flagParam, 113 | }, 114 | { 115 | label: '$MB_RTLREADING', 116 | documentation: 117 | 'Displays message and caption text using right-to-left reading order on Hebrew and Arabic systems.', 118 | detail: flagParam, 119 | }, 120 | { 121 | label: '$MB_SETFOREGROUND', 122 | documentation: 'The message box becomes the foreground window.', 123 | detail: flagParam, 124 | }, 125 | { 126 | label: '$MB_TOPMOST', 127 | documentation: 'MsgBox() has top-most attribute set', 128 | detail: flagParam, 129 | }, 130 | { 131 | label: '$MB_SERVICE_NOTIFICATION', 132 | documentation: 133 | 'The function displays a message box on the current active desktop, even if there is no user logged on to the computer.', 134 | detail: flagParam, 135 | }, 136 | { 137 | label: '$IDOK', 138 | documentation: 'OK button was selected', 139 | detail: returnValue, 140 | }, 141 | { 142 | label: '$IDCANCEL', 143 | documentation: 'Cancel button was selected', 144 | detail: returnValue, 145 | }, 146 | { 147 | label: '$IDABORT', 148 | documentation: 'Abort button was selected', 149 | detail: returnValue, 150 | }, 151 | { 152 | label: '$IDRETRY', 153 | documentation: 'Retry button was selected', 154 | detail: returnValue, 155 | }, 156 | { 157 | label: '$IDIGNORE', 158 | documentation: 'Ignore button was selected', 159 | detail: returnValue, 160 | }, 161 | { 162 | label: '$IDYES', 163 | documentation: 'Yes button was selected', 164 | detail: returnValue, 165 | }, 166 | { 167 | label: '$IDNO', 168 | documentation: 'No button was selected', 169 | detail: returnValue, 170 | }, 171 | { 172 | label: '$IDCLOSE', 173 | documentation: 'Close button was selected', 174 | detail: returnValue, 175 | }, 176 | { 177 | label: '$IDHELP', 178 | documentation: 'Help button was selected', 179 | detail: returnValue, 180 | }, 181 | { 182 | label: '$IDTRYAGAIN', 183 | documentation: 'Try Again button was selected', 184 | detail: returnValue, 185 | }, 186 | { 187 | label: '$IDTRYCONTINUE', 188 | documentation: 'Continue button was selected', 189 | detail: returnValue, 190 | }, 191 | ]; 192 | 193 | export default fillCompletions(items, CompletionItemKind.Constant, '', 'MsgBoxConstants.au3'); 194 | -------------------------------------------------------------------------------- /src/utils/parameterValidation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parameter validation utilities to detect potentially dangerous patterns in user parameters. 3 | * 4 | * These functions check user-supplied parameters passed to AutoIt scripts and warn 5 | * about potentially dangerous patterns, but allow developers to proceed. 6 | */ 7 | 8 | /** 9 | * Checks a single parameter for potentially dangerous patterns. 10 | * 11 | * Since parameters are passed after /UserParams to the user's script (not to the shell 12 | * or AutoIt3Wrapper), and developers often need to test how their scripts handle 13 | * various inputs, this function warns but doesn't block. 14 | * 15 | * Parameters are passed via Node's spawn() as an array, which prevents shell interpretation. 16 | * However, we still warn about patterns that could be problematic in certain contexts. 17 | * 18 | * Warning patterns: 19 | * - Shell metacharacters for command chaining/injection: ; | & $ ` < > ( ) [ ] { } 20 | * - Glob/wildcards that could expand: * ? 21 | * - Path traversal patterns: ../ 22 | * - Control characters and null bytes 23 | * 24 | * @param {string} param - The parameter to check 25 | * @returns {{hasWarnings: boolean, sanitized: string, warnings: string[]}} Validation result 26 | */ 27 | function validateParameter(param) { 28 | const warnings = []; 29 | 30 | if (!param || typeof param !== 'string') { 31 | return { 32 | hasWarnings: false, 33 | sanitized: '', 34 | warnings: [], 35 | }; 36 | } 37 | 38 | // Trim whitespace 39 | const trimmed = param.trim(); 40 | 41 | if (trimmed.length === 0) { 42 | return { 43 | hasWarnings: false, 44 | sanitized: '', 45 | warnings: [], 46 | }; 47 | } 48 | 49 | // Check for null bytes (common injection technique) 50 | if (trimmed.includes('\0')) { 51 | warnings.push('Contains null bytes'); 52 | } 53 | 54 | // Check for shell metacharacters that could enable command injection 55 | // These characters allow breaking out of the command context: 56 | // ; (command chaining) 57 | // | (piping) 58 | // & (background/chaining) 59 | // $ (variable expansion in some shells) 60 | // ` (command substitution) 61 | // < > (redirection) 62 | // ( ) [ ] { } (grouping/subshells) 63 | // * ? (glob expansion) 64 | // \ (escape sequences - can be used to bypass filters) 65 | const dangerousChars = /[;|&$`\\<>()[\]{}*?]/; 66 | if (dangerousChars.test(trimmed)) { 67 | warnings.push('Contains shell metacharacters (; | & $ ` \\ < > ( ) [ ] { } * ?)'); 68 | } 69 | 70 | // Check for path traversal patterns 71 | if (trimmed.includes('../') || trimmed.includes('..\\')) { 72 | warnings.push('Contains path traversal pattern (../)'); 73 | } 74 | 75 | // Check for control characters (except newline and tab which we reject) 76 | // eslint-disable-next-line no-control-regex 77 | const controlChars = /[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/; 78 | if (controlChars.test(trimmed)) { 79 | warnings.push('Contains control characters'); 80 | } 81 | 82 | return { 83 | hasWarnings: warnings.length > 0, 84 | sanitized: trimmed, 85 | warnings, 86 | }; 87 | } 88 | 89 | /** 90 | * Checks a parameter string containing multiple parameters for potentially dangerous patterns. 91 | * Parameters can be space-separated or quoted. 92 | * 93 | * @param {string} paramsString - The parameter string from configuration 94 | * @returns {{hasWarnings: boolean, sanitized: string[], warnings: string[]}} Validation result 95 | */ 96 | function validateParameterString(paramsString) { 97 | if (!paramsString || typeof paramsString !== 'string') { 98 | return { 99 | hasWarnings: false, 100 | sanitized: [], 101 | warnings: [], 102 | }; 103 | } 104 | 105 | // Parse parameters by splitting on spaces, but preserve quoted strings 106 | // This captures ALL content including dangerous characters so we can check them 107 | const paramArray = []; 108 | let current = ''; 109 | let inQuote = false; 110 | 111 | for (let i = 0; i < paramsString.length; i++) { 112 | const char = paramsString[i]; 113 | 114 | if (char === '"') { 115 | if (inQuote) { 116 | // End of quoted string - add it 117 | paramArray.push(current); 118 | current = ''; 119 | inQuote = false; 120 | } else { 121 | // Start of quoted string 122 | inQuote = true; 123 | } 124 | } else if (char === ' ' && !inQuote) { 125 | // Space outside quotes - separator 126 | if (current.length > 0) { 127 | paramArray.push(current); 128 | current = ''; 129 | } 130 | } else { 131 | // Regular character 132 | current += char; 133 | } 134 | } 135 | 136 | // Add any remaining content 137 | if (current.length > 0) { 138 | paramArray.push(current); 139 | } 140 | 141 | if (paramArray.length === 0) { 142 | return { 143 | hasWarnings: false, 144 | sanitized: [], 145 | warnings: [], 146 | }; 147 | } 148 | 149 | const sanitized = []; 150 | const allWarnings = []; 151 | 152 | for (const param of paramArray) { 153 | const validation = validateParameter(param); 154 | sanitized.push(validation.sanitized); 155 | 156 | if (validation.hasWarnings) { 157 | allWarnings.push(`Parameter "${param}": ${validation.warnings.join(', ')}`); 158 | } 159 | } 160 | 161 | return { 162 | hasWarnings: allWarnings.length > 0, 163 | sanitized, 164 | warnings: allWarnings, 165 | }; 166 | } 167 | 168 | module.exports = { 169 | validateParameter, 170 | validateParameterString, 171 | }; 172 | -------------------------------------------------------------------------------- /src/signatures/udf_screencapture.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { 3 | br, 4 | valueFirstHeader as header, 5 | opt, 6 | signatureToCompletion, 7 | signatureToHover, 8 | } from '../util'; 9 | 10 | const include = '(Requires: `#include `)'; 11 | 12 | const signatures = { 13 | _ScreenCapture_Capture: { 14 | documentation: 'Captures a region of the screen', 15 | label: 16 | '_ScreenCapture_Capture ( [$sFileName = "" [, $iLeft = 0 [, $iTop = 0 [, $iRight = -1 [, $iBottom = -1 [, $bCursor = True]]]]]] )', 17 | params: [ 18 | { 19 | label: '$sFileName', 20 | documentation: '**[optional]** Full path and extension of the image file', 21 | }, 22 | { 23 | label: '$iLeft', 24 | documentation: '**[optional]** X coordinate of the upper left corner of the rectangle', 25 | }, 26 | { 27 | label: '$iTop', 28 | documentation: '**[optional]** Y coordinate of the upper left corner of the rectangle', 29 | }, 30 | { 31 | label: '$iRight', 32 | documentation: 33 | '**[optional]** X coordinate of the lower right corner of the rectangle. If this is -1, the current screen width will be used.', 34 | }, 35 | { 36 | label: '$iBottom', 37 | documentation: 38 | '**[optional]** Y coordinate of the lower right corner of the rectangle. If this is -1, the current screen height will be used.', 39 | }, 40 | { 41 | label: '$bCursor', 42 | documentation: '**[optional]** If True the cursor will be captured with the image', 43 | }, 44 | ], 45 | }, 46 | _ScreenCapture_CaptureWnd: { 47 | documentation: 'Captures a screen shot of a specified window or controlID', 48 | label: 49 | '_ScreenCapture_CaptureWnd ( $sFileName, $hWnd [, $iLeft = 0 [, $iTop = 0 [, $iRight = -1 [, $iBottom = -1 [, $bCursor = True]]]]] )', 50 | params: [ 51 | { 52 | label: '$sFileName', 53 | documentation: 'Full path and extension of the image file', 54 | }, 55 | { 56 | label: '$hWnd', 57 | documentation: 'Handle to the window to be captured', 58 | }, 59 | { 60 | label: '$iLeft', 61 | documentation: 62 | '**[optional]** X coordinate of the upper left corner of the client rectangle', 63 | }, 64 | { 65 | label: '$iTop', 66 | documentation: 67 | '**[optional]** Y coordinate of the upper left corner of the client rectangle', 68 | }, 69 | { 70 | label: '$iRight', 71 | documentation: '**[optional]** X coordinate of the lower right corner of the rectangle', 72 | }, 73 | { 74 | label: '$iBottom', 75 | documentation: '**[optional]** Y coordinate of the lower right corner of the rectangle', 76 | }, 77 | { 78 | label: '$bCursor', 79 | documentation: '**[optional]** If True the cursor will be captured with the image', 80 | }, 81 | ], 82 | }, 83 | _ScreenCapture_SaveImage: { 84 | documentation: 'Saves an image to file', 85 | label: '_ScreenCapture_SaveImage ( $sFileName, $hBitmap [, $bFreeBmp = True] )', 86 | params: [ 87 | { 88 | label: '$sFileName', 89 | documentation: 'Full path and extension of the bitmap file to be saved', 90 | }, 91 | { 92 | label: '$hBitmap', 93 | documentation: 'HBITMAP handle', 94 | }, 95 | { 96 | label: '$bFreeBmp', 97 | documentation: 98 | '**[optional]** If True, $hBitmap will be freed on a successful save (default)', 99 | }, 100 | ], 101 | }, 102 | _ScreenCapture_SetBMPFormat: { 103 | documentation: 'Sets the bit format that will be used for BMP screen captures', 104 | label: '_ScreenCapture_SetBMPFormat ( $iFormat )', 105 | params: [ 106 | { 107 | label: '$iFormat', 108 | documentation: 109 | 'Image bits per pixel (bpp) setting:    0 = 16 bpp; 5 bits for each RGB component    1 = 16 bpp; 5 bits for red, 6 bits for green and 5 bits blue    2 = 24 bpp; 8 bits for each RGB component    3 = 32 bpp; 8 bits for each RGB component. No alpha component.    4 = 32 bpp; 8 bits for each RGB and alpha component', 110 | }, 111 | ], 112 | }, 113 | _ScreenCapture_SetJPGQuality: { 114 | documentation: 'Sets the quality level that will be used for JPEG screen captures', 115 | label: '_ScreenCapture_SetJPGQuality ( $iQuality )', 116 | params: [ 117 | { 118 | label: '$iQuality', 119 | documentation: 'The quality level of the image. Must be in the range of 0 to 100.', 120 | }, 121 | ], 122 | }, 123 | _ScreenCapture_SetTIFColorDepth: { 124 | documentation: 'Sets the color depth used for TIFF screen captures', 125 | label: '_ScreenCapture_SetTIFColorDepth ( $iDepth )', 126 | params: [ 127 | { 128 | label: '$iDepth', 129 | documentation: 130 | 'Image color depth:    0 - Default encoder color depth    24 - 24 bit    32 - 32 bit', 131 | }, 132 | ], 133 | }, 134 | _ScreenCapture_SetTIFCompression: { 135 | documentation: 'Sets the compression used for TIFF screen captures', 136 | label: '_ScreenCapture_SetTIFCompression ( $iCompress )', 137 | params: [ 138 | { 139 | label: '$iCompress', 140 | documentation: 141 | 'Image compression type:    0 - Default encoder compression    1 - No compression    2 - LZW compression', 142 | }, 143 | ], 144 | }, 145 | }; 146 | 147 | const hovers = signatureToHover(signatures); 148 | const completions = signatureToCompletion(signatures, CompletionItemKind.Function, include); 149 | 150 | export { signatures as default, hovers, completions }; 151 | -------------------------------------------------------------------------------- /src/commands/DebugCommands.js: -------------------------------------------------------------------------------- 1 | const { window, Position } = require('vscode'); 2 | 3 | /** 4 | * Configuration for debug code templates. 5 | * These can be customized as needed. 6 | */ 7 | const DEBUG_TEMPLATES = { 8 | MSGBOX: { 9 | PREFIX: ';### Debug MSGBOX ↓↓↓', 10 | CODE: "MsgBox(262144, 'Debug line ~' & @ScriptLineNumber, 'Selection:' & @CRLF & '{VAR}' & @CRLF & @CRLF & 'Return:' & @CRLF & {VAR})", 11 | }, 12 | CONSOLE: { 13 | PREFIX: ';### Debug CONSOLE ↓↓↓', 14 | CODE: "ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : {VAR} = ' & {VAR} & @CRLF & '>Error code: ' & @error & @CRLF)", 15 | }, 16 | }; 17 | 18 | /** 19 | * Gets debug information for the selected variable or macro 20 | * @returns {{text: string, position: Position}|{}} Debug info object or empty object 21 | */ 22 | function getDebugText() { 23 | const editor = window.activeTextEditor; 24 | if (!editor) { 25 | throw new Error('No active text editor found'); 26 | } 27 | 28 | const thisDoc = editor.document; 29 | let lineNbr = editor.selection.active.line; 30 | let currentLine = thisDoc.lineAt(lineNbr); 31 | const wordRange = editor.document.getWordRangeAtPosition(editor.selection.start); 32 | const varToDebug = !wordRange 33 | ? '' 34 | : thisDoc.getText(thisDoc.getWordRangeAtPosition(editor.selection.active)); 35 | 36 | // Validate that a variable or macro is selected 37 | if (!varToDebug || (varToDebug.charAt(0) !== '$' && varToDebug.charAt(0) !== '@')) { 38 | throw new Error( 39 | `"${varToDebug}" is not a valid variable or macro. Debug line cannot be generated.`, 40 | ); 41 | } 42 | 43 | const LINE_COUNT_OFFSET = 2; 44 | const lineCount = thisDoc.lineCount - LINE_COUNT_OFFSET; 45 | const isContinue = /\s_\b\s*(;.*)?\s*/; 46 | 47 | // Check if not the last line 48 | if (lineNbr < thisDoc.lineCount - 1) { 49 | // Find first line without continuation character 50 | while (lineNbr <= lineCount) { 51 | const noContinue = isContinue.exec(currentLine.text) === null; 52 | if (noContinue) { 53 | break; 54 | } 55 | 56 | lineNbr += 1; 57 | currentLine = thisDoc.lineAt(lineNbr); 58 | } 59 | } 60 | 61 | const endPos = currentLine.range.end.character; 62 | const newPosition = new Position(lineNbr, endPos); 63 | 64 | return { 65 | text: varToDebug, 66 | position: newPosition, 67 | }; 68 | } 69 | 70 | /** 71 | * Gets the indentation of the current line in a robust way. 72 | * Handles empty lines, whitespace-only lines, and mixed indentation. 73 | * @returns {string} The indentation string (spaces or tabs) 74 | */ 75 | function getIndent() { 76 | const editor = window.activeTextEditor; 77 | if (!editor) { 78 | return ''; 79 | } 80 | 81 | const { document, selection } = editor; 82 | const activeLine = document.lineAt(selection.active.line); 83 | 84 | if (activeLine.isEmptyOrWhitespace) { 85 | // For empty/whitespace lines, try to detect indentation from surrounding lines 86 | let indent = ''; 87 | // Check previous non-empty line 88 | for (let i = selection.active.line - 1; i >= 0; i--) { 89 | const line = document.lineAt(i); 90 | if (!line.isEmptyOrWhitespace) { 91 | const match = line.text.match(/^[\s]*/); 92 | indent = match ? match[0] : ''; 93 | break; 94 | } 95 | } 96 | return indent; 97 | } 98 | 99 | const lineText = activeLine.text; 100 | const indentMatch = lineText.match(/^[\s]*/); 101 | return indentMatch ? indentMatch[0] : ''; 102 | } 103 | 104 | /** 105 | * Inserts a MsgBox debug statement for the selected variable or macro. 106 | * Includes proper indentation and error handling. 107 | * @throws {Error} If no valid variable/macro is selected or no active editor 108 | */ 109 | function debugMsgBox() { 110 | try { 111 | const editor = window.activeTextEditor; 112 | if (!editor) { 113 | throw new Error('No active text editor found'); 114 | } 115 | 116 | const debugText = getDebugText(); 117 | if (!debugText || !('text' in debugText) || !('position' in debugText)) { 118 | return; // Error already thrown in getDebugText 119 | } 120 | 121 | const indent = getIndent(); 122 | const debugCode = `\n${indent}${DEBUG_TEMPLATES.MSGBOX.PREFIX}\n${indent}${DEBUG_TEMPLATES.MSGBOX.CODE.replace(/{VAR}/g, debugText.text)}`; 123 | 124 | // Insert the debug code into the script 125 | editor.edit(edit => { 126 | edit.insert(debugText.position, debugCode); 127 | }); 128 | } catch (error) { 129 | window.showErrorMessage(`Debug MsgBox Error: ${error.message}`); 130 | } 131 | } 132 | 133 | /** 134 | * Inserts a ConsoleWrite debug statement for the selected variable or macro. 135 | * Includes proper indentation and error handling. 136 | * @throws {Error} If no valid variable/macro is selected or no active editor 137 | */ 138 | function debugConsole() { 139 | try { 140 | const editor = window.activeTextEditor; 141 | if (!editor) { 142 | throw new Error('No active text editor found'); 143 | } 144 | 145 | const debugText = getDebugText(); 146 | if (!debugText || !('text' in debugText) || !('position' in debugText)) { 147 | return; // Error already thrown in getDebugText 148 | } 149 | 150 | const indent = getIndent(); 151 | const debugCode = `\n${indent}${DEBUG_TEMPLATES.CONSOLE.PREFIX}\n${indent}${DEBUG_TEMPLATES.CONSOLE.CODE.replace(/{VAR}/g, debugText.text)}`; 152 | 153 | // Insert the debug code into the script 154 | editor.edit(edit => { 155 | edit.insert(debugText.position, debugCode); 156 | }); 157 | } catch (error) { 158 | window.showErrorMessage(`Debug Console Error: ${error.message}`); 159 | } 160 | } 161 | 162 | export { getDebugText, getIndent, debugMsgBox, debugConsole }; 163 | -------------------------------------------------------------------------------- /src/completions/constants_tray.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { fillCompletions } from '../util'; 3 | 4 | const predefined = 'Tray predefined ID Constant'; 5 | const stateValue = 'Tray menu/item state value Constant'; 6 | const eventValue = 'Tray event value Constant'; 7 | const balloonTip = 'Balloon Tip Type Constant'; 8 | const trayCreateItem = 'TrayCreateItem value Constant'; 9 | const traySetCliCk = 'TraySetClick value Constant'; 10 | const traySetState = 'TraySetState value Constant'; 11 | 12 | const items = [ 13 | { 14 | label: '$TRAY_ITEM_EXIT', 15 | documentation: '`= 3`', 16 | detail: predefined, 17 | }, 18 | { 19 | label: '$TRAY_ITEM_PAUSE', 20 | documentation: '`= 4`', 21 | detail: predefined, 22 | }, 23 | { 24 | label: '$TRAY_ITEM_FIRST', 25 | documentation: '`= 7`', 26 | detail: predefined, 27 | }, 28 | { 29 | label: '$TRAY_CHECKED', 30 | documentation: '`= 1`', 31 | detail: stateValue, 32 | }, 33 | { 34 | label: '$TRAY_UNCHECKED', 35 | documentation: '`= 4`', 36 | detail: stateValue, 37 | }, 38 | { 39 | label: '$TRAY_ENABLE', 40 | documentation: '`= 64`', 41 | detail: stateValue, 42 | }, 43 | { 44 | label: '$TRAY_DISABLE', 45 | documentation: '`= 128`', 46 | detail: stateValue, 47 | }, 48 | { 49 | label: '$TRAY_FOCUS', 50 | documentation: '`= 256`', 51 | detail: stateValue, 52 | }, 53 | { 54 | label: '$TRAY_DEFAULT', 55 | documentation: '`= 512`', 56 | detail: stateValue, 57 | }, 58 | { 59 | label: '$TRAY_EVENT_NONE', 60 | documentation: '`= 0`', 61 | detail: eventValue, 62 | }, 63 | { 64 | label: '$TRAY_EVENT_SHOWICON', 65 | documentation: '`= -3`', 66 | detail: eventValue, 67 | }, 68 | { 69 | label: '$TRAY_EVENT_HIDEICON', 70 | documentation: '`= -4`', 71 | detail: eventValue, 72 | }, 73 | { 74 | label: '$TRAY_EVENT_FLASHICON', 75 | documentation: '`= -5`', 76 | detail: eventValue, 77 | }, 78 | { 79 | label: '$TRAY_EVENT_NOFLASHICON', 80 | documentation: '`= -6`', 81 | detail: eventValue, 82 | }, 83 | { 84 | label: '$TRAY_EVENT_PRIMARYDOWN', 85 | documentation: '`= -7`', 86 | detail: eventValue, 87 | }, 88 | { 89 | label: '$TRAY_EVENT_PRIMARYUP', 90 | documentation: '`= -8`', 91 | detail: eventValue, 92 | }, 93 | { 94 | label: '$TRAY_EVENT_SECONDARYDOWN', 95 | documentation: '`= -9`', 96 | detail: eventValue, 97 | }, 98 | { 99 | label: '$TRAY_EVENT_SECONDARYUP', 100 | documentation: '`= -10`', 101 | detail: eventValue, 102 | }, 103 | { 104 | label: '$TRAY_EVENT_MOUSEOVER', 105 | documentation: '`= -11`', 106 | detail: eventValue, 107 | }, 108 | { 109 | label: '$TRAY_EVENT_MOUSEOUT', 110 | documentation: '`= -12`', 111 | detail: eventValue, 112 | }, 113 | { 114 | label: '$TRAY_EVENT_PRIMARYDOUBLE', 115 | documentation: '`= -13`', 116 | detail: eventValue, 117 | }, 118 | { 119 | label: '$TRAY_EVENT_SECONDARYDOUBLE', 120 | documentation: '`= -14`', 121 | detail: eventValue, 122 | }, 123 | { 124 | label: '$TIP_ICONNONE', 125 | documentation: 'No icon (default)\n\n`= 0`', 126 | detail: balloonTip, 127 | }, 128 | { 129 | label: '$TIP_ICONASTERISK', 130 | documentation: 'Info icon\n\n`= 1`', 131 | detail: balloonTip, 132 | }, 133 | { 134 | label: '$TIP_ICONEXCLAMATION', 135 | documentation: 'Warning icon\n\n`= 2`', 136 | detail: balloonTip, 137 | }, 138 | { 139 | label: '$TIP_ICONHAND', 140 | documentation: 'Error icon\n\n`= 3`', 141 | detail: balloonTip, 142 | }, 143 | { 144 | label: '$TIP_NOSOUND', 145 | documentation: 'No sound\n\n`= 16`', 146 | detail: balloonTip, 147 | }, 148 | { 149 | label: '$TRAY_ITEM_NORMAL', 150 | documentation: '`= 0`', 151 | detail: trayCreateItem, 152 | }, 153 | { 154 | label: '$TRAY_ITEM_RADIO', 155 | documentation: '`= 1`', 156 | detail: trayCreateItem, 157 | }, 158 | { 159 | label: '$TRAY_CLICK_SHOW', 160 | documentation: '`= 0`', 161 | detail: traySetCliCk, 162 | }, 163 | { 164 | label: '$TRAY_CLICK_PRIMARYDOWN', 165 | documentation: '`= 1`', 166 | detail: traySetCliCk, 167 | }, 168 | { 169 | label: '$TRAY_CLICK_PRIMARYUP', 170 | documentation: '`= 2`', 171 | detail: traySetCliCk, 172 | }, 173 | { 174 | label: '$TRAY_DBLCLICK_PRIMARY', 175 | documentation: '`= 4`', 176 | detail: traySetCliCk, 177 | }, 178 | { 179 | label: '$TRAY_CLICK_SECONDARYDOWN', 180 | documentation: '`= 8`', 181 | detail: traySetCliCk, 182 | }, 183 | { 184 | label: '$TRAY_CLICK_SECONDARYUP', 185 | documentation: '`= 16`', 186 | detail: traySetCliCk, 187 | }, 188 | { 189 | label: '$TRAY_DBLCLICK_SECONDARY', 190 | documentation: '`= 32`', 191 | detail: traySetCliCk, 192 | }, 193 | { 194 | label: '$TRAY_CLICK_HOVERING', 195 | documentation: '`= 64`', 196 | detail: traySetCliCk, 197 | }, 198 | { 199 | label: '$TRAY_ICONSTATE_SHOW', 200 | documentation: '`= 1`', 201 | detail: traySetState, 202 | }, 203 | { 204 | label: '$TRAY_ICONSTATE_HIDE', 205 | documentation: '`= 2`', 206 | detail: traySetState, 207 | }, 208 | { 209 | label: '$TRAY_ICONSTATE_FLASH', 210 | documentation: '`= 4`', 211 | detail: traySetState, 212 | }, 213 | { 214 | label: '$TRAY_ICONSTATE_STOPFLASH', 215 | documentation: '`= 8`', 216 | detail: traySetState, 217 | }, 218 | { 219 | label: '$TRAY_ICONSTATE_RESET', 220 | documentation: '`= 16`', 221 | detail: traySetState, 222 | }, 223 | ]; 224 | 225 | export default fillCompletions(items, CompletionItemKind.Constant, '', 'TrayConstants.au3'); 226 | -------------------------------------------------------------------------------- /src/completions/constants_statusbar.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { fillCompletions } from '../util'; 3 | 4 | const styles = 'Style Constant'; 5 | const uFlag = 'uFlag Constant'; 6 | const message = 'Message Constant'; 7 | const notify = 'Notification Constant'; 8 | 9 | const items = [ 10 | { 11 | label: '$SBARS_SIZEGRIP', 12 | documentation: '`= 0x100`', 13 | detail: styles, 14 | }, 15 | { 16 | label: '$SBT_TOOLTIPS', 17 | documentation: '`= 0x800`', 18 | detail: styles, 19 | }, 20 | { 21 | label: '$SBARS_TOOLTIPS', 22 | documentation: '`= 0x800`', 23 | detail: styles, 24 | }, 25 | { 26 | label: '$SBT_SUNKEN', 27 | documentation: 'Default\n\n`= 0x0`', 28 | detail: uFlag, 29 | }, 30 | { 31 | label: '$SBT_NOBORDERS', 32 | documentation: 'The text is drawn without borders.\n\n`= 0x100`', 33 | detail: uFlag, 34 | }, 35 | { 36 | label: '$SBT_POPOUT', 37 | documentation: 38 | ' The text is drawn with a border to appear higher than the plane of the window.\n\n`= 0x200`', 39 | detail: uFlag, 40 | }, 41 | { 42 | label: '$SBT_RTLREADING', 43 | documentation: 44 | '`SB_SETTEXT`, `SB_SETTEXT`, `SB_GETTEXTLENGTH` flags only: Displays text using right-to-left reading order on Hebrew or Arabic systems.\n\n`= 0x400`', 45 | detail: uFlag, 46 | }, 47 | { 48 | label: '$SBT_NOTABPARSING', 49 | documentation: 'Tab characters are ignored.\n\n`= 0x800`', 50 | detail: uFlag, 51 | }, 52 | { 53 | label: '$SBT_OWNERDRAW', 54 | documentation: 'The text is drawn by the parent window.\n\n`= 0x1000`', 55 | detail: uFlag, 56 | }, 57 | { 58 | label: '$__STATUSBARCONSTANT_WM_USER', 59 | documentation: '`= 0X400`', 60 | detail: message, 61 | }, 62 | { 63 | label: '$SB_GETBORDERS', 64 | documentation: '`= ($__STATUSBARCONSTANT_WM_USER + 7)`', 65 | detail: message, 66 | }, 67 | { 68 | label: '$SB_GETICON', 69 | documentation: '`= ($__STATUSBARCONSTANT_WM_USER + 20)`', 70 | detail: message, 71 | }, 72 | { 73 | label: '$SB_GETPARTS', 74 | documentation: '`= ($__STATUSBARCONSTANT_WM_USER + 6)`', 75 | detail: message, 76 | }, 77 | { 78 | label: '$SB_GETRECT', 79 | documentation: '`= ($__STATUSBARCONSTANT_WM_USER + 10)`', 80 | detail: message, 81 | }, 82 | { 83 | label: '$SB_GETTEXTA', 84 | documentation: '`= ($__STATUSBARCONSTANT_WM_USER + 2)`', 85 | detail: message, 86 | }, 87 | { 88 | label: '$SB_GETTEXTW', 89 | documentation: '`= ($__STATUSBARCONSTANT_WM_USER + 13)`', 90 | detail: message, 91 | }, 92 | { 93 | label: '$SB_GETTEXT', 94 | documentation: '`= $SB_GETTEXTA`', 95 | detail: message, 96 | }, 97 | { 98 | label: '$SB_GETTEXTLENGTHA', 99 | documentation: '`= ($__STATUSBARCONSTANT_WM_USER + 3)`', 100 | detail: message, 101 | }, 102 | { 103 | label: '$SB_GETTEXTLENGTHW', 104 | documentation: '`= ($__STATUSBARCONSTANT_WM_USER + 12)`', 105 | detail: message, 106 | }, 107 | { 108 | label: '$SB_GETTEXTLENGTH', 109 | documentation: '`= $SB_GETTEXTLENGTHA`', 110 | detail: message, 111 | }, 112 | { 113 | label: '$SB_GETTIPTEXTA', 114 | documentation: '`= ($__STATUSBARCONSTANT_WM_USER + 18)`', 115 | detail: message, 116 | }, 117 | { 118 | label: '$SB_GETTIPTEXTW', 119 | documentation: '`= ($__STATUSBARCONSTANT_WM_USER + 19)`', 120 | detail: message, 121 | }, 122 | { 123 | label: '$SB_GETUNICODEFORMAT', 124 | documentation: '`= 0x2000 + 6`', 125 | detail: message, 126 | }, 127 | { 128 | label: '$SB_ISSIMPLE', 129 | documentation: '`= ($__STATUSBARCONSTANT_WM_USER + 14)`', 130 | detail: message, 131 | }, 132 | 133 | { 134 | label: '$SB_SETBKCOLOR', 135 | documentation: '`= 0x2000 + 1`', 136 | detail: message, 137 | }, 138 | { 139 | label: '$SB_SETICON', 140 | documentation: '`= ($__STATUSBARCONSTANT_WM_USER + 15)`', 141 | detail: message, 142 | }, 143 | { 144 | label: '$SB_SETMINHEIGHT', 145 | documentation: '`= ($__STATUSBARCONSTANT_WM_USER + 8)`', 146 | detail: message, 147 | }, 148 | { 149 | label: '$SB_SETPARTS', 150 | documentation: '`= ($__STATUSBARCONSTANT_WM_USER + 4)`', 151 | detail: message, 152 | }, 153 | { 154 | label: '$SB_SETTEXTA', 155 | documentation: '`= ($__STATUSBARCONSTANT_WM_USER + 1)`', 156 | detail: message, 157 | }, 158 | { 159 | label: '$SB_SETTEXTW', 160 | documentation: '`= ($__STATUSBARCONSTANT_WM_USER + 11)`', 161 | detail: message, 162 | }, 163 | { 164 | label: '$SB_SETTEXT', 165 | documentation: '`= $SB_SETTEXTA`', 166 | detail: message, 167 | }, 168 | { 169 | label: '$SB_SETTIPTEXTA', 170 | documentation: '`= ($__STATUSBARCONSTANT_WM_USER + 16)`', 171 | detail: message, 172 | }, 173 | { 174 | label: '$SB_SETTIPTEXTW', 175 | documentation: '`= ($__STATUSBARCONSTANT_WM_USER + 17)`', 176 | detail: message, 177 | }, 178 | { 179 | label: '$SB_SETUNICODEFORMAT', 180 | documentation: '`= 0x2000 + 5`', 181 | detail: message, 182 | }, 183 | { 184 | label: '$SB_SIMPLE', 185 | documentation: '`= ($__STATUSBARCONSTANT_WM_USER + 9)`', 186 | detail: message, 187 | }, 188 | { 189 | label: '$SB_SIMPLEID', 190 | documentation: '`= 0xff`', 191 | detail: message, 192 | }, 193 | { 194 | label: '$SBN_FIRST', 195 | documentation: '`= -880`', 196 | detail: notify, 197 | }, 198 | { 199 | label: '$SBN_SIMPLEMODECHANGE', 200 | documentation: 201 | 'Sent when the simple mode changes due to a `$SB_SIMPLE` message\n\n`= $SBN_FIRST - 0`', 202 | detail: notify, 203 | }, 204 | ]; 205 | 206 | export default fillCompletions(items, CompletionItemKind.Constant, '', 'StatusBarConstants.au3'); 207 | -------------------------------------------------------------------------------- /scripts/package-all.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Package AutoIt-VSCode extension for both VS Code Marketplace and OpenVSX 5 | * 6 | * This script: 7 | * 1. Builds the extension once 8 | * 2. Packages for VS Code Marketplace (publisher: Damien) 9 | * 3. Packages for OpenVSX (publisher: loganch) 10 | * 4. Restores original package.json 11 | */ 12 | 13 | import { readFileSync, writeFileSync, copyFileSync, unlinkSync } from 'fs'; 14 | import { join, dirname } from 'path'; 15 | import { fileURLToPath } from 'url'; 16 | import { execSync } from 'child_process'; 17 | 18 | const __filename = fileURLToPath(import.meta.url); 19 | const __dirname = dirname(__filename); 20 | const rootDir = join(__dirname, '..'); 21 | const packageJsonPath = join(rootDir, 'package.json'); 22 | const backupPath = join(rootDir, 'package.json.backup'); 23 | 24 | // JSON formatting constant (avoid ESLint magic number) 25 | const JSON_INDENTATION = 2; 26 | 27 | /** 28 | * Read and parse package.json 29 | */ 30 | function readPackageJson() { 31 | const content = readFileSync(packageJsonPath, 'utf8'); 32 | return JSON.parse(content); 33 | } 34 | 35 | /** 36 | * Write package.json with formatting 37 | */ 38 | function writePackageJson(data) { 39 | writeFileSync(packageJsonPath, JSON.stringify(data, null, JSON_INDENTATION) + '\n', 'utf8'); 40 | } 41 | 42 | /** 43 | * Restore package.json from backup 44 | */ 45 | function restorePackageJson() { 46 | try { 47 | copyFileSync(backupPath, packageJsonPath); 48 | unlinkSync(backupPath); 49 | console.log('✓ Restored original package.json\n'); 50 | } catch (error) { 51 | console.error('Error restoring package.json:', error.message); 52 | throw error; 53 | } 54 | } 55 | 56 | /** 57 | * Package extension with specific publisher 58 | */ 59 | function packageExtension(publisher, outputName) { 60 | try { 61 | execSync(`npx @vscode/vsce package --out ${outputName}`, { 62 | cwd: rootDir, 63 | stdio: 'inherit', 64 | }); 65 | return true; 66 | } catch (error) { 67 | console.error(`\n❌ Packaging failed for ${publisher}`); 68 | throw error; 69 | } 70 | } 71 | 72 | /** 73 | * Main packaging function 74 | */ 75 | function packageAll() { 76 | let backupCreated = false; 77 | const createdFiles = []; 78 | 79 | try { 80 | // Read original package.json 81 | const originalPackage = readPackageJson(); 82 | const { name, version, publisher: originalPublisher } = originalPackage; 83 | 84 | console.log('\n╔════════════════════════════════════════════════════════════╗'); 85 | console.log('║ AutoIt-VSCode Dual Marketplace Packaging ║'); 86 | console.log('╚════════════════════════════════════════════════════════════╝\n'); 87 | console.log(`📦 Extension: ${name} v${version}\n`); 88 | 89 | // Create backup 90 | copyFileSync(packageJsonPath, backupPath); 91 | backupCreated = true; 92 | console.log('✓ Created package.json backup\n'); 93 | 94 | // Build extension once 95 | console.log('═══════════════════════════════════════════════════════════'); 96 | console.log('🔨 Building extension...'); 97 | console.log('═══════════════════════════════════════════════════════════\n'); 98 | execSync('npm run vscode:prepublish', { 99 | cwd: rootDir, 100 | stdio: 'inherit', 101 | }); 102 | console.log('\n✓ Build completed\n'); 103 | 104 | // Package 1: VS Code Marketplace (Damien) 105 | console.log('═══════════════════════════════════════════════════════════'); 106 | console.log('📦 Packaging for VS Code Marketplace'); 107 | console.log('═══════════════════════════════════════════════════════════'); 108 | console.log(` Publisher: ${originalPublisher}`); 109 | const vscodeVsixName = `${name}-${version}.vsix`; 110 | console.log(` Output: ${vscodeVsixName}\n`); 111 | 112 | packageExtension(originalPublisher, vscodeVsixName); 113 | createdFiles.push({ 114 | name: vscodeVsixName, 115 | marketplace: 'VS Code Marketplace', 116 | publisher: originalPublisher, 117 | }); 118 | console.log(`\n✅ Created: ${vscodeVsixName}\n`); 119 | 120 | // Package 2: OpenVSX (loganch) 121 | console.log('═══════════════════════════════════════════════════════════'); 122 | console.log('📦 Packaging for OpenVSX'); 123 | console.log('═══════════════════════════════════════════════════════════'); 124 | console.log(' Publisher: loganch'); 125 | const openvsxVsixName = `${name}-${version}-openvsx.vsix`; 126 | console.log(` Output: ${openvsxVsixName}\n`); 127 | 128 | // Modify publisher for OpenVSX 129 | const modifiedPackage = { ...originalPackage, publisher: 'loganch' }; 130 | writePackageJson(modifiedPackage); 131 | console.log('✓ Updated publisher to "loganch"'); 132 | 133 | packageExtension('loganch', openvsxVsixName); 134 | createdFiles.push({ 135 | name: openvsxVsixName, 136 | marketplace: 'OpenVSX', 137 | publisher: 'loganch', 138 | }); 139 | console.log(`\n✅ Created: ${openvsxVsixName}\n`); 140 | 141 | // Summary 142 | console.log('═══════════════════════════════════════════════════════════'); 143 | console.log('✅ PACKAGING COMPLETE'); 144 | console.log('═══════════════════════════════════════════════════════════\n'); 145 | 146 | console.log('📦 Created packages:\n'); 147 | createdFiles.forEach((file, index) => { 148 | console.log(` ${index + 1}. ${file.name}`); 149 | console.log(` → ${file.marketplace} (${file.publisher})\n`); 150 | }); 151 | 152 | console.log('📤 Publishing instructions:\n'); 153 | console.log(` VS Code Marketplace:`); 154 | console.log(` $ npx @vscode/vsce publish\n`); 155 | console.log(` OpenVSX:`); 156 | console.log(` $ npx ovsx publish ${openvsxVsixName} -p \n`); 157 | console.log('═══════════════════════════════════════════════════════════\n'); 158 | } catch (error) { 159 | console.error('\n❌ Error during packaging:', error.message); 160 | process.exitCode = 1; 161 | } finally { 162 | // Always restore the original package.json 163 | if (backupCreated) { 164 | restorePackageJson(); 165 | } 166 | } 167 | } 168 | 169 | // Run the script 170 | packageAll(); 171 | -------------------------------------------------------------------------------- /src/signatures/udf_crypt.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { 3 | br, 4 | valueFirstHeader as header, 5 | opt, 6 | signatureToCompletion, 7 | signatureToHover, 8 | } from '../util'; 9 | 10 | const include = '(Requires: `#include `)'; 11 | 12 | const signatures = { 13 | _Crypt_DecryptData: { 14 | documentation: 'Decrypts data using the supplied key', 15 | label: '_Crypt_DecryptData ( $vData, $vCryptKey, $iAlgID [, $bFinal = True] )', 16 | params: [ 17 | { 18 | label: '$vData', 19 | documentation: 'Data to decrypt', 20 | }, 21 | { 22 | label: '$vCryptKey', 23 | documentation: 'Password or handle to a key if the CALG_USERKEY flag is specified', 24 | }, 25 | { 26 | label: '$iAlgID', 27 | documentation: 'The algorithm to use', 28 | }, 29 | { 30 | label: '$bFinal', 31 | documentation: '**[optional]** False if this is only a segment of the full data', 32 | }, 33 | ], 34 | }, 35 | _Crypt_DecryptFile: { 36 | documentation: 'Decrypts a file with specified key and algorithm', 37 | label: '_Crypt_DecryptFile ( $sSourceFile, $sDestinationFile, $vCryptKey, $iAlgID )', 38 | params: [ 39 | { 40 | label: '$sSourceFile', 41 | documentation: 'File to process', 42 | }, 43 | { 44 | label: '$sDestinationFile', 45 | documentation: 'File to save the processed file', 46 | }, 47 | { 48 | label: '$vCryptKey', 49 | documentation: 'Password or handle to a key if the CALG_USERKEY flag is specified', 50 | }, 51 | { 52 | label: '$iAlgID', 53 | documentation: 'The algorithm to use', 54 | }, 55 | ], 56 | }, 57 | _Crypt_DeriveKey: { 58 | documentation: 'Creates a key from algorithm and password', 59 | label: '_Crypt_DeriveKey ( $vPassword, $iAlgID [, $iHashAlgID = $CALG_MD5] )', 60 | params: [ 61 | { 62 | label: '$vPassword', 63 | documentation: 'Password to use', 64 | }, 65 | { 66 | label: '$iAlgID', 67 | documentation: 'Encryption ID of algorithm to be used with the key', 68 | }, 69 | { 70 | label: '$iHashAlgID', 71 | documentation: '**[optional]** Id of the algo to hash the password with', 72 | }, 73 | ], 74 | }, 75 | _Crypt_DestroyKey: { 76 | documentation: 'Frees the resources used by a key', 77 | label: '_Crypt_DestroyKey ( $hCryptKey )', 78 | params: [ 79 | { 80 | label: '$hCryptKey', 81 | documentation: 'Key to destroy', 82 | }, 83 | ], 84 | }, 85 | _Crypt_EncryptData: { 86 | documentation: 'Encrypts data using the supplied key', 87 | label: '_Crypt_EncryptData ( $vData, $vCryptKey, $iAlgID [, $bFinal = True] )', 88 | params: [ 89 | { 90 | label: '$vData', 91 | documentation: 'Data to encrypt/decrypt', 92 | }, 93 | { 94 | label: '$vCryptKey', 95 | documentation: 'Password or handle to a key if the CALG_USERKEY flag is specified', 96 | }, 97 | { 98 | label: '$iAlgID', 99 | documentation: 'The algorithm to use', 100 | }, 101 | { 102 | label: '$bFinal', 103 | documentation: '**[optional]** False if this is only a segment of the full data', 104 | }, 105 | ], 106 | }, 107 | _Crypt_EncryptFile: { 108 | documentation: 'Encrypts a file with specified key and algorithm', 109 | label: '_Crypt_EncryptFile ( $sSourceFile, $sDestinationFile, $vCryptKey, $iAlgID )', 110 | params: [ 111 | { 112 | label: '$sSourceFile', 113 | documentation: 'File to process', 114 | }, 115 | { 116 | label: '$sDestinationFile', 117 | documentation: 'File to save the processed file', 118 | }, 119 | { 120 | label: '$vCryptKey', 121 | documentation: 'Password or handle to a key if the CALG_USERKEY flag is specified', 122 | }, 123 | { 124 | label: '$iAlgID', 125 | documentation: 'The algorithm to use', 126 | }, 127 | ], 128 | }, 129 | _Crypt_GenRandom: { 130 | documentation: 'Fill a buffer with cryptographically random data', 131 | label: '_Crypt_GenRandom ( $pBuffer, $iSize )', 132 | params: [ 133 | { 134 | label: '$pBuffer', 135 | documentation: 'Pointer to buffer to fill with random data.', 136 | }, 137 | { 138 | label: '$iSize', 139 | documentation: 'Size of the buffer pointed to by $pBuffer.', 140 | }, 141 | ], 142 | }, 143 | _Crypt_HashData: { 144 | documentation: 'Hash data with specified algorithm', 145 | label: '_Crypt_HashData ( $vData, $iAlgID [, $bFinal = True [, $hCryptHash = 0]] )', 146 | params: [ 147 | { 148 | label: '$vData', 149 | documentation: 'Data to hash', 150 | }, 151 | { 152 | label: '$iAlgID', 153 | documentation: 'Hash ID to use', 154 | }, 155 | { 156 | label: '$bFinal', 157 | documentation: 158 | '**[optional]** False if this is only a segment of the full data, also makes the function return a hash object instead of hash', 159 | }, 160 | { 161 | label: '$hCryptHash', 162 | documentation: 163 | '**[optional]** Hash object returned from a previous call to _Crypt_HashData()', 164 | }, 165 | ], 166 | }, 167 | _Crypt_HashFile: { 168 | documentation: 'Hash a string with specified algorithm', 169 | label: '_Crypt_HashFile ( $sFilePath, $iAlgID )', 170 | params: [ 171 | { 172 | label: '$sFilePath', 173 | documentation: 'Path to file to hash', 174 | }, 175 | { 176 | label: '$iAlgID', 177 | documentation: 'Hash ID to use', 178 | }, 179 | ], 180 | }, 181 | _Crypt_Shutdown: { 182 | documentation: 'Uninitialize the Crypt library', 183 | label: '_Crypt_Shutdown ( )', 184 | params: [], 185 | }, 186 | _Crypt_Startup: { 187 | documentation: 'Initialize the Crypt library', 188 | label: '_Crypt_Startup ( )', 189 | params: [], 190 | }, 191 | }; 192 | 193 | const hovers = signatureToHover(signatures); 194 | const completions = signatureToCompletion(signatures, CompletionItemKind.Function, include); 195 | 196 | export { signatures as default, hovers, completions }; 197 | -------------------------------------------------------------------------------- /syntaxes/vscode-autoit-output.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "name": "vscode-autoit-output", 4 | "scopeName": "source.vscode_autoit_output", 5 | "patterns": [ 6 | {"include": "#header"} 7 | ], 8 | "repository": { 9 | "header": { 10 | "patterns": [ 11 | { 12 | "comment": "Timestamp and/or process ID (12:34:56.789 #123: )", 13 | "begin": "^(?:(\\d{2}:\\d{2}:\\d{2}\\.\\d{3})?( ?#\\d+:| {3,})? )?", 14 | "end": "\\r?\\n|$", 15 | "beginCaptures": { 16 | "1": { "name": "vscode-autoit-output-date" }, 17 | "2": { "name": "vscode-autoit-output-process-id" } 18 | }, 19 | "patterns": [ { "include": "#general" } ] 20 | } 21 | ] 22 | }, 23 | "general": { 24 | "patterns": [ 25 | { 26 | "begin": "\\G(Starting process (#\\d+))", 27 | "end": "((.+) \\[PID (\\d+|n\/a)\\])", 28 | "beginCaptures": { 29 | "1": { "name": "vscode-autoit-output-process" }, 30 | "2": { "name": "vscode-autoit-output-process-id" } 31 | }, 32 | "endCaptures": { 33 | "1": { "name": "vscode-autoit-output-process" }, 34 | "2": { "name": "vscode-autoit-output-process-file" }, 35 | "3": { "name": "vscode-autoit-output-process-pid" } 36 | } 37 | }, 38 | { 39 | "comment": "exit with no error code (0 or -1)", 40 | "match": "\\G>Exit code (?:0|-1) .*", 41 | "name": "vscode-autoit-output-EXIT" 42 | }, 43 | { 44 | "match": "\\G>Exit code 1 .*", 45 | "name": "vscode-autoit-output-EXIT_WARNING" 46 | }, 47 | { 48 | "match": "\\G>Exit code \\-?\\d+.*", 49 | "name": "vscode-autoit-output-EXIT_ERROR" 50 | }, 51 | { 52 | "match": "\\G>.*", 53 | "name": "vscode-autoit-output-SCE_ERR_CMD" 54 | }, 55 | { 56 | "match": "\\G(?:---|[+]{3}).*", 57 | "name": "vscode-autoit-output-SCE_ERR_DIFF_MESSAGE" 58 | }, 59 | { 60 | "match": "\\G[-<].*", 61 | "name": "vscode-autoit-output-SCE_ERR_DIFF_DELETION" 62 | }, 63 | { 64 | "match": "\\G!.*", 65 | "name": "vscode-autoit-output-SCE_ERR_DIFF_CHANGED" 66 | }, 67 | { 68 | "match": "\\G[+].*", 69 | "name": "vscode-autoit-output-SCE_ERR_DIFF_ADDITION" 70 | }, 71 | { 72 | "comment": "Absoft Pro Fortran 90/95 v8.2 error and/or warning message", 73 | "match": "\\Gcf90-.*", 74 | "name": "vscode-autoit-output-SCE_ERR_ABSF" 75 | }, 76 | { 77 | "comment": "Intel Fortran Compiler v8.0 error/warning message", 78 | "match": "\\Gfortcom:.*", 79 | "name": "vscode-autoit-output-SCE_ERR_IFORT" 80 | }, 81 | { 82 | "match": "\\G.*(?:File \".*, line|, line.*File).*", 83 | "name": "vscode-autoit-output-SCE_ERR_PYTHON" 84 | }, 85 | { 86 | "match": "\\G.* (?:in .* on line|on line .* in) .*", 87 | "name": "vscode-autoit-output-SCE_ERR_PHP" 88 | }, 89 | { 90 | "comment": "Intel Fortran Compiler error/warning message", 91 | "match": "\\G(?:Warning|Error) at \\(.*\\) : .*", 92 | "name": "vscode-autoit-output-SCE_ERR_IFC" 93 | }, 94 | { 95 | "comment": "Borland error message", 96 | "match": "\\G(?:Warning|Error) .*", 97 | "name": "vscode-autoit-output-SCE_ERR_BORLAND" 98 | }, 99 | { 100 | "comment": "Lua 4 error message", 101 | "match": "\\G(?:.*at line .*file |.*file .*at line ).*", 102 | "name": "vscode-autoit-output-SCE_ERR_LUA" 103 | }, 104 | { 105 | "comment": "perl error message: at line ", 106 | "match": "\\G.* at .+ line .*", 107 | "name": "vscode-autoit-output-SCE_ERR_PERL" 108 | }, 109 | { 110 | "comment": "A .NET traceback", 111 | "match": "\\G.* at .*:line.*", 112 | "name": "vscode-autoit-output-SCE_ERR_NET" 113 | }, 114 | { 115 | "comment": "Essential Lahey Fortran error message", 116 | "match": "\\GLine .*, file .*", 117 | "name": "vscode-autoit-output-SCE_ERR_ELF" 118 | }, 119 | { 120 | "comment": "HTML tidy style: line 42 column 1", 121 | "match": "\\Gline .* column .*", 122 | "name": "vscode-autoit-output-SCE_ERR_TIDY" 123 | }, 124 | { 125 | "comment": "Java stack back trace", 126 | "match": "\\G\tat .*\\(.*\\.java.*", 127 | "name": "vscode-autoit-output-SCE_ERR_JAVA_STACK" 128 | }, 129 | { 130 | "comment": "GCC showing include path to following error", 131 | "match": "\\G(?: from |In file included from ).*", 132 | "name": "vscode-autoit-output-SCE_ERR_GCC_INCLUDED_FROM" 133 | }, 134 | { 135 | "comment": "Microsoft nmake fatal error: NMAKE : fatal error : : return code ", 136 | "match": "\\GNMAKE : fatal error.*", 137 | "name": "vscode-autoit-output-SCE_ERR_MS" 138 | }, 139 | { 140 | "comment": "Microsoft linker warning: { : } warning LNK9999", 141 | "match": "\\G.*warning LNK.*", 142 | "name": "vscode-autoit-output-SCE_ERR_MS" 143 | }, 144 | { 145 | "comment": "GCC code excerpt and pointer to issue: | ^~~~~~~~~~~~", 146 | "match": "\\G [|][ +].*", 147 | "name": "vscode-autoit-output-SCE_ERR_GCC_EXCERPT" 148 | }, 149 | { 150 | "match": "\\G\\S+\\t.*\\t\\d+.*", 151 | "name": "vscode-autoit-output-SCE_ERR_CTAG" 152 | }, 153 | { 154 | "comment": "Lua 5.1 error looks like: lua.exe: test1.lua:3: syntax error", 155 | "match": "\\G.+:\\d+:", 156 | "name": "vscode-autoit-output-SCE_ERR_GCC" 157 | }, 158 | { 159 | "match": "\\G.*\\(\\d+(:?,\\d+)?\\).*", 160 | "name": "vscode-autoit-output-olive" 161 | }, 162 | { 163 | "match": "\\G@@ Debug\\(\\d+\\) :", 164 | "name": "vscode-autoit-output-debug" 165 | } 166 | ] 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/signatures/udf_guictrlavi.js: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind } from 'vscode'; 2 | import { 3 | br, 4 | valueFirstHeader as header, 5 | opt, 6 | signatureToCompletion, 7 | signatureToHover, 8 | } from '../util'; 9 | 10 | const include = '(Requires: `#include `)'; 11 | 12 | const signatures = { 13 | _GUICtrlAVI_Close: { 14 | documentation: 'Closes an AVI clip', 15 | label: '_GUICtrlAVI_Close ( $hWnd )', 16 | params: [ 17 | { 18 | label: '$hWnd', 19 | documentation: 'Control ID/Handle to the control', 20 | }, 21 | ], 22 | }, 23 | _GUICtrlAVI_Create: { 24 | documentation: 'Creates an AVI control', 25 | label: 26 | '_GUICtrlAVI_Create ( $hWnd [, $sFilePath = "" [, $iSubFileID = -1 [, $iX = 0 [, $iY = 0 [, $iWidth = 0 [, $iHeight = 0 [, $iStyle = 0x00000006 [, $iExStyle = 0x00000000]]]]]]]] )', 27 | params: [ 28 | { 29 | label: '$hWnd', 30 | documentation: 'Handle to parent or owner window', 31 | }, 32 | { 33 | label: '$sFilePath', 34 | documentation: '**[optional]** The filename of the video. Only .avi files are supported', 35 | }, 36 | { 37 | label: '$iSubFileID', 38 | documentation: '**[optional]** id of the subfile to be used.', 39 | }, 40 | { 41 | label: '$iX', 42 | documentation: '**[optional]** Horizontal position of the control', 43 | }, 44 | { 45 | label: '$iY', 46 | documentation: '**[optional]** Vertical position of the control', 47 | }, 48 | { 49 | label: '$iWidth', 50 | documentation: '**[optional]** Control width', 51 | }, 52 | { 53 | label: '$iHeight', 54 | documentation: '**[optional]** Control height', 55 | }, 56 | { 57 | label: '$iStyle', 58 | documentation: 59 | "**[optional]** Control styles:    $ACS_CENTER - Centers the animation in the animation control's window    $ACS_TRANSPARENT - Creates the control with a transparent background    $ACS_AUTOPLAY - Starts playing the animation as soon as the AVI clip is opened    $ACS_TIMER - The control plays the clip without creating a threadDefault: $ACS_TRANSPARENT, $ACS_AUTOPLAYForced: $WS_CHILD, $WS_VISIBLE", 60 | }, 61 | { 62 | label: '$iExStyle', 63 | documentation: 64 | '**[optional]** Control extended style. These correspond to the standard $WS_EX_* constants. See Extended Style Table.', 65 | }, 66 | ], 67 | }, 68 | _GUICtrlAVI_Destroy: { 69 | documentation: 'Delete the control', 70 | label: '_GUICtrlAVI_Destroy ( ByRef $hWnd )', 71 | params: [ 72 | { 73 | label: '$hWnd', 74 | documentation: 'Control ID/Handle to the control', 75 | }, 76 | ], 77 | }, 78 | _GUICtrlAVI_IsPlaying: { 79 | documentation: 'Checks whether an Audio-Video Interleaved (AVI) clip is playing', 80 | label: '_GUICtrlAVI_IsPlaying ( $hWnd )', 81 | params: [ 82 | { 83 | label: '$hWnd', 84 | documentation: 'Control ID/Handle to the control', 85 | }, 86 | ], 87 | }, 88 | _GUICtrlAVI_Open: { 89 | documentation: 'Opens an AVI clip and displays its first frame in an animation control', 90 | label: '_GUICtrlAVI_Open ( $hWnd, $sFileName )', 91 | params: [ 92 | { 93 | label: '$hWnd', 94 | documentation: 'Control ID/Handle to the control', 95 | }, 96 | { 97 | label: '$sFileName', 98 | documentation: 'Fully qualified path to the AVI file', 99 | }, 100 | ], 101 | }, 102 | _GUICtrlAVI_OpenEx: { 103 | documentation: 'Opens an AVI clip and displays its first frame in an animation control', 104 | label: '_GUICtrlAVI_OpenEx ( $hWnd, $sFileName, $iResourceID )', 105 | params: [ 106 | { 107 | label: '$hWnd', 108 | documentation: 'Control ID/Handle to the control', 109 | }, 110 | { 111 | label: '$sFileName', 112 | documentation: 'Fully qualified path to resource file', 113 | }, 114 | { 115 | label: '$iResourceID', 116 | documentation: 'AVI resource identifier', 117 | }, 118 | ], 119 | }, 120 | _GUICtrlAVI_Play: { 121 | documentation: 'Plays an AVI clip in an animation control', 122 | label: '_GUICtrlAVI_Play ( $hWnd [, $iFrom = 0 [, $iTo = -1 [, $iRepeat = -1]]] )', 123 | params: [ 124 | { 125 | label: '$hWnd', 126 | documentation: 'Control ID/Handle to the control', 127 | }, 128 | { 129 | label: '$iFrom', 130 | documentation: 131 | '**[optional]** 0-based index of the frame where playing begins. The value must be less than 65,536.A value of 0 means begin with the first frame in the clip.', 132 | }, 133 | { 134 | label: '$iTo', 135 | documentation: 136 | '**[optional]** 0-based index of the frame where playing ends. The value must be less than 65,536.A value of -1 means end with the last frame in the clip.', 137 | }, 138 | { 139 | label: '$iRepeat', 140 | documentation: 141 | '**[optional]** Number of times to replay the AVI clip. A value of -1 means replay the clip indefinitely.', 142 | }, 143 | ], 144 | }, 145 | _GUICtrlAVI_Seek: { 146 | documentation: 'Directs an AVI control to display a particular frame of an AVI clip', 147 | label: '_GUICtrlAVI_Seek ( $hWnd, $iFrame )', 148 | params: [ 149 | { 150 | label: '$hWnd', 151 | documentation: 'Control ID/Handle to the control', 152 | }, 153 | { 154 | label: '$iFrame', 155 | documentation: '0-based index of the frame to display', 156 | }, 157 | ], 158 | }, 159 | _GUICtrlAVI_Show: { 160 | documentation: 'Show/Hide the AVI control', 161 | label: '_GUICtrlAVI_Show ( $hWnd, $iState )', 162 | params: [ 163 | { 164 | label: '$hWnd', 165 | documentation: 'Control ID/Handle to the control', 166 | }, 167 | { 168 | label: '$iState', 169 | documentation: 'State of the AVI, can be the following values:', 170 | }, 171 | ], 172 | }, 173 | _GUICtrlAVI_Stop: { 174 | documentation: 'Stops playing an AVI clip', 175 | label: '_GUICtrlAVI_Stop ( $hWnd )', 176 | params: [ 177 | { 178 | label: '$hWnd', 179 | documentation: 'Control ID/Handle to the control', 180 | }, 181 | ], 182 | }, 183 | }; 184 | 185 | const hovers = signatureToHover(signatures); 186 | const completions = signatureToCompletion(signatures, CompletionItemKind.Function, include); 187 | 188 | export { signatures as default, hovers, completions }; 189 | -------------------------------------------------------------------------------- /src/services/MapTrackingService.js: -------------------------------------------------------------------------------- 1 | import MapParser from '../parsers/MapParser.js'; 2 | import IncludeResolver from '../utils/IncludeResolver.js'; 3 | import fs from 'fs'; 4 | 5 | /** 6 | * Singleton service for tracking Map variables across workspace 7 | */ 8 | class MapTrackingService { 9 | /** 10 | * @type {MapTrackingService | null} 11 | */ 12 | static instance = null; 13 | 14 | constructor(workspaceRoot = '', autoitIncludePaths = [], maxIncludeDepth = 3) { 15 | if (MapTrackingService.instance) { 16 | return MapTrackingService.instance; 17 | } 18 | 19 | this.fileParsers = new Map(); // filePath -> MapParser 20 | this.includeResolver = new IncludeResolver(workspaceRoot, autoitIncludePaths, maxIncludeDepth); 21 | this.workspaceRoot = workspaceRoot; 22 | MapTrackingService.instance = this; 23 | } 24 | 25 | /** 26 | * Get singleton instance 27 | * @param {string} workspaceRoot - Only used on first call, ignored on subsequent calls unless updateConfiguration() is called 28 | * @param {string[]} autoitIncludePaths - Only used on first call, ignored on subsequent calls unless updateConfiguration() is called 29 | * @param {number} maxIncludeDepth - Only used on first call, ignored on subsequent calls unless updateConfiguration() is called 30 | * @returns {MapTrackingService} 31 | * @note Parameters are only used during initial instantiation. To update configuration 32 | * after initialization, use updateConfiguration() method. 33 | */ 34 | static getInstance(workspaceRoot, autoitIncludePaths, maxIncludeDepth) { 35 | if (!MapTrackingService.instance) { 36 | MapTrackingService.instance = new MapTrackingService( 37 | workspaceRoot, 38 | autoitIncludePaths, 39 | maxIncludeDepth, 40 | ); 41 | } else if ( 42 | workspaceRoot !== undefined || 43 | autoitIncludePaths !== undefined || 44 | maxIncludeDepth !== undefined 45 | ) { 46 | // Warn if parameters provided don't match stored config 47 | const { instance } = MapTrackingService; 48 | const hasChanges = 49 | (workspaceRoot !== undefined && workspaceRoot !== instance.workspaceRoot) || 50 | (autoitIncludePaths !== undefined && 51 | JSON.stringify(autoitIncludePaths) !== 52 | JSON.stringify(instance.includeResolver.autoitIncludePaths)) || 53 | (maxIncludeDepth !== undefined && maxIncludeDepth !== instance.includeResolver.maxDepth); 54 | 55 | if (hasChanges) { 56 | console.warn( 57 | '[MapTrackingService] getInstance called with different parameters than initial instance. ' + 58 | 'Use updateConfiguration() to modify singleton settings.', 59 | ); 60 | } 61 | } 62 | return MapTrackingService.instance; 63 | } 64 | 65 | /** 66 | * Update configuration for the singleton instance 67 | * @param {string} workspaceRoot - New workspace root directory 68 | * @param {string[]} autoitIncludePaths - New AutoIt include paths 69 | * @param {number} maxIncludeDepth - New maximum include depth 70 | */ 71 | updateConfiguration(workspaceRoot, autoitIncludePaths, maxIncludeDepth) { 72 | this.workspaceRoot = workspaceRoot; 73 | this.includeResolver = new IncludeResolver(workspaceRoot, autoitIncludePaths, maxIncludeDepth); 74 | // Clear cached parsers to force re-parsing with new include paths 75 | this.fileParsers.clear(); 76 | } 77 | 78 | /** 79 | * Reset singleton instance (for testing) 80 | * @internal 81 | */ 82 | static resetInstance() { 83 | MapTrackingService.instance = null; 84 | } 85 | 86 | /** 87 | * Update parsed data for a file 88 | * @param {string} filePath - Absolute file path 89 | * @param {string} source - File source code 90 | */ 91 | updateFile(filePath, source) { 92 | const parser = new MapParser(source); 93 | this.fileParsers.set(filePath, parser); 94 | } 95 | 96 | /** 97 | * Remove file from cache 98 | * @param {string} filePath - Absolute file path 99 | */ 100 | removeFile(filePath) { 101 | this.fileParsers.delete(filePath); 102 | } 103 | 104 | /** 105 | * Clear all cached data 106 | */ 107 | clear() { 108 | this.fileParsers.clear(); 109 | } 110 | 111 | /** 112 | * Get keys for a Map variable at a specific line in a file 113 | * @param {string} filePath - Absolute file path 114 | * @param {string} mapName - Map variable name 115 | * @param {number} line - Line number 116 | * @returns {object} Object with directKeys and functionKeys arrays 117 | */ 118 | getKeysForMap(filePath, mapName, line) { 119 | const parser = this.fileParsers.get(filePath); 120 | if (!parser) { 121 | return { directKeys: [], functionKeys: [] }; 122 | } 123 | 124 | return parser.getKeysForMapAtLine(mapName, line); 125 | } 126 | 127 | /** 128 | * Get keys for a Map including keys from #include files 129 | * @param {string} filePath - Absolute file path 130 | * @param {string} mapName - Map variable name 131 | * @param {number} line - Line number 132 | * @returns {Promise} Promise resolving to object with directKeys and functionKeys arrays 133 | */ 134 | async getKeysForMapWithIncludes(filePath, mapName, line) { 135 | // Get keys from current file 136 | const currentKeys = this.getKeysForMap(filePath, mapName, line); 137 | 138 | // Get keys from included files 139 | const includedFiles = this.includeResolver.resolveAllIncludes(filePath); 140 | const allDirectKeys = new Set(currentKeys.directKeys); 141 | const allFunctionKeys = [...currentKeys.functionKeys]; 142 | 143 | for (const includedFile of includedFiles) { 144 | // Parse included file if not already cached 145 | if (!this.fileParsers.has(includedFile)) { 146 | try { 147 | // Check if file exists using async access 148 | await fs.promises.access(includedFile, fs.constants.F_OK); 149 | const source = await fs.promises.readFile(includedFile, 'utf8'); 150 | this.updateFile(includedFile, source); 151 | } catch (error) { 152 | // Log read errors instead of silently continuing 153 | console.warn( 154 | `[MapTrackingService] Failed to read included file ${includedFile}:`, 155 | error.message, 156 | ); 157 | continue; 158 | } 159 | } 160 | 161 | const includedKeys = this.getKeysForMap(includedFile, mapName, Infinity); 162 | 163 | // Merge keys 164 | includedKeys.directKeys.forEach(key => allDirectKeys.add(key)); 165 | allFunctionKeys.push(...includedKeys.functionKeys); 166 | } 167 | 168 | return { 169 | directKeys: Array.from(allDirectKeys), 170 | functionKeys: allFunctionKeys, 171 | }; 172 | } 173 | } 174 | 175 | export default MapTrackingService; 176 | --------------------------------------------------------------------------------