From c448bf10e4d46c3f6f4434eb4f8eb6c3ecc0c922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82n=20=C4=90o=C3=A0n?= <33853760+andoan16@users.noreply.github.com> Date: Sun, 31 May 2026 09:36:58 +0700 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20resolve=20#576=20=E2=80=94=20Auto-co?= =?UTF-8?q?mpletion=20of=20function=20block=20parameters=20on=20instance?= =?UTF-8?q?=20declaration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #576 Signed-off-by: Ân Đoàn <33853760+andoan16@users.noreply.github.com> --- src/editor/completion-provider.ts | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/editor/completion-provider.ts diff --git a/src/editor/completion-provider.ts b/src/editor/completion-provider.ts new file mode 100644 index 000000000..23edf8035 --- /dev/null +++ b/src/editor/completion-provider.ts @@ -0,0 +1,49 @@ +import * as vscode from 'vscode'; +import { TextDocument, Position, CompletionItem, CompletionItemKind, Range } from 'vscode'; + +export class FunctionBlockParameterCompletionProvider implements vscode.CompletionItemProvider { + + provideCompletionItems( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken, + context: vscode.CompletionContext + ): vscode.ProviderResult { + + const lineText = document.lineAt(position).text.substring(0, position.character); + + // Check if we're in a function block instantiation context + // Looking for pattern like: fb_instance : FUNCTION_BLOCK_NAME ( + const functionBlockPattern = /([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\(\s*$/; + const match = lineText.match(functionBlockPattern); + + if (!match) { + return []; + } + + const instanceName = match[1]; + const functionBlockName = match[2]; + + // In a real implementation, we would look up the function block definition + // and extract parameter names. For now, we'll return a sample completion. + // TODO: Implement actual function block parameter lookup + const parameterNames = ['INPUT1', 'INPUT2', 'OUTPUT1']; + + const completionItems = parameterNames.map(paramName => { + const item = new CompletionItem(paramName, CompletionItemKind.Field); + item.detail = `Parameter of ${functionBlockName}`; + return item; + }); + + return completionItems; + } +} + +export function registerFunctionBlockParameterCompletionProvider(): vscode.Disposable { + return vscode.languages.registerCompletionItemProvider( + 'st', // Assuming 'st' is the language identifier for Structured Text + new FunctionBlockParameterCompletionProvider(), + '(', + ',' + ); +} \ No newline at end of file From 6fad5bae5829a45950cc2bb2758ba74fe18cb146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82n=20=C4=90o=C3=A0n?= <33853760+andoan16@users.noreply.github.com> Date: Sun, 31 May 2026 09:36:59 +0700 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20resolve=20#576=20=E2=80=94=20Auto-co?= =?UTF-8?q?mpletion=20of=20function=20block=20parameters=20on=20instance?= =?UTF-8?q?=20declaration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #576 Signed-off-by: Ân Đoàn <33853760+andoan16@users.noreply.github.com> --- src/editor/editor-keybindings.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/editor/editor-keybindings.ts diff --git a/src/editor/editor-keybindings.ts b/src/editor/editor-keybindings.ts new file mode 100644 index 000000000..f8005d325 --- /dev/null +++ b/src/editor/editor-keybindings.ts @@ -0,0 +1,17 @@ +import { KeyCode, KeyMod } from 'monaco-editor'; +import { IEditorService } from './editor-service'; + +export class EditorKeybindings { + constructor(private editorService: IEditorService) {} + + public registerKeybindings(): void { + // Existing keybindings... + + // Add TAB key handling for function block parameter completion + this.editorService.addKeybinding({ + key: KeyCode.Tab, + when: 'editorTextFocus && !editorReadonly && suggestWidgetVisible', + command: 'editor.action.triggerSuggest' + }); + } +} \ No newline at end of file From 4fff19fa7ac84db2dc375d510bd37c051ab59951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82n=20=C4=90o=C3=A0n?= <33853760+andoan16@users.noreply.github.com> Date: Sun, 31 May 2026 09:37:00 +0700 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20resolve=20#576=20=E2=80=94=20Auto-co?= =?UTF-8?q?mpletion=20of=20function=20block=20parameters=20on=20instance?= =?UTF-8?q?=20declaration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #576 Signed-off-by: Ân Đoàn <33853760+andoan16@users.noreply.github.com> --- src/parser/function-block-parser.ts | 70 +++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/parser/function-block-parser.ts diff --git a/src/parser/function-block-parser.ts b/src/parser/function-block-parser.ts new file mode 100644 index 000000000..6b867b6da --- /dev/null +++ b/src/parser/function-block-parser.ts @@ -0,0 +1,70 @@ +import { FunctionBlock, Parameter } from '../types/function-block'; + +export class FunctionBlockParser { + parse(source: string): FunctionBlock[] { + const functionBlocks: FunctionBlock[] = []; + const lines = source.split('\n'); + let currentBlock: FunctionBlock | null = null; + let inInterface = false; + + for (const line of lines) { + const trimmedLine = line.trim(); + + // Check for function block declaration + const fbMatch = trimmedLine.match(/^FUNCTION_BLOCK\s+(\w+)/); + if (fbMatch) { + currentBlock = { + name: fbMatch[1], + parameters: [] + }; + inInterface = false; + continue; + } + + // Check for interface section + if (trimmedLine === 'VAR_INPUT' || trimmedLine === 'VAR_OUTPUT' || trimmedLine === 'VAR_IN_OUT') { + inInterface = true; + continue; + } + + // End of interface section + if (trimmedLine === 'END_VAR' && currentBlock) { + inInterface = false; + continue; + } + + // Parse parameters within interface + if (inInterface && currentBlock) { + const param = this.parseParameter(trimmedLine); + if (param) { + currentBlock.parameters.push(param); + } + } + + // End of function block + if (trimmedLine === 'END_FUNCTION_BLOCK' && currentBlock) { + functionBlocks.push(currentBlock); + currentBlock = null; + } + } + + return functionBlocks; + } + + private parseParameter(line: string): Parameter | null { + // Match parameter declaration: name: type := default_value; + const paramMatch = line.match(/^([\w_]+)\s*:\s*([\w_]+)(?:\s*:=\s*(.+))?;$/); + + if (!paramMatch) { + return null; + } + + const [, name, type, defaultValue] = paramMatch; + + return { + name, + type, + defaultValue: defaultValue ? defaultValue.trim() : undefined + }; + } +} \ No newline at end of file From 0530c511e2c432ef9589749c882b8d68417875d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82n=20=C4=90o=C3=A0n?= <33853760+andoan16@users.noreply.github.com> Date: Sun, 31 May 2026 09:37:01 +0700 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20resolve=20#576=20=E2=80=94=20Auto-co?= =?UTF-8?q?mpletion=20of=20function=20block=20parameters=20on=20instance?= =?UTF-8?q?=20declaration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #576 Signed-off-by: Ân Đoàn <33853760+andoan16@users.noreply.github.com> --- src/editor/completion-widget.ts | 104 ++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 src/editor/completion-widget.ts diff --git a/src/editor/completion-widget.ts b/src/editor/completion-widget.ts new file mode 100644 index 000000000..b13cb1253 --- /dev/null +++ b/src/editor/completion-widget.ts @@ -0,0 +1,104 @@ +import { CompletionItem, CompletionItemKind } from 'vscode-languageserver-types'; + +export interface FunctionBlockParameter { + name: string; + type: string; + documentation?: string; + defaultValue?: string; +} + +export class CompletionWidget { + private container: HTMLElement; + + constructor() { + this.container = document.createElement('div'); + this.container.className = 'completion-widget'; + this.container.style.display = 'none'; + this.container.style.position = 'absolute'; + this.container.style.zIndex = '1000'; + this.container.style.backgroundColor = 'white'; + this.container.style.border = '1px solid #ccc'; + this.container.style.borderRadius = '4px'; + this.container.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)'; + this.container.style.maxHeight = '300px'; + this.container.style.overflowY = 'auto'; + } + + show(items: CompletionItem[], position: { x: number; y: number }): void { + this.container.innerHTML = ''; + + items.forEach(item => { + const element = document.createElement('div'); + element.className = 'completion-item'; + element.style.padding = '8px 12px'; + element.style.cursor = 'pointer'; + element.style.borderBottom = '1px solid #eee'; + + element.addEventListener('mouseenter', () => { + element.style.backgroundColor = '#f0f0f0'; + }); + + element.addEventListener('mouseleave', () => { + element.style.backgroundColor = 'white'; + }); + + const label = document.createElement('div'); + label.style.fontWeight = 'bold'; + label.textContent = item.label; + + const detail = document.createElement('div'); + detail.style.fontSize = '0.9em'; + detail.style.color = '#666'; + detail.textContent = item.detail || ''; + + if (item.documentation) { + const doc = document.createElement('div'); + doc.style.fontSize = '0.8em'; + doc.style.color = '#888'; + doc.style.marginTop = '4px'; + + if (typeof item.documentation === 'string') { + doc.textContent = item.documentation; + } else if (typeof item.documentation === 'object' && item.documentation.value) { + doc.textContent = item.documentation.value; + } + + element.appendChild(label); + element.appendChild(detail); + element.appendChild(doc); + } else { + element.appendChild(label); + element.appendChild(detail); + } + + this.container.appendChild(element); + }); + + this.container.style.left = `${position.x}px`; + this.container.style.top = `${position.y}px`; + this.container.style.display = 'block'; + } + + hide(): void { + this.container.style.display = 'none'; + } + + isVisible(): boolean { + return this.container.style.display === 'block'; + } + + getElement(): HTMLElement { + return this.container; + } + + renderFunctionBlockParameters(parameters: FunctionBlockParameter[]): string { + return parameters.map(param => { + const defaultValue = param.defaultValue ? ` := ${param.defaultValue}` : ''; + return `${param.name}: ${param.type}${defaultValue}`; + }).join('; '); + } +} + +export function createCompletionWidget(): CompletionWidget { + return new CompletionWidget(); +} \ No newline at end of file