2026-10-11 04:11:11 +00:00
|
|
|
import * as vscode from 'vscode';
|
|
|
|
|
import { CompletionStrategy } from './CompletionStrategy';
|
2026-10-18 01:03:33 +00:00
|
|
|
import { tplBuiltIns } from '../../data/TplBuiltIns';
|
2026-10-27 22:38:30 +00:00
|
|
|
import { workspaceIndex } from '../../core/WorkspaceIndex';
|
2026-10-11 04:11:11 +00:00
|
|
|
|
|
|
|
|
export class LocalContextCompletion implements CompletionStrategy {
|
2026-10-12 19:43:46 +00:00
|
|
|
public async provideCompletions(document: vscode.TextDocument, position: vscode.Position, linePrefix: string): Promise<vscode.CompletionItem[] | undefined> {
|
|
|
|
|
const text = document.getText();
|
|
|
|
|
const items: vscode.CompletionItem[] = [];
|
|
|
|
|
|
2026-10-27 22:38:30 +00:00
|
|
|
// 1. SMART IMPORT: "from "
|
|
|
|
|
if (linePrefix.match(/\bfrom\s+$/)) {
|
|
|
|
|
for (const mod of workspaceIndex.getAllModules()) {
|
|
|
|
|
const item = new vscode.CompletionItem(mod, vscode.CompletionItemKind.Module);
|
|
|
|
|
item.detail = "Workspace Module";
|
|
|
|
|
items.push(item);
|
|
|
|
|
}
|
|
|
|
|
return items; // Nichts anderes vorschlagen!
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. SMART IMPORT: "from X import "
|
|
|
|
|
const importContextMatch = linePrefix.match(/\bfrom\s+([a-zA-Z0-9_.]+)\s+import\s+$/);
|
|
|
|
|
if (importContextMatch) {
|
|
|
|
|
const modName = importContextMatch[1];
|
|
|
|
|
for (const defBlock of workspaceIndex.getDefinitionBlocksForModule(modName)) {
|
|
|
|
|
const item = new vscode.CompletionItem(defBlock, vscode.CompletionItemKind.Class);
|
|
|
|
|
item.detail = `Definitions from ${modName}`;
|
|
|
|
|
items.push(item);
|
|
|
|
|
}
|
|
|
|
|
return items; // Nichts anderes vorschlagen!
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. NAMESPACE CALLS: "prefix."
|
2026-10-18 01:03:33 +00:00
|
|
|
const dotMatch = linePrefix.match(/\b([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]*)$/);
|
2026-10-12 19:43:46 +00:00
|
|
|
if (dotMatch) {
|
2026-10-18 01:03:33 +00:00
|
|
|
const prefix = dotMatch[1];
|
|
|
|
|
|
2026-10-27 22:38:30 +00:00
|
|
|
// BMC Built-ins ignorieren (übernimmt BuiltInCompletion)
|
|
|
|
|
const bmcNamespaces = Object.keys(tplBuiltIns);
|
2026-10-18 01:03:33 +00:00
|
|
|
if (bmcNamespaces.includes(prefix)) {
|
2026-10-27 22:38:30 +00:00
|
|
|
return undefined; // Hier darf undefined sein, weil BuiltInCompletion das fängt
|
2026-10-18 01:03:33 +00:00
|
|
|
}
|
2026-10-12 19:43:46 +00:00
|
|
|
|
2026-10-27 22:38:30 +00:00
|
|
|
let funcs = [];
|
|
|
|
|
|
|
|
|
|
// A) RAM-Abfrage für importierte Module
|
2026-10-12 19:43:46 +00:00
|
|
|
const importRegex = new RegExp(`from\\s+([a-zA-Z0-9_.]+)\\s+import\\s+${prefix}\\b`);
|
|
|
|
|
const match = importRegex.exec(text);
|
|
|
|
|
if (match) {
|
2026-10-18 01:03:33 +00:00
|
|
|
const moduleName = match[1];
|
2026-10-27 22:38:30 +00:00
|
|
|
// Filtere Funktionen aus dem importierten Modul, die zu diesem Prefix gehören
|
|
|
|
|
funcs = workspaceIndex.getFunctionsForModule(moduleName).filter(f => f.name.startsWith(`${prefix}.`) || !f.name.includes('.'));
|
|
|
|
|
} else {
|
|
|
|
|
// B) Lokale Definitions in der aktuellen Datei prüfen!
|
|
|
|
|
const localDefRegex = new RegExp(`^\\s*definitions?\\s+${prefix}\\b`, 'm');
|
|
|
|
|
if (localDefRegex.test(text)) {
|
|
|
|
|
const lines = text.split('\n');
|
|
|
|
|
let inTargetBlock = false;
|
|
|
|
|
for (const line of lines) {
|
|
|
|
|
if (line.match(new RegExp(`^\\s*definitions?\\s+${prefix}\\b`))) { inTargetBlock = true; continue; }
|
|
|
|
|
if (line.match(/^\s*end\s+definitions?/)) { inTargetBlock = false; continue; }
|
|
|
|
|
if (inTargetBlock) {
|
|
|
|
|
const defineMatch = line.match(/^\s*define\s+([a-zA-Z0-9_]+)\b(?:\s*\((.*?)\))?/);
|
|
|
|
|
if (defineMatch) {
|
|
|
|
|
const params = (defineMatch[2] || "").split(',').map(p => p.trim()).filter(p => p.length > 0);
|
|
|
|
|
funcs.push({ name: defineMatch[1], moduleName: 'local', params: params });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-12 19:43:46 +00:00
|
|
|
}
|
2026-10-27 22:38:30 +00:00
|
|
|
|
|
|
|
|
if (funcs.length > 0) {
|
|
|
|
|
for (const f of funcs) {
|
|
|
|
|
const shortName = f.name.split('.').pop() || f.name;
|
|
|
|
|
const item = new vscode.CompletionItem(shortName, vscode.CompletionItemKind.Function);
|
|
|
|
|
item.detail = f.moduleName === 'local' ? `Local Method` : `Method from ${f.moduleName}`;
|
|
|
|
|
|
|
|
|
|
// FIX: Dynamische Parameter-Generierung! Baut z.B. ${1:host}, ${2:ip}
|
|
|
|
|
let paramSnippet = "";
|
|
|
|
|
if (f.params && f.params.length > 0) {
|
|
|
|
|
paramSnippet = f.params.map((p, idx) => `\${${idx + 1}:${p}}`).join(', ');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
item.insertText = new vscode.SnippetString(`${shortName}(${paramSnippet});$0`);
|
|
|
|
|
items.push(item);
|
|
|
|
|
}
|
|
|
|
|
return items; // Gibt exakt die Funktionen zurück
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FIX: Wenn es ein unbekannter Namespace mit Punkt ist, geben wir ein leeres Array [] zurück!
|
|
|
|
|
// Das zwingt VS Code dazu, KEINE generischen Textwörter vorzuschlagen.
|
|
|
|
|
return [];
|
2026-10-12 19:43:46 +00:00
|
|
|
}
|
2026-10-11 04:11:11 +00:00
|
|
|
|
2026-10-27 22:38:30 +00:00
|
|
|
// 4. LOKALE DEFINITIONS PARSEN (Für den globalen Scope, noch ohne Punkt aufgerufen)
|
2026-10-12 19:43:46 +00:00
|
|
|
const lines = text.split('\n');
|
|
|
|
|
let currentDefBlock = "";
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
|
|
|
const line = lines[i];
|
|
|
|
|
const defBlockMatch = line.match(/^\s*definitions?\s+([a-zA-Z0-9_]+)/);
|
|
|
|
|
if (defBlockMatch) { currentDefBlock = defBlockMatch[1]; continue; }
|
|
|
|
|
if (line.match(/^\s*end\s+definitions?/)) { currentDefBlock = ""; continue; }
|
|
|
|
|
|
2026-10-27 22:38:30 +00:00
|
|
|
const defineMatch = line.match(/^\s*define\s+([a-zA-Z0-9_]+)\b(?:\s*\((.*?)\))?/);
|
2026-10-12 19:43:46 +00:00
|
|
|
if (defineMatch) {
|
|
|
|
|
const funcName = defineMatch[1];
|
|
|
|
|
const fullName = currentDefBlock ? `${currentDefBlock}.${funcName}` : funcName;
|
|
|
|
|
const item = new vscode.CompletionItem(fullName, vscode.CompletionItemKind.Function);
|
|
|
|
|
item.detail = currentDefBlock ? `Local Function in '${currentDefBlock}'` : `Local Function`;
|
2026-10-27 22:38:30 +00:00
|
|
|
|
|
|
|
|
// Dynamische Parameter-Generierung auch hier!
|
|
|
|
|
const params = (defineMatch[2] || "").split(',').map(p => p.trim()).filter(p => p.length > 0);
|
|
|
|
|
let paramSnippet = "";
|
|
|
|
|
if (params.length > 0) {
|
|
|
|
|
paramSnippet = params.map((p, idx) => `\${${idx + 1}:${p}}`).join(', ');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
item.insertText = new vscode.SnippetString(`${fullName}(${paramSnippet});$0`);
|
2026-10-12 19:43:46 +00:00
|
|
|
items.push(item);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-27 22:38:30 +00:00
|
|
|
// Import-Referenzen für den globalen Scope bereitstellen
|
2026-10-12 19:43:46 +00:00
|
|
|
const fromImportRegex = /from\s+([a-zA-Z0-9_.]+)\s+import\s+([a-zA-Z0-9_]+)/g;
|
|
|
|
|
let matchVar;
|
|
|
|
|
while ((matchVar = fromImportRegex.exec(text)) !== null) {
|
|
|
|
|
const item = new vscode.CompletionItem(matchVar[2], vscode.CompletionItemKind.Module);
|
|
|
|
|
item.detail = `Imported Definitions from ${matchVar[1]}`;
|
|
|
|
|
items.push(item);
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-27 22:38:30 +00:00
|
|
|
// Lokale Variablen (:=) vorschlagen
|
2026-10-12 19:43:46 +00:00
|
|
|
const varRegex = /([a-zA-Z0-9_]+)\s*:=/g;
|
|
|
|
|
const foundVars = new Set<string>();
|
|
|
|
|
while ((matchVar = varRegex.exec(text)) !== null) {
|
|
|
|
|
if (matchVar[1] !== 'host') { foundVars.add(matchVar[1]); }
|
|
|
|
|
}
|
|
|
|
|
for (const v of foundVars) {
|
|
|
|
|
items.push(new vscode.CompletionItem(v, vscode.CompletionItemKind.Variable));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return items;
|
|
|
|
|
}
|
2026-10-27 16:48:03 +00:00
|
|
|
}
|