Erweiterung die feste log regel fällt raus, dafür sind jetzt alle möglichen BMC TPL erweiterungen als auto complete drin
This commit is contained in:
parent
93f3179bcd
commit
a5cd6a74ca
|
|
@ -4,4 +4,7 @@ node_modules
|
||||||
.vscode-test/
|
.vscode-test/
|
||||||
*.vsix
|
*.vsix
|
||||||
src/*.js*
|
src/*.js*
|
||||||
src/test/*.js*
|
src/test/*.js*
|
||||||
|
out/*
|
||||||
|
out/*/*
|
||||||
|
out/*/*/*
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
export const tplBuiltIns: Record<string, Record<string, string>> = {
|
||||||
|
"model": {
|
||||||
|
"NodeKind": "Erstellt oder aktualisiert einen Knoten basierend auf der Taxonomie (z.B. model.SoftwareInstance(key:=\"...\", name:=\"...\")).",
|
||||||
|
"destroy": "Zerstört den übergebenen Knoten dauerhaft und sofort aus der Datenbank.",
|
||||||
|
"withdraw": "Zieht die Behauptung des aktuellen Patterns für diesen Knoten zurück (leitet das Standard-Aging ein).",
|
||||||
|
"host": "Gibt den übergeordneten Host-Knoten zurück, der mit dem übergebenen Knoten (z.B. Prozess oder Software) verknüpft ist.",
|
||||||
|
"addDisplayAttribute": "Fügt dem Knoten ein Attribut hinzu, das in der Web-Oberfläche ganz oben hervorgehoben wird.",
|
||||||
|
"setRemovalGroup": "Ordnet den Knoten einer Gruppe zu. Knoten dieser Gruppe, die beim nächsten Scan nicht neu bestätigt werden, werden gelöscht.",
|
||||||
|
"findEq": "Führt eine exakte Suche nach Knoten in der Datenbank durch (z.B. Suche nach einer bestimmten ID)."
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"split": "Teilt einen String anhand eines Trennzeichens in eine Liste von Strings auf.",
|
||||||
|
"join": "Verbindet eine Liste von Strings zu einem einzigen String unter Verwendung eines Trennzeichens.",
|
||||||
|
"lower": "Konvertiert alle Zeichen eines Strings in Kleinbuchstaben.",
|
||||||
|
"upper": "Konvertiert alle Zeichen eines Strings in Großbuchstaben.",
|
||||||
|
"strip": "Entfernt führende und nachfolgende Leerzeichen (Whitespaces) aus einem String.",
|
||||||
|
"replace": "Ersetzt alle Vorkommen eines Suchstrings durch einen neuen String.",
|
||||||
|
"hasPrefix": "Prüft, ob ein String mit einem bestimmten Präfix beginnt (gibt true oder false zurück).",
|
||||||
|
"hasSuffix": "Prüft, ob ein String mit einem bestimmten Suffix endet (gibt true oder false zurück).",
|
||||||
|
"find": "Sucht einen Teilstring innerhalb eines Strings und gibt die numerische Position zurück (oder -1, wenn nicht gefunden).",
|
||||||
|
"hash": "Generiert einen numerischen Hash-Wert aus dem übergebenen String (nützlich für die Key-Generierung)."
|
||||||
|
},
|
||||||
|
"regex": {
|
||||||
|
"extract": "Extrahiert den ersten Treffer einer Capture-Group (in runden Klammern) aus einem String. Nimmt optional einen Fallback-Wert.",
|
||||||
|
"match": "Prüft, ob ein regulärer Ausdruck auf einen String zutrifft (gibt true oder false zurück).",
|
||||||
|
"findAll": "Findet alle Treffer eines regulären Ausdrucks in einem String und gibt sie als Liste zurück.",
|
||||||
|
"replace": "Ersetzt Muster in einem String, die auf den regulären Ausdruck zutreffen, durch einen neuen String."
|
||||||
|
},
|
||||||
|
"discovery": {
|
||||||
|
"runCommand": "Führt einen Kommandozeilenbefehl auf dem gescannten Zielsystem (Host) aus und gibt das Ergebnis zurück.",
|
||||||
|
"fileGet": "Ruft den vollständigen Inhalt einer Datei (z.B. Config-File) vom Zielsystem ab.",
|
||||||
|
"fileInfo": "Ruft nur die Metadaten (Größe, Rechte, Zeitstempel) einer Datei ab, ohne den Inhalt zu lesen.",
|
||||||
|
"listDirectory": "Listet den Inhalt (Dateien und Unterordner) eines Verzeichnisses auf dem Zielsystem auf.",
|
||||||
|
"registryKey": "Liest einen bestimmten Registry-Schlüssel auf einem Windows-Host aus.",
|
||||||
|
"wmiQuery": "Führt eine WQL-Abfrage per WMI auf einem Windows-Host aus.",
|
||||||
|
"snmpGet": "Führt einen SNMP GET-Request für eine spezifische OID aus (z.B. für Netzwerkgeräte).",
|
||||||
|
"snmpGetNext": "Führt einen SNMP GETNEXT-Request aus.",
|
||||||
|
"snmpGetTable": "Ruft eine komplette SNMP-Tabelle vom Zielgerät ab.",
|
||||||
|
"databaseQuery": "Führt eine SQL-Abfrage über eine in Discovery konfigurierte Datenbank-Integration aus.",
|
||||||
|
"restRequest": "Führt einen REST-API Request gegen ein konfiguriertes Ziel aus."
|
||||||
|
},
|
||||||
|
"log": {
|
||||||
|
"debug": "Schreibt eine Debug-Nachricht in das Discovery-Log (nur bei Loglevel DEBUG sichtbar, für tiefe Fehleranalyse).",
|
||||||
|
"info": "Schreibt eine Informationsnachricht in das Log (verfolgt den normalen Ablauf).",
|
||||||
|
"warn": "Schreibt eine Warnung in das Log (für unkritische Fehler, z.B. wenn eine Datei leer ist).",
|
||||||
|
"error": "Schreibt eine Fehlermeldung in das Log (für kritische Pattern-Fehler, die den Ablauf stören).",
|
||||||
|
"critical": "Schreibt einen kritischen Systemfehler in das Log."
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"current": "Gibt den aktuellen Zeitpunkt der Ausführung in TPL-Epochenzeit zurück.",
|
||||||
|
"format": "Formatiert einen TPL-Zeitstempel in einen für Menschen lesbaren String (z.B. \"%Y-%m-%d %H:%M:%S\").",
|
||||||
|
"parseUTC": "Parst einen Zeit-String als UTC-Zeit und konvertiert ihn in einen TPL-Zeitstempel.",
|
||||||
|
"parseLocal": "Parst einen Zeit-String als lokale Zeit des Hosts und konvertiert ihn in einen TPL-Zeitstempel.",
|
||||||
|
"delta": "Berechnet die Differenz zwischen zwei Zeitstempeln (gibt eine Dauer zurück)."
|
||||||
|
},
|
||||||
|
"json": {
|
||||||
|
"decode": "Parst einen JSON-String und konvertiert ihn in durchsuchbare TPL-Datenstrukturen (Listen und Dictionaries).",
|
||||||
|
"encode": "Konvertiert eine TPL-Datenstruktur (wie eine Liste oder ein Dictionary) zurück in einen JSON-formatierten String."
|
||||||
|
},
|
||||||
|
"xpath": {
|
||||||
|
"evaluate": "Führt eine XPath-Abfrage direkt auf einem XML-String aus und gibt die gefundenen Elemente oder Texte zurück.",
|
||||||
|
"open": "Öffnet und parst ein XML-Dokument für komplexere oder mehrfache Abfragen, was performanter als wiederholtes evaluate ist."
|
||||||
|
},
|
||||||
|
"hash": {
|
||||||
|
"md5": "Generiert einen MD5-Hash aus dem übergebenen String.",
|
||||||
|
"sha1": "Generiert einen SHA-1-Hash aus dem übergebenen String.",
|
||||||
|
"sha256": "Generiert einen SHA-256-Hash aus dem übergebenen String."
|
||||||
|
},
|
||||||
|
"math": {
|
||||||
|
"abs": "Gibt den absoluten (positiven) Wert einer Zahl zurück.",
|
||||||
|
"ceil": "Rundet eine Zahl auf die nächste Ganzzahl auf.",
|
||||||
|
"floor": "Rundet eine Zahl auf die nächste Ganzzahl ab.",
|
||||||
|
"round": "Rundet eine Zahl nach kaufmännischen Regeln.",
|
||||||
|
"min": "Gibt die kleinste Zahl aus einer übergebenen Liste von Zahlen zurück.",
|
||||||
|
"max": "Gibt die größte Zahl aus einer übergebenen Liste von Zahlen zurück."
|
||||||
|
},
|
||||||
|
"net": {
|
||||||
|
"ipFromString": "Konvertiert einen IP-String in ein internes TPL-IP-Objekt zur weiteren Verarbeitung.",
|
||||||
|
"ipToString": "Konvertiert ein TPL-IP-Objekt zurück in einen lesbaren String.",
|
||||||
|
"isIPv4": "Prüft, ob es sich bei dem String um eine gültige IPv4-Adresse handelt (true/false).",
|
||||||
|
"isIPv6": "Prüft, ob es sich bei dem String um eine gültige IPv6-Adresse handelt (true/false).",
|
||||||
|
"isMAC": "Prüft, ob der String das Format einer gültigen MAC-Adresse hat (true/false).",
|
||||||
|
"inSubnet": "Prüft, ob eine bestimmte IP-Adresse innerhalb eines angegebenen Subnetzes liegt."
|
||||||
|
},
|
||||||
|
"built_in_globals": {
|
||||||
|
"size": "Gibt die Länge oder Anzahl der Elemente einer Liste, eines Strings oder eines Dictionaries zurück.",
|
||||||
|
"toNumber": "Konvertiert einen String (z.B. \"123\") in einen Integer (Ganzzahl).",
|
||||||
|
"toString": "Konvertiert einen Wert (z.B. eine Zahl oder ein Objekt) in einen String.",
|
||||||
|
"type": "Gibt den Datentyp der übergebenen Variable als String zurück (z.B. \"list\", \"string\").",
|
||||||
|
"defined": "Prüft, ob eine Variable existiert oder ob ein Attribut an einem Knoten vorhanden ist, bevor darauf zugegriffen wird."
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
import { tplBuiltIns } from './TplBuiltIns';
|
||||||
|
|
||||||
export class TplCompletionProvider implements vscode.CompletionItemProvider {
|
export class TplCompletionProvider implements vscode.CompletionItemProvider {
|
||||||
async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.CompletionItem[]> {
|
async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.CompletionItem[]> {
|
||||||
|
|
@ -6,15 +7,6 @@ export class TplCompletionProvider implements vscode.CompletionItemProvider {
|
||||||
const items: vscode.CompletionItem[] = [];
|
const items: vscode.CompletionItem[] = [];
|
||||||
const text = document.getText();
|
const text = document.getText();
|
||||||
|
|
||||||
// 1. Die feste "log." Regel
|
|
||||||
if (linePrefix.endsWith('log.')) {
|
|
||||||
['debug', 'info', 'warn', 'error'].forEach(method => {
|
|
||||||
items.push(new vscode.CompletionItem(method, vscode.CompletionItemKind.Method));
|
|
||||||
});
|
|
||||||
return items; // Zwingt VS Code, nur diese 4 anzuzeigen!
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Importe und lokale Definitions-Blöcke (Namespaces) sammeln
|
|
||||||
const importMap = new Map<string, string>();
|
const importMap = new Map<string, string>();
|
||||||
const importRegex = /from\s+([\w.]+)\s+import\s+(\w+)/g;
|
const importRegex = /from\s+([\w.]+)\s+import\s+(\w+)/g;
|
||||||
let m;
|
let m;
|
||||||
|
|
@ -28,11 +20,10 @@ export class TplCompletionProvider implements vscode.CompletionItemProvider {
|
||||||
localNamespaces.add(m[1]);
|
localNamespaces.add(m[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Wenn wir auf freier Fläche tippen (kein Punkt davor)
|
|
||||||
const matchDot = linePrefix.match(/([a-zA-Z0-9_]+)\.$/);
|
const matchDot = linePrefix.match(/([a-zA-Z0-9_]+)\.$/);
|
||||||
|
|
||||||
if (!matchDot) {
|
if (!matchDot) {
|
||||||
// A) Namespaces (Importiert und Lokal) vorschlagen
|
// A) Externe & Lokale Module
|
||||||
importMap.forEach((_, key) => {
|
importMap.forEach((_, key) => {
|
||||||
const item = new vscode.CompletionItem(key, vscode.CompletionItemKind.Module);
|
const item = new vscode.CompletionItem(key, vscode.CompletionItemKind.Module);
|
||||||
item.detail = "Importiertes Modul";
|
item.detail = "Importiertes Modul";
|
||||||
|
|
@ -44,7 +35,23 @@ export class TplCompletionProvider implements vscode.CompletionItemProvider {
|
||||||
items.push(item);
|
items.push(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
// B) Lokale Variablen vorschlagen (z.B. attribute_list :=)
|
// B) Standard BMC Namespaces (text, regex, discovery etc.)
|
||||||
|
for (const key of Object.keys(tplBuiltIns)) {
|
||||||
|
if (key !== 'built_in_globals') {
|
||||||
|
const item = new vscode.CompletionItem(key, vscode.CompletionItemKind.Module);
|
||||||
|
item.detail = "BMC Built-In Namespace";
|
||||||
|
items.push(item);
|
||||||
|
} else {
|
||||||
|
for (const glob of Object.keys(tplBuiltIns[key])) {
|
||||||
|
const item = new vscode.CompletionItem(glob, vscode.CompletionItemKind.Function);
|
||||||
|
item.detail = "BMC Globale Funktion";
|
||||||
|
item.documentation = new vscode.MarkdownString(tplBuiltIns[key][glob]);
|
||||||
|
items.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// C) Lokale Variablen
|
||||||
const varRegex = /\b([a-zA-Z0-9_]+)\s*:=/g;
|
const varRegex = /\b([a-zA-Z0-9_]+)\s*:=/g;
|
||||||
const variables = new Set<string>();
|
const variables = new Set<string>();
|
||||||
while ((m = varRegex.exec(text)) !== null) {
|
while ((m = varRegex.exec(text)) !== null) {
|
||||||
|
|
@ -54,39 +61,20 @@ export class TplCompletionProvider implements vscode.CompletionItemProvider {
|
||||||
items.push(new vscode.CompletionItem(v, vscode.CompletionItemKind.Variable));
|
items.push(new vscode.CompletionItem(v, vscode.CompletionItemKind.Variable));
|
||||||
});
|
});
|
||||||
|
|
||||||
// C) Das große Skeleton Snippet
|
// D) Snippets
|
||||||
const skeletonItem = new vscode.CompletionItem('tpl_skeleton', vscode.CompletionItemKind.Snippet);
|
const skeletonItem = new vscode.CompletionItem('tpl_skeleton', vscode.CompletionItemKind.Snippet);
|
||||||
skeletonItem.insertText = new vscode.SnippetString(
|
skeletonItem.insertText = new vscode.SnippetString(
|
||||||
'tpl 1.23 module ${1:module_name};\n\n' +
|
'tpl 1.23 module ${1:module_name};\n\n' +
|
||||||
'metadata\n' +
|
'metadata\n origin := \'${2:origin}\';\n author := \'${3:author}\';\n products := \'${4:products}\';\n publishers := \'${5:publishers}\';\n tree_path := \'${6:company}\', \'${7:category}\', \'${8:subcategory}\';\nend metadata;\n\n' +
|
||||||
' origin := \'${2:origin}\';\n' +
|
'pattern ${9:pattern_name} 1.0\n """\n TODO: explain\n """\n\n triggers\n on process := DiscoveredProcess where cmd matches regex_escape("${10:process}");\n end triggers;\n\n body\n log.debug("Start pattern ${9:pattern_name}");\n $0\n end body;\nend pattern;'
|
||||||
' author := \'${3:author}\';\n' +
|
|
||||||
' products := \'${4:products}\';\n' +
|
|
||||||
' publishers := \'${5:publishers}\';\n' +
|
|
||||||
' tree_path := \'${6:company}\', \'${7:category}\', \'${8:subcategory}\';\n' +
|
|
||||||
'end metadata;\n\n' +
|
|
||||||
'pattern ${9:pattern_name} 1.0\n' +
|
|
||||||
' """\n' +
|
|
||||||
' TODO: explain\n' +
|
|
||||||
' """\n\n' +
|
|
||||||
' triggers\n' +
|
|
||||||
' on process := DiscoveredProcess where cmd matches regex_escape("${10:process}");\n' +
|
|
||||||
' end triggers;\n\n' +
|
|
||||||
' body\n' +
|
|
||||||
' log.debug("Start pattern ${9:pattern_name}");\n' +
|
|
||||||
' $0\n' +
|
|
||||||
' end body;\n' +
|
|
||||||
'end pattern;'
|
|
||||||
);
|
);
|
||||||
skeletonItem.detail = "Generiert das komplette TPL Modul-Grundgerüst";
|
skeletonItem.detail = "Generiert das komplette TPL Modul-Grundgerüst";
|
||||||
items.push(skeletonItem);
|
items.push(skeletonItem);
|
||||||
|
|
||||||
// D) Block-Snippets
|
|
||||||
const blocks = ['metadata', 'overview', 'constants', 'triggers', 'body', 'mapping', 'removal', 'definitions', 'enumeration', 'configuration'];
|
const blocks = ['metadata', 'overview', 'constants', 'triggers', 'body', 'mapping', 'removal', 'definitions', 'enumeration', 'configuration'];
|
||||||
for (const block of blocks) {
|
for (const block of blocks) {
|
||||||
const item = new vscode.CompletionItem(block, vscode.CompletionItemKind.Snippet);
|
const item = new vscode.CompletionItem(block, vscode.CompletionItemKind.Snippet);
|
||||||
item.insertText = new vscode.SnippetString(`${block}\n $0\nend ${block};`);
|
item.insertText = new vscode.SnippetString(`${block}\n $0\nend ${block};`);
|
||||||
item.detail = `Fügt ${block} Block mit 'end' ein`;
|
|
||||||
items.push(item);
|
items.push(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,39 +94,36 @@ export class TplCompletionProvider implements vscode.CompletionItemProvider {
|
||||||
tableItem.insertText = new vscode.SnippetString('table ${1:name}${2:1.0}\n $0\n default -> "unknown";\nend table;');
|
tableItem.insertText = new vscode.SnippetString('table ${1:name}${2:1.0}\n $0\n default -> "unknown";\nend table;');
|
||||||
items.push(tableItem);
|
items.push(tableItem);
|
||||||
|
|
||||||
const ifItem = new vscode.CompletionItem('if', vscode.CompletionItemKind.Snippet);
|
|
||||||
ifItem.insertText = new vscode.SnippetString('if ${1:condition} then\n $0\nend if;');
|
|
||||||
items.push(ifItem);
|
|
||||||
|
|
||||||
const forItem = new vscode.CompletionItem('for', vscode.CompletionItemKind.Snippet);
|
|
||||||
forItem.insertText = new vscode.SnippetString('for ${1:item} in${2:list} do\n $0\nend for;');
|
|
||||||
items.push(forItem);
|
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
} else {
|
} else {
|
||||||
// 4. Wir haben nach einem Punkt getippt (z.B. helpers.)
|
|
||||||
const ns = matchDot[1];
|
const ns = matchDot[1];
|
||||||
const moduleMethods: vscode.CompletionItem[] = [];
|
|
||||||
|
// NEU: Ist es ein BMC Built-In Namespace? (z.B. text. oder log.)
|
||||||
|
if (tplBuiltIns[ns]) {
|
||||||
|
const bmcMethods: vscode.CompletionItem[] = [];
|
||||||
|
for (const [method, desc] of Object.entries(tplBuiltIns[ns])) {
|
||||||
|
const item = new vscode.CompletionItem(method, vscode.CompletionItemKind.Method);
|
||||||
|
item.documentation = new vscode.MarkdownString(desc);
|
||||||
|
bmcMethods.push(item);
|
||||||
|
}
|
||||||
|
return bmcMethods;
|
||||||
|
}
|
||||||
|
|
||||||
// Regex sucht nach dem Wort direkt vor der Klammer: define [add] method_name(
|
const moduleMethods: vscode.CompletionItem[] = [];
|
||||||
const extractDefineRegex = /^\s*define\s+.*?\b([a-zA-Z0-9_]+)\s*\(/gm;
|
const extractDefineRegex = /^\s*define\s+.*?\b([a-zA-Z0-9_]+)\s*\(/gm;
|
||||||
|
|
||||||
// Fall A: Lokaler Definitions-Block (gleiche Datei!)
|
|
||||||
if (localNamespaces.has(ns)) {
|
if (localNamespaces.has(ns)) {
|
||||||
let defMatch;
|
let defMatch;
|
||||||
while ((defMatch = extractDefineRegex.exec(text)) !== null) {
|
while ((defMatch = extractDefineRegex.exec(text)) !== null) {
|
||||||
const methodName = defMatch[1];
|
const methodName = defMatch[1];
|
||||||
const item = new vscode.CompletionItem(methodName, vscode.CompletionItemKind.Method);
|
const item = new vscode.CompletionItem(methodName, vscode.CompletionItemKind.Method);
|
||||||
const docStr = this.extractDocString(text, defMatch.index);
|
const docStr = this.extractDocString(text, defMatch.index);
|
||||||
if (docStr) {
|
if (docStr) item.documentation = new vscode.MarkdownString(docStr);
|
||||||
item.documentation = new vscode.MarkdownString(docStr);
|
|
||||||
}
|
|
||||||
moduleMethods.push(item);
|
moduleMethods.push(item);
|
||||||
}
|
}
|
||||||
return moduleMethods;
|
return moduleMethods;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall B: Importiertes Modul (externe Datei)
|
|
||||||
if (importMap.has(ns)) {
|
if (importMap.has(ns)) {
|
||||||
const modPath = importMap.get(ns)!;
|
const modPath = importMap.get(ns)!;
|
||||||
const uris = await vscode.workspace.findFiles('**/*.tpl', '**/node_modules/**', 50);
|
const uris = await vscode.workspace.findFiles('**/*.tpl', '**/node_modules/**', 50);
|
||||||
|
|
@ -146,28 +131,21 @@ export class TplCompletionProvider implements vscode.CompletionItemProvider {
|
||||||
try {
|
try {
|
||||||
const docBytes = await vscode.workspace.fs.readFile(uri);
|
const docBytes = await vscode.workspace.fs.readFile(uri);
|
||||||
const docText = Buffer.from(docBytes).toString('utf8');
|
const docText = Buffer.from(docBytes).toString('utf8');
|
||||||
|
|
||||||
const modRegex = new RegExp(`tpl\\s+1\\.\\d+\\s+module\\s+${modPath.replace(/\./g, '\\\\.')}\\s*;`);
|
const modRegex = new RegExp(`tpl\\s+1\\.\\d+\\s+module\\s+${modPath.replace(/\./g, '\\\\.')}\\s*;`);
|
||||||
if (modRegex.test(docText)) {
|
if (modRegex.test(docText)) {
|
||||||
let defMatch;
|
let defMatch;
|
||||||
while ((defMatch = extractDefineRegex.exec(docText)) !== null) {
|
while ((defMatch = extractDefineRegex.exec(docText)) !== null) {
|
||||||
const methodName = defMatch[1];
|
const methodName = defMatch[1];
|
||||||
const item = new vscode.CompletionItem(methodName, vscode.CompletionItemKind.Method);
|
const item = new vscode.CompletionItem(methodName, vscode.CompletionItemKind.Method);
|
||||||
|
|
||||||
const docStr = this.extractDocString(docText, defMatch.index);
|
const docStr = this.extractDocString(docText, defMatch.index);
|
||||||
if (docStr) {
|
if (docStr) item.documentation = new vscode.MarkdownString(docStr);
|
||||||
item.documentation = new vscode.MarkdownString(docStr);
|
|
||||||
}
|
|
||||||
moduleMethods.push(item);
|
moduleMethods.push(item);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
|
if (moduleMethods.length > 0) return moduleMethods;
|
||||||
if (moduleMethods.length > 0) {
|
|
||||||
return moduleMethods;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
|
|
@ -178,32 +156,23 @@ export class TplCompletionProvider implements vscode.CompletionItemProvider {
|
||||||
let docString = "";
|
let docString = "";
|
||||||
let inDocString = false;
|
let inDocString = false;
|
||||||
let quoteType = "";
|
let quoteType = "";
|
||||||
|
|
||||||
for (let j = 1; j < Math.min(15, lines.length); j++) {
|
for (let j = 1; j < Math.min(15, lines.length); j++) {
|
||||||
const line = lines[j].trim();
|
const line = lines[j].trim();
|
||||||
if (line.length === 0) continue;
|
if (line.length === 0) continue;
|
||||||
|
|
||||||
if (!inDocString) {
|
if (!inDocString) {
|
||||||
if (line.startsWith('"""') || line.startsWith("'''")) {
|
if (line.startsWith('"""') || line.startsWith("'''")) {
|
||||||
inDocString = true;
|
inDocString = true;
|
||||||
quoteType = line.substring(0, 3);
|
quoteType = line.substring(0, 3);
|
||||||
if (line.length > 3 && line.endsWith(quoteType)) {
|
if (line.length > 3 && line.endsWith(quoteType)) { docString = line.substring(3, line.length - 3).trim(); break; }
|
||||||
docString = line.substring(3, line.length - 3).trim();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const content = line.substring(3).trim();
|
const content = line.substring(3).trim();
|
||||||
if (content) docString += content + "\n";
|
if (content) docString += content + "\n";
|
||||||
} else {
|
} else { break; }
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (line.endsWith(quoteType)) {
|
if (line.endsWith(quoteType)) {
|
||||||
const content = line.substring(0, line.length - 3).trim();
|
const content = line.substring(0, line.length - 3).trim();
|
||||||
if (content) docString += content;
|
if (content) docString += content;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else { docString += line + "\n"; }
|
||||||
docString += line + "\n";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return docString.trim() || null;
|
return docString.trim() || null;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
import { tplBuiltIns } from './TplBuiltIns';
|
||||||
|
|
||||||
export class TplHoverProvider implements vscode.HoverProvider {
|
export class TplHoverProvider implements vscode.HoverProvider {
|
||||||
async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.Hover | null> {
|
async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.Hover | null> {
|
||||||
|
|
@ -17,32 +18,34 @@ export class TplHoverProvider implements vscode.HoverProvider {
|
||||||
methodOrPatternName = parts[1];
|
methodOrPatternName = parts[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
// FEHLERBEHEBUNG: [ \t] statt \s verwenden!
|
// NEU: BMC Built-Ins abfangen und direkt ausgeben!
|
||||||
// \s frisst Zeilenumbrüche (\n), was dazu geführt hat, dass match.index
|
if (namespace && tplBuiltIns[namespace] && tplBuiltIns[namespace][methodOrPatternName]) {
|
||||||
// auf leere Zeilen oberhalb der Methode gezeigt hat -> Leeres Hover-Fenster.
|
const markdown = new vscode.MarkdownString();
|
||||||
const searchRegex = new RegExp(`^[ \\t]*(?:(?:define[ \\t]+(?:[a-zA-Z0-9_]+[ \\t]+)*${methodOrPatternName}[ \\t]*\\()|(?:(?:pattern|syncmapping|table|enumeration|configuration)[ \\t]+${methodOrPatternName}\\b))`, 'm');
|
markdown.appendCodeblock(`${namespace}.${methodOrPatternName}`, 'tpl');
|
||||||
|
markdown.appendMarkdown(`---\n*${tplBuiltIns[namespace][methodOrPatternName]}*`);
|
||||||
// 1. Suche im aktuellen Dokument (Ohne Namespace)
|
return new vscode.Hover(markdown);
|
||||||
if (!namespace) {
|
} else if (!namespace && tplBuiltIns['built_in_globals'][methodOrPatternName]) {
|
||||||
const match = searchRegex.exec(currentText);
|
const markdown = new vscode.MarkdownString();
|
||||||
if (match) {
|
markdown.appendCodeblock(`${methodOrPatternName}`, 'tpl');
|
||||||
return this.buildHover(currentText, match.index);
|
markdown.appendMarkdown(`---\n*${tplBuiltIns['built_in_globals'][methodOrPatternName]}*`);
|
||||||
}
|
return new vscode.Hover(markdown);
|
||||||
return null; // Bricht sauber ab, ohne das System zu blockieren
|
}
|
||||||
|
|
||||||
|
const searchRegex = new RegExp(`^[ \\t]*(?:(?:define[ \\t]+(?:[a-zA-Z0-9_]+[ \\t]+)*${methodOrPatternName}[ \\t]*\\()|(?:(?:pattern|syncmapping|table|enumeration|configuration)[ \\t]+${methodOrPatternName}\\b))`, 'm');
|
||||||
|
|
||||||
|
if (!namespace) {
|
||||||
|
const match = searchRegex.exec(currentText);
|
||||||
|
if (match) return this.buildHover(currentText, match.index);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Suche mit Namespace (z.B. helpers.method)
|
|
||||||
// A) Ist der Namespace in derselben Datei? (lokaler definitions block)
|
|
||||||
const localDefRegex = new RegExp(`^[ \\t]*definitions[ \\t]+${namespace}\\b`, 'm');
|
const localDefRegex = new RegExp(`^[ \\t]*definitions[ \\t]+${namespace}\\b`, 'm');
|
||||||
if (localDefRegex.test(currentText)) {
|
if (localDefRegex.test(currentText)) {
|
||||||
const match = searchRegex.exec(currentText);
|
const match = searchRegex.exec(currentText);
|
||||||
if (match) {
|
if (match) return this.buildHover(currentText, match.index);
|
||||||
return this.buildHover(currentText, match.index);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// B) Ist der Namespace importiert? (Workspace durchsuchen)
|
|
||||||
const importRegex = new RegExp(`from\\s+([\\w.]+)\\s+import\\s+${namespace}\\b`);
|
const importRegex = new RegExp(`from\\s+([\\w.]+)\\s+import\\s+${namespace}\\b`);
|
||||||
const importMatch = importRegex.exec(currentText);
|
const importMatch = importRegex.exec(currentText);
|
||||||
|
|
||||||
|
|
@ -53,35 +56,25 @@ export class TplHoverProvider implements vscode.HoverProvider {
|
||||||
try {
|
try {
|
||||||
const docBytes = await vscode.workspace.fs.readFile(uri);
|
const docBytes = await vscode.workspace.fs.readFile(uri);
|
||||||
const docText = Buffer.from(docBytes).toString('utf8');
|
const docText = Buffer.from(docBytes).toString('utf8');
|
||||||
|
|
||||||
const modRegex = new RegExp(`tpl\\s+1\\.\\d+\\s+module\\s+${modPath.replace(/\./g, '\\\\.')}\\s*;`);
|
const modRegex = new RegExp(`tpl\\s+1\\.\\d+\\s+module\\s+${modPath.replace(/\./g, '\\\\.')}\\s*;`);
|
||||||
if (modRegex.test(docText)) {
|
if (modRegex.test(docText)) {
|
||||||
const match = searchRegex.exec(docText);
|
const match = searchRegex.exec(docText);
|
||||||
if (match) {
|
if (match) return this.buildHover(docText, match.index);
|
||||||
return this.buildHover(docText, match.index);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildHover(text: string, matchIndex: number): vscode.Hover {
|
private buildHover(text: string, matchIndex: number): vscode.Hover {
|
||||||
const docStr = this.extractDocString(text, matchIndex);
|
const docStr = this.extractDocString(text, matchIndex);
|
||||||
const markdown = new vscode.MarkdownString();
|
const markdown = new vscode.MarkdownString();
|
||||||
|
|
||||||
// Schneidet exakt die Code-Zeile aus (ohne vorausgehende Leerzeilen)
|
|
||||||
const declLine = text.substring(matchIndex).split('\n')[0].trim();
|
const declLine = text.substring(matchIndex).split('\n')[0].trim();
|
||||||
markdown.appendCodeblock(declLine, 'tpl');
|
markdown.appendCodeblock(declLine, 'tpl');
|
||||||
|
if (docStr) markdown.appendMarkdown(`---\n*${docStr}*`);
|
||||||
if (docStr) {
|
else markdown.appendMarkdown(`---\n*(Keine Dokumentation gefunden)*`);
|
||||||
markdown.appendMarkdown(`---\n*${docStr}*`);
|
|
||||||
} else {
|
|
||||||
markdown.appendMarkdown(`---\n*(Keine Dokumentation gefunden)*`);
|
|
||||||
}
|
|
||||||
return new vscode.Hover(markdown);
|
return new vscode.Hover(markdown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,32 +83,23 @@ export class TplHoverProvider implements vscode.HoverProvider {
|
||||||
let docString = "";
|
let docString = "";
|
||||||
let inDocString = false;
|
let inDocString = false;
|
||||||
let quoteType = "";
|
let quoteType = "";
|
||||||
|
|
||||||
for (let j = 1; j < Math.min(15, lines.length); j++) {
|
for (let j = 1; j < Math.min(15, lines.length); j++) {
|
||||||
const line = lines[j].trim();
|
const line = lines[j].trim();
|
||||||
if (line.length === 0) continue;
|
if (line.length === 0) continue;
|
||||||
|
|
||||||
if (!inDocString) {
|
if (!inDocString) {
|
||||||
if (line.startsWith('"""') || line.startsWith("'''")) {
|
if (line.startsWith('"""') || line.startsWith("'''")) {
|
||||||
inDocString = true;
|
inDocString = true;
|
||||||
quoteType = line.substring(0, 3);
|
quoteType = line.substring(0, 3);
|
||||||
if (line.length > 3 && line.endsWith(quoteType)) {
|
if (line.length > 3 && line.endsWith(quoteType)) { docString = line.substring(3, line.length - 3).trim(); break; }
|
||||||
docString = line.substring(3, line.length - 3).trim();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const content = line.substring(3).trim();
|
const content = line.substring(3).trim();
|
||||||
if (content) docString += content + "\n";
|
if (content) docString += content + "\n";
|
||||||
} else {
|
} else { break; }
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (line.endsWith(quoteType)) {
|
if (line.endsWith(quoteType)) {
|
||||||
const content = line.substring(0, line.length - 3).trim();
|
const content = line.substring(0, line.length - 3).trim();
|
||||||
if (content) docString += content;
|
if (content) docString += content;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else { docString += line + "\n"; }
|
||||||
docString += line + "\n";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return docString.trim() || null;
|
return docString.trim() || null;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { TplCodeActionProvider } from './TplCodeActionProvider';
|
||||||
import { TplDocumentFormatter } from './TplDocumentFormatter';
|
import { TplDocumentFormatter } from './TplDocumentFormatter';
|
||||||
import { TplCompletionProvider } from './TplCompletionProvider';
|
import { TplCompletionProvider } from './TplCompletionProvider';
|
||||||
import { TplHoverProvider } from './TplHoverProvider';
|
import { TplHoverProvider } from './TplHoverProvider';
|
||||||
|
import { tplBuiltIns } from './TplBuiltIns';
|
||||||
|
|
||||||
const SUPPORTED_TPL_VERSION = 23;
|
const SUPPORTED_TPL_VERSION = 23;
|
||||||
const warnedDocuments = new Set<string>();
|
const warnedDocuments = new Set<string>();
|
||||||
|
|
@ -150,6 +151,21 @@ function validateTplDocument(document: vscode.TextDocument, collection: vscode.D
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Linter-Check für falsche BMC Built-In Methoden
|
||||||
|
const callMatch = rawLine.match(/\b(text|regex|discovery|log|time|json|xpath|hash|math|net)\.([a-zA-Z0-9_]+)\b/g);
|
||||||
|
if (callMatch && !inMultiLineComment && !isInternalStringLine) {
|
||||||
|
for (const call of callMatch) {
|
||||||
|
const parts = call.split('.');
|
||||||
|
const ns = parts[0];
|
||||||
|
const method = parts[1];
|
||||||
|
|
||||||
|
if (tplBuiltIns[ns] && !tplBuiltIns[ns][method]) {
|
||||||
|
const idx = rawLine.indexOf(call);
|
||||||
|
addDiag(i, idx, idx + call.length, `Unbekannte Methode '${method}' im Namespace '${ns}'.`, vscode.DiagnosticSeverity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasModuleDef) {
|
if (!hasModuleDef) {
|
||||||
if (line.startsWith('tpl ')) { hasModuleDef = true; }
|
if (line.startsWith('tpl ')) { hasModuleDef = true; }
|
||||||
else { addDiag(i, 0, rawLine.length, "Jede Datei muss mit 'tpl 1.x module <name>;' beginnen.", vscode.DiagnosticSeverity.Error); hasModuleDef = true; }
|
else { addDiag(i, 0, rawLine.length, "Jede Datei muss mit 'tpl 1.x module <name>;' beginnen.", vscode.DiagnosticSeverity.Error); hasModuleDef = true; }
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
"mocha"
|
"mocha"
|
||||||
],
|
],
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
|
"outDir": "out",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"strict": true, /* enable all strict type-checking options */
|
"strict": true, /* enable all strict type-checking options */
|
||||||
/* Additional Checks */
|
/* Additional Checks */
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue