diff --git a/.github/workflows/publish-vscode-extension.yml b/.github/workflows/publish-vscode-extension.yml new file mode 100644 index 0000000..710279e --- /dev/null +++ b/.github/workflows/publish-vscode-extension.yml @@ -0,0 +1,38 @@ +name: Publish VS Code Extension + +on: + push: + branches: + - master + paths: + - 'vscode-pace/**' + - '.github/workflows/publish-vscode-extension.yml' + +jobs: + publish: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Install dependencies + working-directory: ./vscode-pace + run: npm install + + - name: Compile extension + working-directory: ./vscode-pace + run: npm run compile + + - name: Publish to VS Code Marketplace + working-directory: ./vscode-pace + run: | + npm install -g @vscode/vsce + vsce publish -p $VSCE_PAT + env: + VSCE_PAT: ${{ secrets.VSCE_PAT }} diff --git a/vscode-pace/.gitignore b/vscode-pace/.gitignore index 40b878d..f0fd870 100644 --- a/vscode-pace/.gitignore +++ b/vscode-pace/.gitignore @@ -1 +1,3 @@ -node_modules/ \ No newline at end of file +node_modules/ +out/ +.vsix \ No newline at end of file diff --git a/vscode-pace/config.pace b/vscode-pace/config.pace new file mode 100644 index 0000000..6b91ac9 --- /dev/null +++ b/vscode-pace/config.pace @@ -0,0 +1,16 @@ +task "compile" { + command "npm run compile" + description "Compile the VSCode Pace extension" + + inputs ["package.json", "tsconfig.json", "src/*.ts"] + outputs ["out/*.js"] + cache true +} + +task "package" { + command "vsce package" + description "Package the VSCode Pace extension into a .vsix file" + dependencies ["compile"] + + cache false +} \ No newline at end of file diff --git a/vscode-pace/out/completionProvider.js b/vscode-pace/out/completionProvider.js deleted file mode 100644 index 33a831d..0000000 --- a/vscode-pace/out/completionProvider.js +++ /dev/null @@ -1,139 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -exports.PaceCompletionProvider = void 0; -const vscode = __importStar(require("vscode")); -class PaceCompletionProvider { - provideCompletionItems(document, position, token, context) { - const linePrefix = document.lineAt(position).text.substr(0, position.character); - const completions = []; - // Check if we're inside a block - const inTaskBlock = this.isInBlock(document, position, 'task'); - const inHookBlock = this.isInBlock(document, position, 'hook'); - const inEnvBlock = this.isInBlock(document, position, 'env'); - const inArgsBlock = this.isInBlock(document, position, 'args'); - // Top-level keywords - if (!inTaskBlock && !inHookBlock && !inEnvBlock && !inArgsBlock) { - completions.push(...this.getTopLevelCompletions()); - } - // Task/Hook properties - if (inTaskBlock) { - completions.push(...this.getTaskPropertyCompletions()); - } - if (inHookBlock) { - completions.push(...this.getHookPropertyCompletions()); - } - if (inArgsBlock) { - completions.push(...this.getArgsPropertyCompletions()); - } - return completions; - } - isInBlock(document, position, blockType) { - let openBraces = 0; - let inTargetBlock = false; - for (let i = position.line; i >= 0; i--) { - const line = document.lineAt(i).text; - if (line.includes('}')) - openBraces--; - if (line.includes('{')) { - openBraces++; - if (openBraces > 0 && line.includes(blockType + ' ')) { - inTargetBlock = true; - break; - } - } - } - return inTargetBlock && openBraces > 0; - } - getTopLevelCompletions() { - return [ - this.createSnippet('set', 'set "${1:VAR_NAME}" "${2:value}"', 'Define a variable'), - this.createSnippet('default', 'default "${1:task-name}"', 'Set default task'), - this.createSnippet('alias', 'alias "${1:short}" "${2:task-name}"', 'Create task alias'), - this.createSnippet('globals', 'globals {\n\t"${1:KEY}" "${2:value}"\n}', 'Define global environment variables'), - this.createSnippet('hook', 'hook "${1:hook-name}" {\n\tdescription "${2:description}"\n\tcommand "${3:command}"\n}', 'Define a hook'), - this.createSnippet('task', 'task "${1:task-name}" {\n\tdescription "${2:description}"\n\tcommand "${3:command}"\n}', 'Define a task'), - ]; - } - getTaskPropertyCompletions() { - return [ - this.createSnippet('description', 'description "${1:task description}"', 'Task description'), - this.createSnippet('command', 'command "${1:command to run}"', 'Command to execute'), - this.createSnippet('dependencies', 'dependencies [${1:"dep1", "dep2"}]', 'Task dependencies'), - this.createSnippet('before', 'before [${1:"hook1"}]', 'Hooks to run before task'), - this.createSnippet('after', 'after [${1:"hook1"}]', 'Hooks to run after task'), - this.createSnippet('on_success', 'on_success [${1:"hook1"}]', 'Hooks to run on success'), - this.createSnippet('on_failure', 'on_failure [${1:"hook1"}]', 'Hooks to run on failure'), - this.createSnippet('inputs', 'inputs [${1:"src/**/*.go"}]', 'Input file patterns'), - this.createSnippet('outputs', 'outputs [${1:"build/output"}]', 'Output file patterns'), - this.createSnippet('env', 'env {\n\t"${1:KEY}" "${2:value}"\n}', 'Environment variables'), - this.createSnippet('args', 'args {\n\trequired [${1:"arg1"}]\n}', 'Command arguments'), - this.createKeyword('cache', 'Enable caching (true/false)'), - this.createKeyword('parallel', 'Run dependencies in parallel (true/false)'), - this.createKeyword('silent', 'Suppress output (true/false)'), - this.createKeyword('watch', 'Enable watch mode (true/false)'), - this.createKeyword('continue_on_error', 'Continue on error (true/false)'), - this.createSnippet('timeout', 'timeout "${1:5m}"', 'Execution timeout'), - this.createSnippet('retry', 'retry ${1:2}', 'Number of retries'), - this.createSnippet('retry_delay', 'retry_delay "${1:3s}"', 'Delay between retries'), - ]; - } - getHookPropertyCompletions() { - return [ - this.createSnippet('description', 'description "${1:hook description}"', 'Hook description'), - this.createSnippet('command', 'command "${1:command to run}"', 'Command to execute'), - this.createSnippet('env', 'env {\n\t"${1:KEY}" "${2:value}"\n}', 'Environment variables'), - ]; - } - getArgsPropertyCompletions() { - return [ - this.createSnippet('required', 'required [${1:"arg1"}]', 'Required arguments'), - this.createSnippet('optional', 'optional [${1:"arg1"}]', 'Optional arguments'), - ]; - } - createSnippet(label, snippet, documentation) { - const item = new vscode.CompletionItem(label, vscode.CompletionItemKind.Snippet); - item.insertText = new vscode.SnippetString(snippet); - item.documentation = new vscode.MarkdownString(documentation); - return item; - } - createKeyword(label, documentation) { - const item = new vscode.CompletionItem(label, vscode.CompletionItemKind.Keyword); - item.documentation = new vscode.MarkdownString(documentation); - return item; - } -} -exports.PaceCompletionProvider = PaceCompletionProvider; -//# sourceMappingURL=completionProvider.js.map \ No newline at end of file diff --git a/vscode-pace/out/completionProvider.js.map b/vscode-pace/out/completionProvider.js.map deleted file mode 100644 index ec3d682..0000000 --- a/vscode-pace/out/completionProvider.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"completionProvider.js","sourceRoot":"","sources":["../src/completionProvider.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/vscode-pace/out/extension.js b/vscode-pace/out/extension.js deleted file mode 100644 index 1d593c5..0000000 --- a/vscode-pace/out/extension.js +++ /dev/null @@ -1,47 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -exports.activate = activate; -exports.deactivate = deactivate; -const vscode = __importStar(require("vscode")); -const completionProvider_1 = require("./completionProvider"); -function activate(context) { - console.log('Pace language extension is now active'); - // Register completion provider - const completionProvider = vscode.languages.registerCompletionItemProvider('pace', new completionProvider_1.PaceCompletionProvider(), ' ', '"', '\n'); - context.subscriptions.push(completionProvider); -} -function deactivate() { } -//# sourceMappingURL=extension.js.map \ No newline at end of file diff --git a/vscode-pace/out/extension.js.map b/vscode-pace/out/extension.js.map deleted file mode 100644 index be6dca5..0000000 --- a/vscode-pace/out/extension.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGA,4BAWC;AAED,gCAA+B;AAhB/B,+CAAiC;AACjC,6DAA8D;AAE9D,SAAgB,QAAQ,CAAC,OAAgC;IACrD,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;IAErD,+BAA+B;IAC/B,MAAM,kBAAkB,GAAG,MAAM,CAAC,SAAS,CAAC,8BAA8B,CACtE,MAAM,EACN,IAAI,2CAAsB,EAAE,EAC5B,GAAG,EAAE,GAAG,EAAE,IAAI,CACjB,CAAC;IAEF,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;AACnD,CAAC;AAED,SAAgB,UAAU,KAAI,CAAC"} \ No newline at end of file diff --git a/vscode-pace/pace-language-0.1.0.vsix b/vscode-pace/pace-language-0.1.0.vsix deleted file mode 100644 index 70c3ce1..0000000 Binary files a/vscode-pace/pace-language-0.1.0.vsix and /dev/null differ diff --git a/vscode-pace/pace-language-0.2.0.vsix b/vscode-pace/pace-language-0.2.0.vsix index be1a3d9..c0eb081 100644 Binary files a/vscode-pace/pace-language-0.2.0.vsix and b/vscode-pace/pace-language-0.2.0.vsix differ diff --git a/vscode-pace/package.json b/vscode-pace/package.json index f1e0da1..2d20e49 100644 --- a/vscode-pace/package.json +++ b/vscode-pace/package.json @@ -3,16 +3,13 @@ "displayName": "Pace Language Support", "description": "Syntax highlighting and autocomplete for Pace configuration files", "version": "0.2.0", - "publisher": "pace", + "publisher": "PaceLanguageSupport", "engines": { "vscode": "^1.75.0" }, "categories": [ "Programming Languages" ], - "activationEvents": [ - "onLanguage:pace" - ], "main": "./out/extension.js", "contributes": { "languages": [ diff --git a/vscode-pace/src/completionProvider.ts b/vscode-pace/src/completionProvider.ts index ff7dfb3..e19905a 100644 --- a/vscode-pace/src/completionProvider.ts +++ b/vscode-pace/src/completionProvider.ts @@ -1,124 +1,38 @@ import * as vscode from 'vscode'; +import { snippetsConfig } from './snippets.config'; +import { ContextDetector } from './utils/contextDetector'; +import { CompletionFactory } from './utils/completionFactory'; export class PaceCompletionProvider implements vscode.CompletionItemProvider { - provideCompletionItems( document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext ): vscode.CompletionItem[] { - - const linePrefix = document.lineAt(position).text.substr(0, position.character); - const completions: vscode.CompletionItem[] = []; + if (!ContextDetector.hasTypedContent(document, position)) { + return []; + } - // Check if we're inside a block - const inTaskBlock = this.isInBlock(document, position, 'task'); - const inHookBlock = this.isInBlock(document, position, 'hook'); - const inEnvBlock = this.isInBlock(document, position, 'env'); - const inArgsBlock = this.isInBlock(document, position, 'args'); + const completions: vscode.CompletionItem[] = []; + const docContext = ContextDetector.detectContext(document, position); - // Top-level keywords - if (!inTaskBlock && !inHookBlock && !inEnvBlock && !inArgsBlock) { - completions.push(...this.getTopLevelCompletions()); + if (docContext.isTopLevel) { + completions.push(...CompletionFactory.createCompletionItems(snippetsConfig.topLevel)); } - // Task/Hook properties - if (inTaskBlock) { - completions.push(...this.getTaskPropertyCompletions()); + if (docContext.inTaskBlock) { + completions.push(...CompletionFactory.createCompletionItems(snippetsConfig.taskProperties)); } - if (inHookBlock) { - completions.push(...this.getHookPropertyCompletions()); + if (docContext.inHookBlock) { + completions.push(...CompletionFactory.createCompletionItems(snippetsConfig.hookProperties)); } - if (inArgsBlock) { - completions.push(...this.getArgsPropertyCompletions()); + if (docContext.inArgsBlock) { + completions.push(...CompletionFactory.createCompletionItems(snippetsConfig.argsProperties)); } return completions; } - - private isInBlock(document: vscode.TextDocument, position: vscode.Position, blockType: string): boolean { - let openBraces = 0; - let inTargetBlock = false; - - for (let i = position.line; i >= 0; i--) { - const line = document.lineAt(i).text; - - if (line.includes('}')) openBraces--; - if (line.includes('{')) { - openBraces++; - if (openBraces > 0 && line.includes(blockType + ' ')) { - inTargetBlock = true; - break; - } - } - } - - return inTargetBlock && openBraces > 0; - } - - private getTopLevelCompletions(): vscode.CompletionItem[] { - return [ - this.createSnippet('set', 'set "${1:VAR_NAME}" "${2:value}"', 'Define a variable'), - this.createSnippet('default', 'default "${1:task-name}"', 'Set default task'), - this.createSnippet('alias', 'alias "${1:short}" "${2:task-name}"', 'Create task alias'), - this.createSnippet('globals', 'globals {\n\t"${1:KEY}" "${2:value}"\n}', 'Define global environment variables'), - this.createSnippet('hook', 'hook "${1:hook-name}" {\n\tdescription "${2:description}"\n\tcommand "${3:command}"\n}', 'Define a hook'), - this.createSnippet('task', 'task "${1:task-name}" {\n\tdescription "${2:description}"\n\tcommand "${3:command}"\n}', 'Define a task'), - ]; - } - - private getTaskPropertyCompletions(): vscode.CompletionItem[] { - return [ - this.createSnippet('description', 'description "${1:task description}"', 'Task description'), - this.createSnippet('command', 'command "${1:command to run}"', 'Command to execute'), - this.createSnippet('dependencies', 'dependencies [${1:"dep1", "dep2"}]', 'Task dependencies'), - this.createSnippet('before', 'before [${1:"hook1"}]', 'Hooks to run before task'), - this.createSnippet('after', 'after [${1:"hook1"}]', 'Hooks to run after task'), - this.createSnippet('on_success', 'on_success [${1:"hook1"}]', 'Hooks to run on success'), - this.createSnippet('on_failure', 'on_failure [${1:"hook1"}]', 'Hooks to run on failure'), - this.createSnippet('inputs', 'inputs [${1:"src/**/*.go"}]', 'Input file patterns'), - this.createSnippet('outputs', 'outputs [${1:"build/output"}]', 'Output file patterns'), - this.createSnippet('env', 'env {\n\t"${1:KEY}" "${2:value}"\n}', 'Environment variables'), - this.createSnippet('args', 'args {\n\trequired [${1:"arg1"}]\n}', 'Command arguments'), - this.createKeyword('cache', 'Enable caching (true/false)'), - this.createKeyword('parallel', 'Run dependencies in parallel (true/false)'), - this.createKeyword('silent', 'Suppress output (true/false)'), - this.createKeyword('watch', 'Enable watch mode (true/false)'), - this.createKeyword('continue_on_error', 'Continue on error (true/false)'), - this.createSnippet('timeout', 'timeout "${1:5m}"', 'Execution timeout'), - this.createSnippet('retry', 'retry ${1:2}', 'Number of retries'), - this.createSnippet('retry_delay', 'retry_delay "${1:3s}"', 'Delay between retries'), - ]; - } - - private getHookPropertyCompletions(): vscode.CompletionItem[] { - return [ - this.createSnippet('description', 'description "${1:hook description}"', 'Hook description'), - this.createSnippet('command', 'command "${1:command to run}"', 'Command to execute'), - this.createSnippet('env', 'env {\n\t"${1:KEY}" "${2:value}"\n}', 'Environment variables'), - ]; - } - - private getArgsPropertyCompletions(): vscode.CompletionItem[] { - return [ - this.createSnippet('required', 'required [${1:"arg1"}]', 'Required arguments'), - this.createSnippet('optional', 'optional [${1:"arg1"}]', 'Optional arguments'), - ]; - } - - private createSnippet(label: string, snippet: string, documentation: string): vscode.CompletionItem { - const item = new vscode.CompletionItem(label, vscode.CompletionItemKind.Snippet); - item.insertText = new vscode.SnippetString(snippet); - item.documentation = new vscode.MarkdownString(documentation); - return item; - } - - private createKeyword(label: string, documentation: string): vscode.CompletionItem { - const item = new vscode.CompletionItem(label, vscode.CompletionItemKind.Keyword); - item.documentation = new vscode.MarkdownString(documentation); - return item; - } } diff --git a/vscode-pace/src/extension.ts b/vscode-pace/src/extension.ts index aaffe9b..d665a0e 100644 --- a/vscode-pace/src/extension.ts +++ b/vscode-pace/src/extension.ts @@ -4,11 +4,10 @@ import { PaceCompletionProvider } from './completionProvider'; export function activate(context: vscode.ExtensionContext) { console.log('Pace language extension is now active'); - // Register completion provider const completionProvider = vscode.languages.registerCompletionItemProvider( 'pace', new PaceCompletionProvider(), - ' ', '"', '\n' + ' ', '"' ); context.subscriptions.push(completionProvider); diff --git a/vscode-pace/src/snippets.config.ts b/vscode-pace/src/snippets.config.ts new file mode 100644 index 0000000..876c081 --- /dev/null +++ b/vscode-pace/src/snippets.config.ts @@ -0,0 +1,181 @@ +import * as vscode from 'vscode'; + +export interface SnippetDefinition { + label: string; + snippet: string; + documentation: string; + kind?: vscode.CompletionItemKind; +} + +export interface SnippetsConfig { + topLevel: SnippetDefinition[]; + taskProperties: SnippetDefinition[]; + hookProperties: SnippetDefinition[]; + argsProperties: SnippetDefinition[]; +} + +export const snippetsConfig: SnippetsConfig = { + topLevel: [ + { + label: 'set', + snippet: 'set "${1:VAR_NAME}" "${2:value}"', + documentation: 'Define a variable' + }, + { + label: 'default', + snippet: 'default "${1:task-name}"', + documentation: 'Set default task' + }, + { + label: 'alias', + snippet: 'alias "${1:short}" "${2:task-name}"', + documentation: 'Create task alias' + }, + { + label: 'globals', + snippet: 'globals {\n\t"${1:KEY}" "${2:value}"\n}', + documentation: 'Define global environment variables' + }, + { + label: 'hook', + snippet: 'hook "${1:hook-name}" {\n\tdescription "${2:description}"\n\tcommand "${3:command}"\n}', + documentation: 'Define a hook' + }, + { + label: 'task', + snippet: 'task "${1:task-name}" {\n\tdescription "${2:description}"\n\tcommand "${3:command}"\n}', + documentation: 'Define a task' + } + ], + taskProperties: [ + { + label: 'description', + snippet: 'description "${1:task description}"', + documentation: 'Task description' + }, + { + label: 'command', + snippet: 'command "${1:command to run}"', + documentation: 'Command to execute' + }, + { + label: 'dependencies', + snippet: 'dependencies [${1:"dep1", "dep2"}]', + documentation: 'Task dependencies' + }, + { + label: 'before', + snippet: 'before [${1:"hook1"}]', + documentation: 'Hooks to run before task' + }, + { + label: 'after', + snippet: 'after [${1:"hook1"}]', + documentation: 'Hooks to run after task' + }, + { + label: 'on_success', + snippet: 'on_success [${1:"hook1"}]', + documentation: 'Hooks to run on success' + }, + { + label: 'on_failure', + snippet: 'on_failure [${1:"hook1"}]', + documentation: 'Hooks to run on failure' + }, + { + label: 'inputs', + snippet: 'inputs [${1:"src/**/*.go"}]', + documentation: 'Input file patterns' + }, + { + label: 'outputs', + snippet: 'outputs [${1:"build/output"}]', + documentation: 'Output file patterns' + }, + { + label: 'env', + snippet: 'env {\n\t"${1:KEY}" "${2:value}"\n}', + documentation: 'Environment variables' + }, + { + label: 'args', + snippet: 'args {\n\trequired [${1:"arg1"}]\n}', + documentation: 'Command arguments' + }, + { + label: 'cache', + snippet: 'cache', + documentation: 'Enable caching (true/false)', + kind: vscode.CompletionItemKind.Keyword + }, + { + label: 'parallel', + snippet: 'parallel', + documentation: 'Run dependencies in parallel (true/false)', + kind: vscode.CompletionItemKind.Keyword + }, + { + label: 'silent', + snippet: 'silent', + documentation: 'Suppress output (true/false)', + kind: vscode.CompletionItemKind.Keyword + }, + { + label: 'watch', + snippet: 'watch', + documentation: 'Enable watch mode (true/false)', + kind: vscode.CompletionItemKind.Keyword + }, + { + label: 'continue_on_error', + snippet: 'continue_on_error', + documentation: 'Continue on error (true/false)', + kind: vscode.CompletionItemKind.Keyword + }, + { + label: 'timeout', + snippet: 'timeout "${1:5m}"', + documentation: 'Execution timeout' + }, + { + label: 'retry', + snippet: 'retry ${1:2}', + documentation: 'Number of retries' + }, + { + label: 'retry_delay', + snippet: 'retry_delay "${1:3s}"', + documentation: 'Delay between retries' + } + ], + hookProperties: [ + { + label: 'description', + snippet: 'description "${1:hook description}"', + documentation: 'Hook description' + }, + { + label: 'command', + snippet: 'command "${1:command to run}"', + documentation: 'Command to execute' + }, + { + label: 'env', + snippet: 'env {\n\t"${1:KEY}" "${2:value}"\n}', + documentation: 'Environment variables' + } + ], + argsProperties: [ + { + label: 'required', + snippet: 'required [${1:"arg1"}]', + documentation: 'Required arguments' + }, + { + label: 'optional', + snippet: 'optional [${1:"arg1"}]', + documentation: 'Optional arguments' + } + ] +}; diff --git a/vscode-pace/src/utils/completionFactory.ts b/vscode-pace/src/utils/completionFactory.ts new file mode 100644 index 0000000..01979d1 --- /dev/null +++ b/vscode-pace/src/utils/completionFactory.ts @@ -0,0 +1,22 @@ +import * as vscode from 'vscode'; +import { SnippetDefinition } from '../snippets.config'; + +export class CompletionFactory { + static createCompletionItem(definition: SnippetDefinition): vscode.CompletionItem { + const kind = definition.kind || vscode.CompletionItemKind.Snippet; + const item = new vscode.CompletionItem(definition.label, kind); + + if (kind === vscode.CompletionItemKind.Snippet) { + item.insertText = new vscode.SnippetString(definition.snippet); + } else { + item.insertText = definition.snippet; + } + + item.documentation = new vscode.MarkdownString(definition.documentation); + return item; + } + + static createCompletionItems(definitions: SnippetDefinition[]): vscode.CompletionItem[] { + return definitions.map(def => this.createCompletionItem(def)); + } +} diff --git a/vscode-pace/src/utils/contextDetector.ts b/vscode-pace/src/utils/contextDetector.ts new file mode 100644 index 0000000..9278a4e --- /dev/null +++ b/vscode-pace/src/utils/contextDetector.ts @@ -0,0 +1,56 @@ +import * as vscode from 'vscode'; + +export interface DocumentContext { + inTaskBlock: boolean; + inHookBlock: boolean; + inEnvBlock: boolean; + inArgsBlock: boolean; + isTopLevel: boolean; +} + +export class ContextDetector { + static detectContext(document: vscode.TextDocument, position: vscode.Position): DocumentContext { + const inTaskBlock = this.isInBlock(document, position, 'task'); + const inHookBlock = this.isInBlock(document, position, 'hook'); + const inEnvBlock = this.isInBlock(document, position, 'env'); + const inArgsBlock = this.isInBlock(document, position, 'args'); + + return { + inTaskBlock, + inHookBlock, + inEnvBlock, + inArgsBlock, + isTopLevel: !inTaskBlock && !inHookBlock && !inEnvBlock && !inArgsBlock + }; + } + + private static isInBlock( + document: vscode.TextDocument, + position: vscode.Position, + blockType: string + ): boolean { + let openBraces = 0; + let inTargetBlock = false; + + for (let i = position.line; i >= 0; i--) { + const line = document.lineAt(i).text; + + if (line.includes('}')) openBraces--; + if (line.includes('{')) { + openBraces++; + if (openBraces > 0 && line.includes(blockType + ' ')) { + inTargetBlock = true; + break; + } + } + } + + return inTargetBlock && openBraces > 0; + } + + static hasTypedContent(document: vscode.TextDocument, position: vscode.Position): boolean { + const lineText = document.lineAt(position.line).text; + const textBeforeCursor = lineText.substring(0, position.character).trim(); + return textBeforeCursor.length > 0; + } +}