From 684d760f4b2cdc1df62250c356f840a8d1cc3bad Mon Sep 17 00:00:00 2001 From: kuro Date: Thu, 8 Oct 2026 22:02:08 +0200 Subject: [PATCH] =?UTF-8?q?Komplettes=20update=20f=C3=BCr=20die=20funktion?= =?UTF-8?q?en=20und=20beschreibungen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 113 +- build_release.sh | 1 + package.json | 26 +- src/TplCodeActionProvider.ts | 84 +- src/TplCompletionProvider.ts | 211 ++++ src/TplDocumentFormatter.ts | 91 +- src/TplHoverProvider.ts | 123 ++ src/extension.ts | 347 +++--- syntaxes/tpl.tmLanguage.json | 2166 ++++++++++++++++++++++++++++++++++ vsc-extension-quickstart.md | 48 - 10 files changed, 2888 insertions(+), 322 deletions(-) create mode 100755 build_release.sh create mode 100644 src/TplCompletionProvider.ts create mode 100644 src/TplHoverProvider.ts create mode 100644 syntaxes/tpl.tmLanguage.json delete mode 100644 vsc-extension-quickstart.md diff --git a/README.md b/README.md index 6c62d6d..ee895e1 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,68 @@ -# bmc-tpl-code-all-around README +# BMC TPL Code All-Around -This is the README for your extension "bmc-tpl-code-all-around". After writing up a brief description, we recommend including the following sections. +**Linter, Auto-Formatter, IntelliSense und Hover-Dokumentationen für die BMC TPL (Template Language).**[cite: 3] -## Features - -Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file. - -For example if there is an image subfolder under your extension project workspace: - -\!\[feature X\]\(images/feature-x.png\) - -> Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow. - -## Requirements - -If you have any requirements or dependencies, add a section describing those and how to install and configure them. - -## Extension Settings - -Include if your extension adds any VS Code settings through the `contributes.configuration` extension point. - -For example: - -This extension contributes the following settings: - -* `myExtension.enable`: Enable/disable this extension. -* `myExtension.thing`: Set to `blah` to do something. - -## Known Issues - -Calling out known issues can help limit users opening duplicate issues against your extension. - -## Release Notes - -Users appreciate release notes as you update your extension. - -### 1.0.0 - -Initial release of ... - -### 1.0.1 - -Fixed issue #. - -### 1.1.0 - -Added features X, Y, and Z. +Diese Visual Studio Code Erweiterung verwandelt deinen Editor in eine vollwertige Entwicklungsumgebung für BMC Discovery TPL-Skripte. Sie nimmt dir Tipparbeit ab, formatiert Code nach strengen Best Practices, verhindert Syntax-Fehler noch vor dem Deployment und versteht projektübergreifende Abhängigkeiten. --- -## Following extension guidelines +## ✨ Features -Ensure that you've read through the extensions guidelines and follow the best practices for creating your extension. +### 🤖 Intelligenter Linter (Syntax-Scanner) +Der Linter analysiert den Code in Echtzeit und warnt vor typischen TPL-Fehlern: +* **Strikte Hierarchie-Prüfung:** Überwacht die korrekte Reihenfolge der Blöcke innerhalb von Patterns und Syncmappings (`metadata` -> `overview` -> `constants` -> `triggers` -> `mapping`/`removal` -> `body`). +* **Semicolon- & Zuweisungs-Check:** Findet fehlende Semikolons (ignoriert sie aber intelligent in `enumeration`-Blöcken) und warnt, wenn `=` anstelle von `:=` für Zuweisungen genutzt wird. +* **Block- & Klammer-Tracking:** Erkennt nicht geschlossene Blöcke (wie fehlende `end body;` oder `end pattern;`) und findet ungeschlossene runde `(` oder eckige `[` Klammern. +* **Pflichtfeld-Validierung:** Prüft, ob auf Modul-Ebene zwingend benötigte Metadaten (`origin`, `author`, `products`, `publishers`, `tree_path`) vorhanden sind und ob im `body` ein initiales `log.debug` Statement steht. +* **Toleranz-Modus:** Erkennt automatisch TPL-Versionen (z.B. > 1.23) und stuft in neueren Versionen Fehler zu reinen Warnungen herab, um zukünftige Kompatibilität zu gewährleisten. -* [Extension Guidelines](https://code.visualstudio.com/api/references/extension-guidelines) +### 🎨 Auto-Formatter & Visual Alignment (`Shift + Alt + F`) +Schluss mit kaputten Einrückungen! Der Formatter bringt deinen Code in Bestform: +* **VS Code Standard:** Erzwingt strikt 4 Leerzeichen pro Einrückungs-Ebene. +* **Dynamische Block-Einrückung:** Erkennt automatisch startende und endende Blöcke (`if`, `for`, `pattern`, `table` etc.) und passt die Einrückungsebene an. +* **Klammer-Gedächtnis (Visual Alignment):** Die Königsdisziplin! Der Formatter merkt sich die spaltengenaue Position von geöffneten Arrays `[` oder Funktionsaufrufen `(` und richtet alle Folgezeilen makellos an dieser Klammer aus. Schließende Klammern werden auf Wunsch perfekt darunter platziert. -## Working with Markdown +### 💡 IntelliSense & Autovervollständigung (Cross-File) +Spart extrem viel Zeit beim Coden durch Kontext-Wissen: +* **Workspace-Scanning:** Importierst du ein Modul (z.B. `from company.helpers import my_helper`), durchsucht das Plugin im Hintergrund dein gesamtes Projekt. Tippst du `my_helper.`, werden dir automatisch alle verfügbaren `define`-Methoden aus der fremden Datei vorgeschlagen! +* **Lokale Namespaces:** Erkennt `definitions`-Blöcke in der gleichen Datei und schlägt deren Methoden vor. +* **Variablen-Tracking:** Lernt alle Variablen, die du mit `:=` definiert hast, und schlägt sie dir später vor. +* **Log-Filter:** Nach dem Tippen von `log.` zeigt VS Code strikt nur noch die validen Methoden (`debug`, `info`, `warn`, `error`) an. -You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts: +### 🔍 Hover-Dokumentation (Tooltips) +Wenn du mit der Maus über einem Methodennamen, einem Pattern oder einer Tabelle stehen bleibst: +* **DocString Extraction:** Das Plugin liest Multi-Line-Kommentare (`"""` oder `'''`), die direkt unter einer `define` oder `pattern` Deklaration stehen, aus und rendert sie als saubere Markdown-Tooltips. +* **Projektübergreifend:** Funktioniert auch für Methoden aus importierten Dateien! Du musst die aktuelle Datei nicht mehr verlassen, um zu sehen, was eine Hilfsfunktion macht. -* Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux). -* Toggle preview (`Shift+Cmd+V` on macOS or `Shift+Ctrl+V` on Windows and Linux). -* Press `Ctrl+Space` (Windows, Linux, macOS) to see a list of Markdown snippets. +### ⚡ Quick Fixes (Code Actions / Glühbirne) +Bietet sofortige Reparaturoptionen mit einem Klick: +* **"Fehlende Modul-Metadaten einfügen":** Generiert sofort alle 5 Pflichtfelder im Modul-Metadata-Block. +* **"Log-Statement einfügen":** Baut das fehlende `log.debug("Start pattern...");` direkt am Anfang des `body`-Blocks ein. +* **"Tabs zu Spaces":** Ersetzt sofort alle illegalen Tabs (`\t`) in einer Zeile durch jeweils 4 korrekte Leerzeichen. -## For more information +--- -* [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown) -* [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/) +## 🚀 Snippets (Code-Vorlagen) -**Enjoy!** +Einfach das Keyword tippen und `Enter` drücken, um komplette, perfekt eingerückte Blöcke zu generieren: + +| Keyword | Beschreibung | +| :--- | :--- | +| `tpl_skeleton` | **God-Snippet**: Generiert eine komplett frische `.tpl` Datei inklusive Modul-Definition, Pflicht-Metadaten, Pattern, Triggern und Log-Statements. | +| `pattern` | Baut einen leeren Pattern-Block inklusive DocString und `end pattern;`. | +| `syncmapping` | Baut einen leeren Syncmapping-Block inklusive DocString. | +| `define` | Generiert eine Methodendefinition (`define name(params) ... end define;`). | +| `metadata`, `overview`, `body`... | Baut den jeweiligen Block auf und platziert den Cursor in der Mitte. | +| `table` | Erstellt eine Tabelle inklusive des `default -> "unknown";` Fallbacks. | +| `if`, `for` | Baut Standard-Schleifen und Abfragen. | + +--- + +## 🛠️ Installation & Nutzung + +1. Das Plugin aktivieren. +2. TPL-Dateien (Endung `.tpl`) öffnen. +3. Der Linter startet automatisch bei jedem Tastendruck. +4. **Formatieren:** Rechtsklick -> *Dokument formatieren* (oder `Shift + Alt + F`). +5. **Autovervollständigung:** Triggert automatisch beim Tippen, speziell nach einem Punkt (`.`) bei Modul-Aufrufen. +6. **Quick Fixes:** Auf die gelbe/rote Glühbirne am Zeilenanfang klicken oder `Ctrl + .` (Windows/Linux) / `Cmd + .` (Mac) drücken. \ No newline at end of file diff --git a/build_release.sh b/build_release.sh new file mode 100755 index 0000000..abad3ed --- /dev/null +++ b/build_release.sh @@ -0,0 +1 @@ +vsce package \ No newline at end of file diff --git a/package.json b/package.json index 5c6f717..b0ec2f9 100644 --- a/package.json +++ b/package.json @@ -3,19 +3,31 @@ "displayName": "BMC TPL Code All around", "description": "Code linter, tracker and all around tool for coding in BMC Helix TPL", "version": "0.0.1", + "publisher": "kuro", "engines": { "vscode": "^1.140.0" }, "categories": [ "Other" ], - "activationEvents": [], + "activationEvents": [ + "onLanguage:tpl" + ], "main": "./dist/extension.js", "contributes": { - "commands": [ + "languages": [ { - "command": "bmc-tpl-code-all-around.helloWorld", - "title": "Hello World" + "id": "tpl", + "aliases": ["TPL", "tpl"], + "extensions": [".tpl"], + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "tpl", + "scopeName": "source.tpl", + "path": "./syntaxes/tpl.tmLanguage.json" } ] }, @@ -44,9 +56,5 @@ "typescript": "^6.0.3", "@vscode/test-cli": "^0.0.15", "@vscode/test-electron": "^3.0.0" - }, - "overrides": { - "diff": "^8.0.4", - "serialize-javascript": "^7.0.6" } -} +} \ No newline at end of file diff --git a/src/TplCodeActionProvider.ts b/src/TplCodeActionProvider.ts index 9362615..63f1046 100644 --- a/src/TplCodeActionProvider.ts +++ b/src/TplCodeActionProvider.ts @@ -2,60 +2,66 @@ import * as vscode from 'vscode'; export class TplCodeActionProvider implements vscode.CodeActionProvider { - provideCodeActions(document: vscode.TextDocument, range: vscode.Range | vscode.Selection, context: vscode.CodeActionContext, token: vscode.CancellationToken): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> { - const actions: vscode.CodeAction[] = []; + public provideCodeActions( + document: vscode.TextDocument, + range: vscode.Range | vscode.Selection, + context: vscode.CodeActionContext, + token: vscode.CancellationToken + ): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> { + + const actionList: vscode.CodeAction[] = []; - for (const diagnostic of context.diagnostics) { + for (const diag of context.diagnostics) { - // Fix für fehlende Metadaten - if (diagnostic.code === 'missing_metadata') { - const fix = new vscode.CodeAction('Fehlende Metadaten einfügen', vscode.CodeActionKind.QuickFix); - fix.edit = new vscode.WorkspaceEdit(); + // 1. Fix für fehlende Modul-Metadaten (Auf Modul-Ebene, 4 Spaces) + if (diag.code === 'missing_module_metadata') { + const action = new vscode.CodeAction('Fehlende Modul-Metadaten einfügen', vscode.CodeActionKind.QuickFix); + action.edit = new vscode.WorkspaceEdit(); - const missingParts = diagnostic.message.replace('Fehlende Pflicht-Metadaten: ', '').split(', '); - let insertText = ''; + const fehlendeFelder = diag.message.replace('Fehlende Pflicht-Metadaten (Modul-Ebene): ', '').split(', '); + let neuerText = ''; - // Wir nutzen hier auch konsequent die 3 Spaces für den Insert! - if (missingParts.includes('author')) { insertText += ` author := 'Dein Name';\n`; } - if (missingParts.includes('publisher')) { insertText += ` publisher := 'Dein Unternehmen';\n`; } - if (missingParts.includes('origin')) { insertText += ` origin := 'Custom';\n`; } - if (missingParts.includes('tree_path')) { insertText += ` tree_path := 'Eigener Pfad', 'Kategorie', 'Pattern';\n`; } + if (fehlendeFelder.includes('origin')) { neuerText += ` origin := 'Origin';\n`; } + if (fehlendeFelder.includes('author')) { neuerText += ` author := 'Dein Name';\n`; } + if (fehlendeFelder.includes('products')) { neuerText += ` products := 'Produktname';\n`; } + if (fehlendeFelder.includes('publishers')) { neuerText += ` publishers := 'Dein Unternehmen';\n`; } + if (fehlendeFelder.includes('tree_path')) { neuerText += ` tree_path := 'Company', 'Kategorie', 'Sub';\n`; } - fix.edit.insert(document.uri, new vscode.Position(diagnostic.range.start.line + 1, 0), insertText); - fix.diagnostics = [diagnostic]; - fix.isPreferred = true; - actions.push(fix); + action.edit.insert(document.uri, new vscode.Position(diag.range.start.line + 1, 0), neuerText); + action.diagnostics = [diag]; + action.isPreferred = true; + actionList.push(action); } - // Fix für fehlenden Pattern-Start-Log - if (diagnostic.code === 'missing_log_start') { - const fix = new vscode.CodeAction('Log-Statement am Body-Anfang einfügen', vscode.CodeActionKind.QuickFix); - fix.edit = new vscode.WorkspaceEdit(); + // 2. Fix für fehlendes Log-Statement im Body (8 Spaces Einrückung) + if (diag.code === 'missing_log_start') { + const action = new vscode.CodeAction('Log-Statement am Body-Anfang einfügen', vscode.CodeActionKind.QuickFix); + action.edit = new vscode.WorkspaceEdit(); - const expectedLog = diagnostic.source as string; - const insertText = ` ${expectedLog}\n`; // Auch hier 3 Spaces + const erwartetesLog = diag.source as string; + const neuerText = ` ${erwartetesLog}\n`; - fix.edit.insert(document.uri, new vscode.Position(diagnostic.range.start.line, 0), insertText); - fix.diagnostics = [diagnostic]; - fix.isPreferred = true; - actions.push(fix); + action.edit.insert(document.uri, new vscode.Position(diag.range.start.line + 1, 0), neuerText); + action.diagnostics = [diag]; + action.isPreferred = true; + actionList.push(action); } - // Fix für Tabs (Ersetzen durch 3 Spaces) - if (diagnostic.code === 'invalid_tab') { - const fix = new vscode.CodeAction('Tabs in dieser Zeile durch 3 Spaces ersetzen', vscode.CodeActionKind.QuickFix); - fix.edit = new vscode.WorkspaceEdit(); + // 3. Fix für Tabs (Ersetzt \t durch 4 Spaces in der ganzen Zeile) + if (diag.code === 'invalid_tab') { + const action = new vscode.CodeAction('Tabs in dieser Zeile durch 4 Spaces ersetzen', vscode.CodeActionKind.QuickFix); + action.edit = new vscode.WorkspaceEdit(); - const line = document.lineAt(diagnostic.range.start.line); - const fixedText = line.text.replace(/\t/g, ' '); + const zeile = document.lineAt(diag.range.start.line); + const bereinigterText = zeile.text.replace(/\t/g, ' '); - fix.edit.replace(document.uri, line.range, fixedText); - fix.diagnostics = [diagnostic]; - fix.isPreferred = true; - actions.push(fix); + action.edit.replace(document.uri, zeile.range, bereinigterText); + action.diagnostics = [diag]; + action.isPreferred = true; + actionList.push(action); } } - return actions; + return actionList; } } \ No newline at end of file diff --git a/src/TplCompletionProvider.ts b/src/TplCompletionProvider.ts new file mode 100644 index 0000000..8775a00 --- /dev/null +++ b/src/TplCompletionProvider.ts @@ -0,0 +1,211 @@ +import * as vscode from 'vscode'; + +export class TplCompletionProvider implements vscode.CompletionItemProvider { + async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { + const linePrefix = document.lineAt(position).text.substring(0, position.character); + const items: vscode.CompletionItem[] = []; + 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(); + const importRegex = /from\s+([\w.]+)\s+import\s+(\w+)/g; + let m; + while ((m = importRegex.exec(text)) !== null) { + importMap.set(m[2], m[1]); + } + + const localNamespaces = new Set(); + const defBlockRegex = /^\s*definitions\s+([a-zA-Z0-9_]+)/gm; + while ((m = defBlockRegex.exec(text)) !== null) { + localNamespaces.add(m[1]); + } + + // 3. Wenn wir auf freier Fläche tippen (kein Punkt davor) + const matchDot = linePrefix.match(/([a-zA-Z0-9_]+)\.$/); + + if (!matchDot) { + // A) Namespaces (Importiert und Lokal) vorschlagen + importMap.forEach((_, key) => { + const item = new vscode.CompletionItem(key, vscode.CompletionItemKind.Module); + item.detail = "Importiertes Modul"; + items.push(item); + }); + localNamespaces.forEach(ns => { + const item = new vscode.CompletionItem(ns, vscode.CompletionItemKind.Module); + item.detail = "Lokales Modul (definitions)"; + items.push(item); + }); + + // B) Lokale Variablen vorschlagen (z.B. attribute_list :=) + const varRegex = /\b([a-zA-Z0-9_]+)\s*:=/g; + const variables = new Set(); + while ((m = varRegex.exec(text)) !== null) { + variables.add(m[1]); + } + variables.forEach(v => { + items.push(new vscode.CompletionItem(v, vscode.CompletionItemKind.Variable)); + }); + + // C) Das große Skeleton Snippet + const skeletonItem = new vscode.CompletionItem('tpl_skeleton', vscode.CompletionItemKind.Snippet); + skeletonItem.insertText = new vscode.SnippetString( + 'tpl 1.23 module ${1:module_name};\n\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}\';\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"; + items.push(skeletonItem); + + // D) Block-Snippets + const blocks = ['metadata', 'overview', 'constants', 'triggers', 'body', 'mapping', 'removal', 'definitions', 'enumeration', 'configuration']; + for (const block of blocks) { + const item = new vscode.CompletionItem(block, vscode.CompletionItemKind.Snippet); + item.insertText = new vscode.SnippetString(`${block}\n $0\nend ${block};`); + item.detail = `Fügt ${block} Block mit 'end' ein`; + items.push(item); + } + + const defineItem = new vscode.CompletionItem('define', vscode.CompletionItemKind.Snippet); + defineItem.insertText = new vscode.SnippetString('define ${1:name}(${2:params})\n $0\nend define;'); + items.push(defineItem); + + const patternItem = new vscode.CompletionItem('pattern', vscode.CompletionItemKind.Snippet); + patternItem.insertText = new vscode.SnippetString('pattern ${1:name}${2:1.0}\n """\n TODO: explain\n """\n\n $0\nend pattern;'); + items.push(patternItem); + + const syncmappingItem = new vscode.CompletionItem('syncmapping', vscode.CompletionItemKind.Snippet); + syncmappingItem.insertText = new vscode.SnippetString('syncmapping ${1:name}${2:1.0}\n """\n TODO: explain\n """\n\n $0\nend syncmapping;'); + items.push(syncmappingItem); + + const tableItem = new vscode.CompletionItem('table', vscode.CompletionItemKind.Snippet); + tableItem.insertText = new vscode.SnippetString('table ${1:name}${2:1.0}\n $0\n default -> "unknown";\nend table;'); + 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; + } else { + // 4. Wir haben nach einem Punkt getippt (z.B. helpers.) + const ns = matchDot[1]; + const moduleMethods: vscode.CompletionItem[] = []; + + // Regex sucht nach dem Wort direkt vor der Klammer: define [add] method_name( + const extractDefineRegex = /^\s*define\s+.*?\b([a-zA-Z0-9_]+)\s*\(/gm; + + // Fall A: Lokaler Definitions-Block (gleiche Datei!) + if (localNamespaces.has(ns)) { + let defMatch; + while ((defMatch = extractDefineRegex.exec(text)) !== null) { + const methodName = defMatch[1]; + const item = new vscode.CompletionItem(methodName, vscode.CompletionItemKind.Method); + const docStr = this.extractDocString(text, defMatch.index); + if (docStr) { + item.documentation = new vscode.MarkdownString(docStr); + } + moduleMethods.push(item); + } + return moduleMethods; + } + + // Fall B: Importiertes Modul (externe Datei) + if (importMap.has(ns)) { + const modPath = importMap.get(ns)!; + const uris = await vscode.workspace.findFiles('**/*.tpl', '**/node_modules/**', 50); + for (const uri of uris) { + try { + const docBytes = await vscode.workspace.fs.readFile(uri); + const docText = Buffer.from(docBytes).toString('utf8'); + + const modRegex = new RegExp(`tpl\\s+1\\.\\d+\\s+module\\s+${modPath.replace(/\./g, '\\\\.')}\\s*;`); + if (modRegex.test(docText)) { + let defMatch; + while ((defMatch = extractDefineRegex.exec(docText)) !== null) { + const methodName = defMatch[1]; + const item = new vscode.CompletionItem(methodName, vscode.CompletionItemKind.Method); + + const docStr = this.extractDocString(docText, defMatch.index); + if (docStr) { + item.documentation = new vscode.MarkdownString(docStr); + } + moduleMethods.push(item); + } + break; + } + } catch(e) {} + } + + if (moduleMethods.length > 0) { + return moduleMethods; + } + } + } + return items; + } + + private extractDocString(text: string, startIndex: number): string | null { + const lines = text.substring(startIndex).split('\n'); + let docString = ""; + let inDocString = false; + let quoteType = ""; + + for (let j = 1; j < Math.min(15, lines.length); j++) { + const line = lines[j].trim(); + if (line.length === 0) continue; + + if (!inDocString) { + if (line.startsWith('"""') || line.startsWith("'''")) { + inDocString = true; + quoteType = line.substring(0, 3); + if (line.length > 3 && line.endsWith(quoteType)) { + docString = line.substring(3, line.length - 3).trim(); + break; + } + const content = line.substring(3).trim(); + if (content) docString += content + "\n"; + } else { + break; + } + } else { + if (line.endsWith(quoteType)) { + const content = line.substring(0, line.length - 3).trim(); + if (content) docString += content; + break; + } else { + docString += line + "\n"; + } + } + } + return docString.trim() || null; + } +} \ No newline at end of file diff --git a/src/TplDocumentFormatter.ts b/src/TplDocumentFormatter.ts index a79b6b9..cdface0 100644 --- a/src/TplDocumentFormatter.ts +++ b/src/TplDocumentFormatter.ts @@ -2,39 +2,98 @@ import * as vscode from 'vscode'; export class TplDocumentFormatter implements vscode.DocumentFormattingEditProvider { provideDocumentFormattingEdits(document: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken): vscode.ProviderResult { + const text = document.getText(); + const isCRLF = text.includes('\r\n'); + const lines = text.split(/\r?\n/); + + const formattedLines: string[] = []; const edits: vscode.TextEdit[] = []; - const lines = document.getText().split('\n'); let indentLevel = 0; - // Zwingt den Formatter strikt zu 3 Spaces (ignoriert VS Code Settings) - const indentString = ' '; + const indentString = ' '; + let inTripleDouble = false; + let inTripleSingle = false; + let consecutiveEmptyLines = 0; + + const alignmentStack: number[] = []; for (let i = 0; i < lines.length; i++) { - let line = lines[i].trim(); - if (line.length === 0) { continue; } + const rawLine = lines[i]; + let line = rawLine.trim(); - // 1. Wenn die Zeile einen Block schließt, VORHER die Einrückung verringern - if (line.match(/^(end\s+(pattern|table|if|for|while)|else|elif)\b/)) { + const tripleDoubleCount = (rawLine.match(/"""/g) || []).length; + const tripleSingleCount = (rawLine.match(/'''/g) || []).length; + + let isInternalStringLine = false; + + if (inTripleDouble) { + isInternalStringLine = true; + if (tripleDoubleCount % 2 !== 0) { inTripleDouble = false; isInternalStringLine = false; } + } else if (tripleDoubleCount % 2 !== 0) { inTripleDouble = true; } + + if (inTripleSingle) { + isInternalStringLine = true; + if (tripleSingleCount % 2 !== 0) { inTripleSingle = false; isInternalStringLine = false; } + } else if (tripleSingleCount % 2 !== 0) { inTripleSingle = true; } + + if (isInternalStringLine && (inTripleDouble || inTripleSingle)) { + formattedLines.push(rawLine); + consecutiveEmptyLines = 0; + continue; + } + + if (line.length === 0) { + consecutiveEmptyLines++; + if (consecutiveEmptyLines <= 1) { formattedLines.push(""); } + continue; + } + + consecutiveEmptyLines = 0; + + if (line.match(/^(end\s+(pattern|syncmapping|table|enumeration|configuration|definitions?|define|if|for|while|metadata|overview|constants|triggers|mapping|removal|body)|else|elif)\b/)) { indentLevel = Math.max(0, indentLevel - 1); } - // 2. Die Zeile neu aufbauen (korrekte Einrückung + getrimmter Text) - const newLineText = indentString.repeat(indentLevel) + line; - - // 3. Nur wenn sich was geändert hat, ein Edit erzeugen - if (lines[i] !== newLineText && lines[i] !== newLineText + '\r') { - const range = new vscode.Range(i, 0, i, lines[i].length); - edits.push(vscode.TextEdit.replace(range, newLineText)); + let newLineText = ""; + if (alignmentStack.length > 0) { + let spaces = alignmentStack[alignmentStack.length - 1]; + if (line.match(/^[)\]]/)) { spaces = Math.max(0, spaces - 1); } + newLineText = " ".repeat(spaces) + line; + } else { + newLineText = indentString.repeat(indentLevel) + line; } - // 4. Wenn die Zeile einen Block öffnet, NACHHER die Einrückung erhöhen - if (line.match(/^(pattern|table|metadata|overview|constants|triggers|body|if|for|while|else|elif)\b/)) { + formattedLines.push(newLineText); + + let cleanLineForParens = newLineText.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, match => " ".repeat(match.length)); + const commentIdx = cleanLineForParens.indexOf('//'); + if (commentIdx !== -1) { + cleanLineForParens = cleanLineForParens.substring(0, commentIdx) + " ".repeat(cleanLineForParens.length - commentIdx); + } + + for (let charIdx = 0; charIdx < cleanLineForParens.length; charIdx++) { + const char = cleanLineForParens[charIdx]; + if (char === '(' || char === '[') { + alignmentStack.push(charIdx + 1); + } else if (char === ')' || char === ']') { + if (alignmentStack.length > 0) { alignmentStack.pop(); } + } + } + + if (line.match(/^(pattern|syncmapping|table|enumeration|configuration|definitions?|define|metadata|overview|constants|triggers|mapping|removal|body|if|for|while|else|elif)\b/)) { if (!line.startsWith('end ')) { indentLevel++; } } } + const newText = formattedLines.join(isCRLF ? '\r\n' : '\n'); + + if (newText !== text) { + const fullRange = new vscode.Range(document.positionAt(0), document.positionAt(text.length)); + edits.push(vscode.TextEdit.replace(fullRange, newText)); + } + return edits; } } \ No newline at end of file diff --git a/src/TplHoverProvider.ts b/src/TplHoverProvider.ts new file mode 100644 index 0000000..4d73312 --- /dev/null +++ b/src/TplHoverProvider.ts @@ -0,0 +1,123 @@ +import * as vscode from 'vscode'; + +export class TplHoverProvider implements vscode.HoverProvider { + async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { + const wordRange = document.getWordRangeAtPosition(position, /[a-zA-Z0-9_.]+/); + if (!wordRange) return null; + + const fullWord = document.getText(wordRange); + const currentText = document.getText(); + + let namespace = ""; + let methodOrPatternName = fullWord; + + if (fullWord.includes('.')) { + const parts = fullWord.split('.'); + namespace = parts[0]; + methodOrPatternName = parts[1]; + } + + // FEHLERBEHEBUNG: [ \t] statt \s verwenden! + // \s frisst Zeilenumbrüche (\n), was dazu geführt hat, dass match.index + // auf leere Zeilen oberhalb der Methode gezeigt hat -> Leeres Hover-Fenster. + const searchRegex = new RegExp(`^[ \\t]*(?:(?:define[ \\t]+(?:[a-zA-Z0-9_]+[ \\t]+)*${methodOrPatternName}[ \\t]*\\()|(?:(?:pattern|syncmapping|table|enumeration|configuration)[ \\t]+${methodOrPatternName}\\b))`, 'm'); + + // 1. Suche im aktuellen Dokument (Ohne Namespace) + if (!namespace) { + const match = searchRegex.exec(currentText); + if (match) { + return this.buildHover(currentText, match.index); + } + return null; // Bricht sauber ab, ohne das System zu blockieren + } + + // 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'); + if (localDefRegex.test(currentText)) { + const match = searchRegex.exec(currentText); + if (match) { + return this.buildHover(currentText, match.index); + } + return null; + } + + // B) Ist der Namespace importiert? (Workspace durchsuchen) + const importRegex = new RegExp(`from\\s+([\\w.]+)\\s+import\\s+${namespace}\\b`); + const importMatch = importRegex.exec(currentText); + + if (importMatch) { + const modPath = importMatch[1]; + const uris = await vscode.workspace.findFiles('**/*.tpl', '**/node_modules/**', 50); + for (const uri of uris) { + try { + const docBytes = await vscode.workspace.fs.readFile(uri); + const docText = Buffer.from(docBytes).toString('utf8'); + + const modRegex = new RegExp(`tpl\\s+1\\.\\d+\\s+module\\s+${modPath.replace(/\./g, '\\\\.')}\\s*;`); + if (modRegex.test(docText)) { + const match = searchRegex.exec(docText); + if (match) { + return this.buildHover(docText, match.index); + } + break; + } + } catch(e) {} + } + } + + return null; + } + + private buildHover(text: string, matchIndex: number): vscode.Hover { + const docStr = this.extractDocString(text, matchIndex); + const markdown = new vscode.MarkdownString(); + + // Schneidet exakt die Code-Zeile aus (ohne vorausgehende Leerzeilen) + const declLine = text.substring(matchIndex).split('\n')[0].trim(); + markdown.appendCodeblock(declLine, 'tpl'); + + if (docStr) { + markdown.appendMarkdown(`---\n*${docStr}*`); + } else { + markdown.appendMarkdown(`---\n*(Keine Dokumentation gefunden)*`); + } + return new vscode.Hover(markdown); + } + + private extractDocString(text: string, startIndex: number): string | null { + const lines = text.substring(startIndex).split('\n'); + let docString = ""; + let inDocString = false; + let quoteType = ""; + + for (let j = 1; j < Math.min(15, lines.length); j++) { + const line = lines[j].trim(); + if (line.length === 0) continue; + + if (!inDocString) { + if (line.startsWith('"""') || line.startsWith("'''")) { + inDocString = true; + quoteType = line.substring(0, 3); + if (line.length > 3 && line.endsWith(quoteType)) { + docString = line.substring(3, line.length - 3).trim(); + break; + } + const content = line.substring(3).trim(); + if (content) docString += content + "\n"; + } else { + break; + } + } else { + if (line.endsWith(quoteType)) { + const content = line.substring(0, line.length - 3).trim(); + if (content) docString += content; + break; + } else { + docString += line + "\n"; + } + } + } + return docString.trim() || null; + } +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index b1f97c1..7937b78 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,41 +1,29 @@ import * as vscode from 'vscode'; import { TplCodeActionProvider } from './TplCodeActionProvider'; import { TplDocumentFormatter } from './TplDocumentFormatter'; +import { TplCompletionProvider } from './TplCompletionProvider'; +import { TplHoverProvider } from './TplHoverProvider'; -// --- Konstanten --- -const SUPPORTED_TPL_VERSION = 23; // TPL 1.23 +const SUPPORTED_TPL_VERSION = 23; const warnedDocuments = new Set(); -// Erlaubte Phasen in einem Pattern (strikte Reihenfolge laut BMC TPL) -enum PatternPhase { - None = 0, - Metadata = 1, - Overview = 2, - Constants = 3, - Triggers = 4, - Body = 5 -} +enum PatternPhase { None = 0, Metadata = 1, Overview = 2, Constants = 3, Triggers = 4, Mapping = 5, Removal = 6, Body = 7 } export function activate(context: vscode.ExtensionContext) { - // 1. Diagnostic Collection (Der Scanner für rote/gelbe Wellenlinien) const diagnosticCollection = vscode.languages.createDiagnosticCollection('tpl-scanner'); context.subscriptions.push(diagnosticCollection); - // 2. Events registrieren (Ausführen beim Öffnen und Ändern) vscode.workspace.onDidOpenTextDocument(doc => validateTplDocument(doc, diagnosticCollection)); vscode.workspace.onDidChangeTextDocument(event => validateTplDocument(event.document, diagnosticCollection)); - // 3. Quick-Fix Provider (Code Actions für Glühbirnen-Menü) registrieren - context.subscriptions.push( - vscode.languages.registerCodeActionsProvider('tpl', new TplCodeActionProvider(), { - providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] - }) - ); + context.subscriptions.push(vscode.languages.registerCodeActionsProvider('tpl', new TplCodeActionProvider(), { providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] })); + context.subscriptions.push(vscode.languages.registerDocumentFormattingEditProvider('tpl', new TplDocumentFormatter())); + context.subscriptions.push(vscode.languages.registerCompletionItemProvider('tpl', new TplCompletionProvider(), '.')); + context.subscriptions.push(vscode.languages.registerHoverProvider('tpl', new TplHoverProvider())); - // 4. Format Document Provider (Auto-Aufräumen mit Shift+Alt+F) - context.subscriptions.push( - vscode.languages.registerDocumentFormattingEditProvider('tpl', new TplDocumentFormatter()) - ); + for (const doc of vscode.workspace.textDocuments) { + validateTplDocument(doc, diagnosticCollection); + } } function validateTplDocument(document: vscode.TextDocument, collection: vscode.DiagnosticCollection) { @@ -49,131 +37,201 @@ function validateTplDocument(document: vscode.TextDocument, collection: vscode.D let toleranceMode = false; let hasModuleDef = false; - // --- State Machine Variablen --- - let inBlock: 'none' | 'pattern' | 'table' | 'definitions' = 'none'; + let inBlock: 'none' | 'pattern' | 'table' | 'definitions' | 'enumeration' | 'configuration' = 'none'; let currentPatternPhase: PatternPhase = PatternPhase.None; let inMultiLineComment = false; + + let inTripleDouble = false; + let inTripleSingle = false; + let stringNeedsSemicolon = false; - // State Tracking für Quick-Fixes + let activePhaseName = ""; + let activePhaseLine = -1; let currentPatternName = ""; - let foundMetadata = { author: false, publisher: false, origin: false, tree_path: false }; - let metadataBlockLine = -1; - let bodyLineCount = 0; - // --- 1. Versions-Check im Header --- + // Modul-Ebene Metadaten Tracking + let inModuleMetadata = false; + let hasModuleMetadata = false; + let moduleMetadataBlockLine = -1; + let foundModuleMetadata = { origin: false, author: false, products: false, publishers: false, tree_path: false }; + + const parenStack: number[] = []; + const bracketStack: number[] = []; + const openDefines: number[] = []; + const versionMatch = text.match(/tpl\s+1\.(\d+)\s+module/); if (versionMatch) { tplVersion = parseInt(versionMatch[1], 10); if (tplVersion > SUPPORTED_TPL_VERSION) { toleranceMode = true; if (!warnedDocuments.has(document.uri.toString())) { - vscode.window.showInformationMessage(`TPL 1.${tplVersion} erkannt. Linter läuft im Toleranz-Modus (Fehler werden zu Warnungen).`); + vscode.window.showInformationMessage(`TPL 1.${tplVersion} erkannt. Linter läuft im Toleranz-Modus.`); warnedDocuments.add(document.uri.toString()); } } } function addDiag(lineIdx: number, start: number, end: number, msg: string, severity: vscode.DiagnosticSeverity): vscode.Diagnostic { - if (toleranceMode && severity === vscode.DiagnosticSeverity.Error) { - severity = vscode.DiagnosticSeverity.Warning; - } + if (toleranceMode && severity === vscode.DiagnosticSeverity.Error) { severity = vscode.DiagnosticSeverity.Warning; } const diag = new vscode.Diagnostic(new vscode.Range(lineIdx, start, lineIdx, end), msg, severity); diagnostics.push(diag); return diag; } - // --- 2. Der Haupt-Scanner (Zeile für Zeile) --- for (let i = 0; i < lines.length; i++) { const rawLine = lines[i]; const line = rawLine.trim(); - // --- Tab-Check (Strikte 3-Spaces-Regel) --- + const tripleDoubleCount = (rawLine.match(/"""/g) || []).length; + const tripleSingleCount = (rawLine.match(/'''/g) || []).length; + + let isInternalStringLine = false; + let justOpenedString = false; + let isClosingString = false; + + if (inTripleDouble) { + isInternalStringLine = true; + if (tripleDoubleCount % 2 !== 0) { inTripleDouble = false; isClosingString = true; } + } else if (tripleDoubleCount % 2 !== 0) { + inTripleDouble = true; justOpenedString = true; + } + + if (inTripleSingle) { + isInternalStringLine = true; + if (tripleSingleCount % 2 !== 0) { inTripleSingle = false; isClosingString = true; } + } else if (tripleSingleCount % 2 !== 0 && !justOpenedString) { + inTripleSingle = true; justOpenedString = true; + } + + if (isInternalStringLine && (inTripleDouble || inTripleSingle)) { continue; } + if (justOpenedString) { stringNeedsSemicolon = !line.startsWith('"""') && !line.startsWith("'''"); } + const tabIndex = rawLine.indexOf('\t'); if (tabIndex !== -1) { - const diag = addDiag( - i, - tabIndex, - rawLine.length, - "Tabs sind hier nicht erlaubt! Bitte nutze exakt 3 Spaces für Einrückungen. (Tipp: Mit 'Alt + Shift' + Mausklick/Pfeiltasten kannst du mehrere Zeilen gleichzeitig markieren und korrigieren).", - vscode.DiagnosticSeverity.Warning - ); + const diag = addDiag(i, tabIndex, tabIndex + 1, "Bitte Tabs durch Spaces ersetzen", vscode.DiagnosticSeverity.Warning); diag.code = 'invalid_tab'; } - // Kommentare überspringen (aber Regex für Semikolon-Check braucht MultiLine-Info) - if (line.startsWith('//')) { continue; } - if (line.startsWith('/*')) { inMultiLineComment = true; continue; } - if (inMultiLineComment) { - if (line.includes('*/')) { inMultiLineComment = false; } - continue; + let cleanLineForParens = line.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, ''); + const commentIdx = cleanLineForParens.indexOf('//'); + if (commentIdx !== -1) { cleanLineForParens = cleanLineForParens.substring(0, commentIdx); } + + for (let charIdx = 0; charIdx < cleanLineForParens.length; charIdx++) { + const char = cleanLineForParens[charIdx]; + if (char === '(') { parenStack.push(i); } + else if (char === ')') { + if (parenStack.length > 0) { parenStack.pop(); } + else { addDiag(i, 0, rawLine.length, "Unerwartete schließende Klammer ')'. Es fehlt eine öffnende Klammer.", vscode.DiagnosticSeverity.Error); } + } + else if (char === '[') { bracketStack.push(i); } + else if (char === ']') { + if (bracketStack.length > 0) { bracketStack.pop(); } + else { addDiag(i, 0, rawLine.length, "Unerwartete schließende eckige Klammer ']'. Es fehlt eine öffnende Klammer.", vscode.DiagnosticSeverity.Error); } + } } + + if (line.startsWith('//') || line.startsWith('/*')) { if (line.startsWith('/*')) inMultiLineComment = true; continue; } + if (inMultiLineComment) { if (line.includes('*/')) inMultiLineComment = false; continue; } if (line.length === 0) { continue; } - // --- Semikolon-Check --- - const noSemicolonNeeded = /^(pattern|table|metadata|overview|constants|triggers|body|end\s+|if\s+|else|elif\s+|for\s+|while\s+|\/\/|\/\*|\*)/; - if (!noSemicolonNeeded.test(line)) { - if (!line.endsWith(';') && !line.endsWith('{') && !line.endsWith(',')) { - addDiag(i, rawLine.length - 1 < 0 ? 0 : rawLine.length - 1, rawLine.length, "Fehlendes Semikolon am Ende der Anweisung.", vscode.DiagnosticSeverity.Error); + if (isClosingString) { + if (stringNeedsSemicolon && !line.endsWith(';')) { + addDiag(i, rawLine.length - 1 < 0 ? 0 : rawLine.length - 1, rawLine.length, "Fehlendes Semikolon am Ende der Zuweisung.", vscode.DiagnosticSeverity.Error); + } + } else { + const noSemicolonNeeded = /^(pattern|syncmapping|table|enumeration|configuration|definitions?|define|metadata|overview|constants|triggers|mapping|removal|body|end\s+|if\s+|else|elif\s+|for\s+|while\s+|\/\/|\/\*|\*)/; + + // Enumerations benötigen keine Semikolons + if (!noSemicolonNeeded.test(line) && !justOpenedString && parenStack.length === 0 && bracketStack.length === 0 && inBlock !== 'enumeration') { + const lineToCheck = line.split('//')[0].trim(); + if (!lineToCheck.endsWith(';') && !lineToCheck.endsWith('{') && !lineToCheck.endsWith(',')) { + addDiag(i, rawLine.length - 1 < 0 ? 0 : rawLine.length - 1, rawLine.length, "Fehlendes Semikolon am Ende der Anweisung.", vscode.DiagnosticSeverity.Error); + } } } - // Modul-Definition prüfen (Muss als Erstes stehen) if (!hasModuleDef) { - if (line.startsWith('tpl ')) { - hasModuleDef = true; - } else { - addDiag(i, 0, rawLine.length, "Jede Datei muss mit 'tpl 1.x module ;' beginnen.", vscode.DiagnosticSeverity.Error); - hasModuleDef = true; + if (line.startsWith('tpl ')) { hasModuleDef = true; } + else { addDiag(i, 0, rawLine.length, "Jede Datei muss mit 'tpl 1.x module ;' beginnen.", vscode.DiagnosticSeverity.Error); hasModuleDef = true; } + } + + // --- Modul-Metadaten Logik --- + if (line.startsWith('metadata') && inBlock === 'none') { + inModuleMetadata = true; + hasModuleMetadata = true; + moduleMetadataBlockLine = i; + continue; + } + + if (inModuleMetadata) { + if (line.match(/^origin\b/)) { foundModuleMetadata.origin = true; } + if (line.match(/^author\b/)) { foundModuleMetadata.author = true; } + if (line.match(/^products\b/)) { foundModuleMetadata.products = true; } + if (line.match(/^publishers\b/)) { foundModuleMetadata.publishers = true; } + if (line.match(/^tree_path\b/)) { foundModuleMetadata.tree_path = true; } + + if (line.startsWith('end metadata')) { + inModuleMetadata = false; + const missing = Object.keys(foundModuleMetadata).filter(k => !(foundModuleMetadata as any)[k]); + if (missing.length > 0 && moduleMetadataBlockLine !== -1) { + const diag = addDiag(moduleMetadataBlockLine, 0, 8, `Fehlende Pflicht-Metadaten (Modul-Ebene): ${missing.join(', ')}`, vscode.DiagnosticSeverity.Error); + diag.code = 'missing_module_metadata'; + } + } + continue; + } + + // Block checks + if (line.match(/^(pattern|syncmapping|table|definitions?|enumeration|configuration)\b/)) { + if (!hasModuleMetadata && inBlock === 'none') { + addDiag(i, 0, line.length, "Ein 'metadata' Block auf Modul-Ebene ist Pflicht, bevor andere Blöcke definiert werden.", vscode.DiagnosticSeverity.Error); } } - // --- Block-Einstiege (Pattern & Table) --- - if (line.startsWith('pattern ')) { - if ((inBlock as string) !== 'none') { addDiag(i, 0, 7, "Ein Pattern kann nicht innerhalb eines anderen Blocks deklariert werden.", vscode.DiagnosticSeverity.Error); } + if (line.startsWith('definitions ') || line.startsWith('definition ')) { + if (inBlock !== 'none') { addDiag(i, 0, line.length, "Ein Definitions-Block muss auf oberster Ebene deklariert werden.", vscode.DiagnosticSeverity.Error); } + inBlock = 'definitions'; continue; + } + + if (line.startsWith('define ')) { openDefines.push(i); continue; } + + if (line.startsWith('enumeration ')) { + if (inBlock !== 'none') { addDiag(i, 0, line.length, "Eine Enumeration muss auf oberster Ebene deklariert werden.", vscode.DiagnosticSeverity.Error); } + inBlock = 'enumeration'; continue; + } + + if (line.startsWith('configuration ')) { + if (inBlock !== 'none') { addDiag(i, 0, line.length, "Eine Konfiguration muss auf oberster Ebene deklariert werden.", vscode.DiagnosticSeverity.Error); } + inBlock = 'configuration'; continue; + } + + if (line.startsWith('pattern ') || line.startsWith('syncmapping ')) { + if (inBlock !== 'none') { addDiag(i, 0, 7, "Ein Pattern/Syncmapping kann nicht innerhalb eines anderen Blocks deklariert werden.", vscode.DiagnosticSeverity.Error); } inBlock = 'pattern'; currentPatternPhase = PatternPhase.None; + activePhaseName = ""; + activePhaseLine = -1; - const nameMatch = line.match(/pattern\s+([a-zA-Z0-9_]+)\s+\d+\.\d+/); - if (nameMatch) { - currentPatternName = nameMatch[1]; - } else { - addDiag(i, 0, rawLine.length, "Ungültige Pattern-Deklaration. Erwartet: 'pattern '", vscode.DiagnosticSeverity.Error); - } - - foundMetadata = { author: false, publisher: false, origin: false, tree_path: false }; - metadataBlockLine = -1; - bodyLineCount = 0; + const nameMatch = line.match(/(pattern|syncmapping)\s+([a-zA-Z0-9_]+)\s+\d+\.\d+/); + if (nameMatch) { currentPatternName = nameMatch[2]; } + else { addDiag(i, 0, rawLine.length, "Ungültige Pattern-Deklaration. Erwartet: 'pattern '", vscode.DiagnosticSeverity.Error); } continue; } if (line.startsWith('table ')) { - if ((inBlock as string) !== 'none') { addDiag(i, 0, 5, "Eine Tabelle kann nicht innerhalb eines anderen Blocks deklariert werden.", vscode.DiagnosticSeverity.Error); } - inBlock = 'table'; - - if (!line.match(/table\s+[a-zA-Z0-9_]+\s+\d+\.\d+/)) { - addDiag(i, 0, rawLine.length, "Ungültige Tabellen-Deklaration. Erwartet: 'table '", vscode.DiagnosticSeverity.Error); - } - continue; + if (inBlock !== 'none') { addDiag(i, 0, 5, "Eine Tabelle kann nicht innerhalb eines anderen Blocks deklariert werden.", vscode.DiagnosticSeverity.Error); } + inBlock = 'table'; continue; } - // --- Innerhalb eines Patterns --- - if ((inBlock as string) === 'pattern') { + if (inBlock === 'pattern') { const checkPhase = (phaseName: string, phaseEnum: PatternPhase) => { if (line.startsWith(phaseName)) { - if ((currentPatternPhase as PatternPhase) >= phaseEnum) { - addDiag(i, 0, phaseName.length, `'${phaseName}' Block ist hier nicht erlaubt. Die Reihenfolge ist: metadata -> overview -> constants -> triggers -> body.`, vscode.DiagnosticSeverity.Error); - } - - if ((currentPatternPhase as PatternPhase) === PatternPhase.Metadata && phaseEnum > PatternPhase.Metadata) { - const missing = Object.keys(foundMetadata).filter(k => !(foundMetadata as any)[k]); - if (missing.length > 0 && metadataBlockLine !== -1) { - const diag = addDiag(metadataBlockLine, 0, 8, `Fehlende Pflicht-Metadaten: ${missing.join(', ')}`, vscode.DiagnosticSeverity.Error); - diag.code = 'missing_metadata'; - } - } + if (activePhaseName !== "") { addDiag(activePhaseLine, 0, activePhaseName.length, `Block '${activePhaseName}' wurde nicht mit 'end ${activePhaseName};' geschlossen.`, vscode.DiagnosticSeverity.Error); } + if (currentPatternPhase >= phaseEnum) { addDiag(i, 0, phaseName.length, `'${phaseName}' Block ist hier nicht erlaubt.`, vscode.DiagnosticSeverity.Error); } currentPatternPhase = phaseEnum; - if (phaseEnum === PatternPhase.Metadata) { metadataBlockLine = i; } + activePhaseName = phaseName; + activePhaseLine = i; } }; @@ -181,81 +239,66 @@ function validateTplDocument(document: vscode.TextDocument, collection: vscode.D checkPhase('overview', PatternPhase.Overview); checkPhase('constants', PatternPhase.Constants); checkPhase('triggers', PatternPhase.Triggers); - checkPhase('body', PatternPhase.Body); + checkPhase('mapping', PatternPhase.Mapping); + checkPhase('removal', PatternPhase.Removal); - if ((currentPatternPhase as PatternPhase) === PatternPhase.Metadata) { - if (line.startsWith('author ')) { foundMetadata.author = true; } - if (line.startsWith('publisher ')) { foundMetadata.publisher = true; } - if (line.startsWith('origin ')) { foundMetadata.origin = true; } - if (line.startsWith('tree_path ')) { foundMetadata.tree_path = true; } + if (line.startsWith('body')) { + if (activePhaseName !== "") { addDiag(activePhaseLine, 0, activePhaseName.length, `Block '${activePhaseName}' wurde nicht mit 'end ${activePhaseName};' geschlossen.`, vscode.DiagnosticSeverity.Error); } + activePhaseName = "body"; + activePhaseLine = i; + + let foundLog = false; + for (let j = i + 1; j < lines.length; j++) { + const aheadLine = lines[j].trim(); + if (aheadLine.startsWith('end pattern') || aheadLine.startsWith('end syncmapping')) { break; } + if (aheadLine.includes('log.debug') && aheadLine.includes(currentPatternName)) { foundLog = true; break; } + } + + if (!foundLog) { + const expectedLog = `log.debug("Start pattern ${currentPatternName}");`; + const diag = addDiag(i, 0, rawLine.length, `Im Body fehlt das Log-Statement mit dem Pattern-Namen. Bsp: ${expectedLog}`, vscode.DiagnosticSeverity.Error); + diag.code = 'missing_log_start'; diag.source = expectedLog; + } } - if ((currentPatternPhase as PatternPhase) === PatternPhase.Body && !line.startsWith('body')) { - bodyLineCount++; - if (bodyLineCount === 1) { - const expectedLog = `log.debug("Pattern start ${currentPatternName}");`; - if (!line.includes(`log.debug("Pattern start ${currentPatternName}`)) { - const diag = addDiag(i, 0, rawLine.length, `Body muss mit dem Log-Statement beginnen: ${expectedLog}`, vscode.DiagnosticSeverity.Error); - diag.code = 'missing_log_start'; - diag.source = expectedLog; - } + const closers = ['end metadata', 'end overview', 'end constants', 'end triggers', 'end mapping', 'end removal', 'end body']; + for (const closer of closers) { + if (line.startsWith(closer)) { + const phase = closer.replace('end ', '').replace(';', '').trim(); + if (activePhaseName !== phase) { addDiag(i, 0, line.length, `Unerwartetes '${closer}'. Erwartet wurde 'end ${activePhaseName};'.`, vscode.DiagnosticSeverity.Error); } + else { activePhaseName = ""; } } } } - // --- Innerhalb einer Tabelle --- - if ((inBlock as string) === 'table') { - if (!line.startsWith('end ') && !line.includes('table ')) { - if (!line.includes('->') && !line.includes(':=')) { - addDiag(i, 0, rawLine.length, "Ungültiger Tabelleneintrag. Nutze '[\"Key\"] -> \"Value\";' oder 'default := \"Value\";'", vscode.DiagnosticSeverity.Error); - } - } - } - - // --- Allgemeine Syntax-Checks --- - const eqMatch = rawLine.match(/(?=!])=(?![=])/); - if (eqMatch && eqMatch.index) { + const eqMatch = rawLine.match(/(?=!:])=(?![=])/); + if (eqMatch && eqMatch.index && !justOpenedString) { if (!line.includes('"') && !line.includes("'")) { addDiag(i, eqMatch.index, eqMatch.index + 1, "Zuweisung in TPL erfolgt mit ':=', nicht mit '='. '=' ist für Vergleiche.", vscode.DiagnosticSeverity.Error); } } - if (line.includes('?') && line.includes(':')) { - if (tplVersion < 15) { - addDiag(i, 0, rawLine.length, `Ternärer Operator (? :) erfordert mindestens TPL 1.15 (Aktuell: 1.${tplVersion}).`, vscode.DiagnosticSeverity.Error); - } - } - - const streamMatch = rawLine.match(/\bstream\b/); - if (streamMatch && streamMatch.index !== undefined) { - if (tplVersion < 20) { - addDiag(i, streamMatch.index, streamMatch.index + 6, `Das 'stream'-Keyword erfordert mindestens TPL 1.20.`, vscode.DiagnosticSeverity.Error); - } - } - - const notInMatch = rawLine.match(/\bnot\s+in\b/); - if (notInMatch && notInMatch.index !== undefined) { - if (tplVersion < 18) { - addDiag(i, notInMatch.index, notInMatch.index + 6, `'not in' erfordert mindestens TPL 1.18.`, vscode.DiagnosticSeverity.Error); - } - } - - // --- Block-Ausstiege --- - if (line.startsWith('end pattern')) { - if ((inBlock as string) !== 'pattern') { - addDiag(i, 0, 11, "'end pattern' gefunden, aber es wurde kein Pattern gestartet.", vscode.DiagnosticSeverity.Error); - } else if ((currentPatternPhase as PatternPhase) < PatternPhase.Body) { - addDiag(i, 0, 11, "Dem Pattern fehlt ein 'body'-Block.", vscode.DiagnosticSeverity.Error); - } + if (line.startsWith('end pattern') || line.startsWith('end syncmapping')) { + if (activePhaseName !== "") { addDiag(activePhaseLine, 0, activePhaseName.length, `Block '${activePhaseName}' wurde nicht mit 'end ${activePhaseName};' geschlossen.`, vscode.DiagnosticSeverity.Error); } + if (inBlock !== 'pattern') { addDiag(i, 0, 11, "'end pattern' gefunden, ohne offenen Pattern-Block.", vscode.DiagnosticSeverity.Error); } inBlock = 'none'; } - else if (line.startsWith('end table')) { - if ((inBlock as string) !== 'table') { - addDiag(i, 0, 9, "'end table' gefunden, aber es wurde keine Tabelle gestartet.", vscode.DiagnosticSeverity.Error); - } + else if (line.startsWith('end table') || line.startsWith('end enumeration') || line.startsWith('end configuration')) { inBlock = 'none'; } + else if (line.startsWith('end definitions') || line.startsWith('end definition')) { + if (inBlock !== 'definitions') { addDiag(i, 0, line.length, "'end definitions' gefunden, ohne offenen Definitions-Block.", vscode.DiagnosticSeverity.Error); } + inBlock = 'none'; + } + else if (line.startsWith('end define')) { + if (openDefines.length > 0) { openDefines.pop(); } + else { addDiag(i, 0, line.length, "'end define' gefunden, ohne offenes 'define'.", vscode.DiagnosticSeverity.Error); } + } } + for (const unclosedLine of parenStack) { addDiag(unclosedLine, 0, lines[unclosedLine].length, "Öffnende Klammer '(' wird nicht geschlossen!", vscode.DiagnosticSeverity.Error); } + for (const unclosedLine of bracketStack) { addDiag(unclosedLine, 0, lines[unclosedLine].length, "Öffnende eckige Klammer '[' wird nicht geschlossen!", vscode.DiagnosticSeverity.Error); } + for (const unclosedLine of openDefines) { addDiag(unclosedLine, 0, lines[unclosedLine].length, "Block 'define' wurde nicht mit 'end define;' geschlossen!", vscode.DiagnosticSeverity.Error); } + collection.set(document.uri, diagnostics); } \ No newline at end of file diff --git a/syntaxes/tpl.tmLanguage.json b/syntaxes/tpl.tmLanguage.json new file mode 100644 index 0000000..20a6b58 --- /dev/null +++ b/syntaxes/tpl.tmLanguage.json @@ -0,0 +1,2166 @@ +{ + "name": "TPL", + "comment": "Based on https://github.com/trianglesis/language-tplpre", + "scopeName": "source.tpl", + "fileTypes": [ + "tpl" + ], + "foldingStartMarker": "^(//|tpl).*$", + "foldingStopMarker": "(^$)", + "patterns": [ + { + "include": "#doc_strings" + }, + { + "include": "#comment_TODO_line" + }, + { + "include": "#comment_line" + }, + { + "include": "#metadata_2" + }, + { + "include": "#overwiev" + }, + { + "include": "#simple_identifiers" + }, + { + "include": "#if_addm" + }, + { + "match": "tpl\\s+(\\$\\$TPLVERSION\\$\\$|\\d+\\.\\d+)\\s+module\\s(.*);", + "captures": { + "1": { + "name": "constant.character.tpl" + }, + "2": { + "name": "constant.character.tpl" + } + } + }, + { + "match": "from\\s(\\w+)(?:.(\\w+))?(?:.(\\w+))?\\simport\\s(\\S+)\\s(\\d+(?:.\\d+)+);", + "captures": { + "1": { + "name": "entity.name.function.tpl" + }, + "2": { + "name": "storage.type.tpl" + }, + "3": { + "name": "variable.parameter.tpl" + }, + "4": { + "name": "entity.name.function.tpl" + }, + "5": { + "name": "constant.numeric.tpl" + } + } + }, + { + "begin": "^(configuration)\\s(\\S+)\\s(\\d+\\.\\d+)", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "entity.name.function.tpl" + }, + "3": { + "name": "constant.numeric.tpl" + } + }, + "end": "^(end\\sconfiguration;)", + "endCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "patterns": [ + { + "include": "#comment_line" + }, + { + "include": "#doc_strings" + }, + { + "include": "#lines" + }, + { + "include": "#if_addm" + } + ] + }, + { + "begin": "^(table)\\s(\\S+)\\s(\\d+(?:\\.\\d+)+)", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "entity.name.function" + }, + "3": { + "name": "constant.numeric.tpl" + }, + "4": { + "name": "comment.block.documentation.tpl" + } + }, + "end": "^(end\\stable;)", + "endCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "patterns": [ + { + "include": "#comment_line" + }, + { + "include": "#main" + } + ] + }, + { + "comment": "Adding SQL support via #lines", + "begin": "^(definitions)\\s(\\S+)\\s(\\d+(?:\\.\\d+)+)", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "entity.name.function" + }, + "3": { + "name": "constant.numeric.tpl" + } + }, + "end": "^(end\\sdefinitions;)", + "endCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "patterns": [ + { + "include": "#define" + }, + { + "include": "#alt_define" + }, + { + "include": "#comment_line" + }, + { + "include": "#doc_strings" + }, + { + "include": "#lines" + }, + { + "include": "#if_addm" + } + ] + }, + { + "begin": "^\\b(pattern|syncmapping)\\s+(\\w+)\\s(\\d+(?:\\.\\d+)+)\\s+$", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "constant.numeric.tpl" + }, + "3": { + "name": "constant.numeric.tpl" + } + }, + "end": "\\b(end\\spattern|syncmapping;)", + "endCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "patterns": [ + { + "include": "#comment_TODO_line" + }, + { + "include": "#comment_line" + }, + { + "include": "#doc_strings" + }, + { + "include": "#metadata" + }, + { + "include": "#overwiev" + }, + { + "include": "#constants" + }, + { + "include": "#mapping" + }, + { + "include": "#triggers" + }, + { + "include": "#removal" + }, + { + "include": "#body" + }, + { + "include": "#if_addm" + }, + { + "include": "#docstrings" + }, + { + "include": "#doc_strings_block_doulbe_q" + } + ] + } + ], + "repository": { + "tags": { + "comment": "Tags", + "match": "(tags)", + "name": "keyword.control.import.tpl" + }, + "tags_TKU": { + "comment": "Tags TKU", + "match": "(TKU_\\d+_\\d+_\\d+)", + "name": "constant.other.tpl" + }, + "metadata": { + "begin": "^\\s*(metadata)\\s+?$", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "end": "^\\s*(end\\smetadata;)\\s+?$", + "endCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "patterns": [ + { + "include": "#comment_line" + }, + { + "include": "#lines" + }, + { + "include": "#if_addm" + } + ] + }, + "metadata_2": { + "begin": "^\\s*(metadata)\\s+?$", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "end": "^(end\\smetadata;)\\s+?$", + "endCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "patterns": [ + { + "include": "#lines" + }, + { + "include": "#comment_line" + }, + { + "include": "#if_addm" + } + ] + }, + "overwiev": { + "begin": "^\\s*(overview)\\s+?$", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "end": "^\\s*(end\\soverview;)\\s+?$", + "endCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "patterns": [ + { + "include": "#comment_line" + }, + { + "include": "#tags_TKU" + }, + { + "include": "#keywords_all" + }, + { + "include": "#if_addm" + } + ] + }, + "constants": { + "begin": "\\b(constants)\\s+?$", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "end": "\\b(end\\sconstants;)\\s+?$", + "endCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "patterns": [ + { + "include": "#lines" + }, + { + "include": "#all_functions" + }, + { + "include": "#if_addm" + } + ] + }, + "mapping": { + "begin": "\\b(mapping)\\s+?$", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "end": "\\b(end\\mapping;)\\s+?$", + "endCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "patterns": [ + { + "include": "#lines" + }, + { + "include": "#all_functions" + }, + { + "include": "#if_addm" + } + ] + }, + "triggers": { + "begin": "^\\s*(triggers)\\s+?$", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "end": "^\\s*(end\\striggers;)\\s+?$", + "endCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "patterns": [ + { + "include": "#lines" + }, + { + "include": "#all_functions" + }, + { + "include": "#all_controls" + }, + { + "include": "#keywords_cmd_indentify_trigger" + }, + { + "include": "#if_addm" + } + ] + }, + "body": { + "begin": "^\\s*(body)$", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "end": "^\\s*(end\\sbody;)$", + "endCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "patterns": [ + { + "include": "#dev_keys" + }, + { + "include": "#doc_strings" + }, + { + "include": "#lines" + }, + { + "include": "#all_functions" + }, + { + "include": "#all_controls" + }, + { + "include": "#if_addm" + } + ] + }, + "removal": { + "begin": "^\\s*(removal)", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "end": "^\\s*(end\\sremoval;)", + "endCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "patterns": [ + { + "include": "#lines" + }, + { + "include": "#all_functions" + }, + { + "include": "#all_controls" + }, + { + "include": "#if_addm" + } + ] + }, + "comment_line": { + "name": "comment.line.tpl", + "match": "(?", + "name": "keyword.operator.tpl" + }, + "more_equal": { + "match": "\\>\\=", + "name": "keyword.operator.tpl" + }, + "less_equal": { + "match": "\\<\\=", + "name": "keyword.operator.tpl" + }, + "decimal": { + "match": "\\s\\-\\d+\\s", + "name": "constant.numeric.integer.tpl" + }, + "minus_decimal": { + "match": "\\s\\-\\d+\\s", + "name": "constant.numeric.integer.tpl" + }, + "just_do": { + "match": "(\\.)", + "name": "punctuation.accessor.tpl", + "captures": { + "1": { + "name": "keyword.operator.tpl" + } + } + }, + "all_keys": { + "patterns": [ + { + "include": "#var_assign" + }, + { + "include": "#equal" + }, + { + "include": "#less" + }, + { + "include": "#more" + }, + { + "include": "#more_equal" + }, + { + "include": "#less_equal" + }, + { + "include": "#plus" + }, + { + "include": "#decimal" + }, + { + "include": "#minus_decimal" + }, + { + "include": "#just_do" + } + ] + }, + "regex_token_s_quote": { + "comment": "Catch regex in single quotes", + "begin": "(regex)\\s+?(?=')(.+?)", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "support.function.tpl" + } + }, + "end": "(')", + "endCaptures": { + "1": { + "name": "support.function.tpl" + } + }, + "patterns": [ + { + "include": "#regular_expressions" + } + ] + }, + "regex_token_d_quote": { + "comment": "Catch regex in double qoutes", + "begin": "(regex)\\s+?(?=\")(.+?)", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "support.function.tpl" + } + }, + "end": "(\")", + "endCaptures": { + "1": { + "name": "support.function.tpl" + } + }, + "patterns": [ + { + "include": "#regular_expressions" + } + ] + }, + "regex_token_triple_s_quote": { + "comment": "Catch regex in double qoutes", + "begin": "(regex)\\s+?('{3})(.+?)", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "support.function.tpl" + } + }, + "end": "('{3})", + "endCaptures": { + "1": { + "name": "support.function.tpl" + } + }, + "patterns": [ + { + "include": "#regular_expressions" + } + ] + }, + "regex_token_triple_d_quote": { + "comment": "Catch regex in double qoutes", + "begin": "(regex)\\s+?(\"{3})(.+?)", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "support.function.tpl" + } + }, + "end": "(\"{3})", + "endCaptures": { + "1": { + "name": "support.function.tpl" + } + }, + "patterns": [ + { + "include": "#regular_expressions" + } + ] + }, + "regexes": { + "patterns": [ + { + "include": "#regex_token_triple_s_quote" + }, + { + "include": "#regex_token_triple_d_quote" + }, + { + "include": "#regex_token_s_quote" + }, + { + "include": "#regex_token_d_quote" + } + ] + }, + "begin_variable_assignment": { + "comment": "Covers beginning assignment of a value to a variable. IN PROGRESS", + "match": "^\\s*(\\w+)\\s*(\\:\\=)", + "captures": { + "1": { + "name": "variable.parameter.tpl" + }, + "2": { + "name": "variable.parameter.tpl" + }, + "3": { + "name": "keyword.operator.assignment.tpl" + } + } + }, + "end_variable_assignment": { + "comment": "Covers ending assignment of a value to a variable. IN PROGRESS", + "match": "(?<=:=)\\s*(\\w+)(;|,)$", + "name": "punctuation.accessor.tpl", + "captures": { + "1": { + "name": "entity.name.type.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + } + } + }, + "temp_test": { + "comment": "TEMP TEST PLEASE DELETE", + "match": "(?<=abc)WHAT", + "name": "variable.parameter.tpl", + "captures": { + "1": { + "name": "entity.name.type.tpl" + } + } + }, + "discovery_action_functions": { + "comment": "TODO: confirm that Discovery functions DEV is working", + "begin": "(discovery)(\\.)(fileGet|fileInfo|getNames|lookupAddresses|listDirectory|listRegistry|registryKey|wmiQuery|wbemQuery|wbemEnumInstances|runCommand|snmpGet|snmpGetTable|vSphereFindObjects|vSphereTraverseToObjects|vSphereGetProperties|vSphereGetPropertyTable|scan|restfulGet|restfulPost|restfulDelete|restfulPatch|restfulPut)", + "beginCaptures": { + "1": { + "name": "support.function.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "variable.parameter.tpl" + } + }, + "end": "\\;", + "endCaptures": { + "1": { + "name": "disabled.tpl" + } + }, + "patterns": [ + { + "include": "#lines" + }, + { + "include": "#all_controls" + }, + { + "include": "#regex" + } + ] + }, + "discovery_cloud_aws_functions": { + "begin": "(discovery)(\\.)(AWS)(\\.)(AutoScaling|CF|EC2|ELB|ELBv2|RDS|S3)(\\.)((Describe|Get|List)\\w+\\b)", + "beginCaptures": { + "1": { + "name": "support.function.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "support.function.tpl" + }, + "4": { + "name": "punctuation.accessor.tpl" + }, + "5": { + "name": "variable.parameter.tpl" + }, + "6": { + "name": "punctuation.accessor.tpl" + }, + "7": { + "name": "variable.parameter.tpl" + } + }, + "end": "\\;", + "endCaptures": { + "1": { + "name": "disabled.tpl" + } + }, + "patterns": [ + { + "include": "#lines" + }, + { + "include": "#all_controls" + }, + { + "include": "#regex" + } + ] + }, + "discovery_cloud_azure_functions": { + "comment": "Due to the number of possible matching groups, the whole match is wrapped for punctuation and each sub-group match is colored.", + "begin": "((discovery)\\.(Azure)\\.((Availability|Virtual|LoadBalancers|Network|Public|RouteTables|Providers|ResourceGroups|Subscriptions|Tenants|SQLServers|Storage|AutoScale|AppService)\\w*)\\.(\\w+)?\\.?(\\w+)?\\.?((List|Get)\\w*\\b))", + "beginCaptures": { + "1": { + "name": "punctuation.accessor.tpl" + }, + "2": { + "name": "support.function.tpl" + }, + "3": { + "name": "support.function.tpl" + }, + "4": { + "name": "variable.parameter.tpl" + }, + "5": { + "name": "variable.parameter.tpl" + }, + "6": { + "name": "variable.parameter.tpl" + }, + "7": { + "name": "variable.parameter.tpl" + }, + "8": { + "name": "variable.parameter.tpl" + } + }, + "end": "\\;", + "endCaptures": { + "1": { + "name": "disabled.tpl" + } + }, + "patterns": [ + { + "include": "#lines" + }, + { + "include": "#all_controls" + }, + { + "include": "#regex" + } + ] + }, + "model_functions": { + "comment": "TODO: confirm that Model functions DEV is working", + "begin": "(model)(\\.)(addContainment|setContainment|destroy|withdraw|setRemovalGroup|anchorRemovalGroup|suppressRemovalGroup|host|hosts|findPackages|addDisplayAttribute|removeDisplayAttribute|kind)", + "beginCaptures": { + "1": { + "name": "entity.name.function.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "constant.character.escape.tpl" + } + }, + "end": "\\;", + "endCaptures": { + "1": { + "name": "disabled.tpl" + } + }, + "patterns": [ + { + "include": "#lines" + }, + { + "include": "#all_controls" + }, + { + "include": "#regex" + } + ] + }, + "model_rel_functions": { + "comment": "TODO: confirm that Model rel functions DEV is working", + "begin": "(model)(\\.)(rel|uniquerel)(\\.)(Communication|Containment|Dependency|Detail|FunctionalContainment|HostContainment|HostedFile|HostedSoftware|Management|RelatedFile|SoftwareService|SoftwareContainment|StorageUse)", + "beginCaptures": { + "1": { + "name": "entity.name.function.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "support.function.tpl" + }, + "4": { + "name": "punctuation.accessor.tpl" + }, + "5": { + "name": "constant.character.escape.tpl" + } + }, + "end": "\\;", + "endCaptures": { + "1": { + "name": "disabled.tpl" + } + }, + "patterns": [ + { + "include": "#lines" + }, + { + "include": "#all_controls" + }, + { + "include": "#regex" + }, + { + "include": "#model_attribute_assignment" + } + ] + }, + "model_node_functions": { + "comment": "TODO: confirm that Model SI functions DEV is working. TODO: add more node types. TODO: Finish branch", + "begin": "(model)(\\.)(BusinessApplicationInstance|Detail|FunctionalComponent|SoftwareInstance)", + "beginCaptures": { + "1": { + "name": "entity.name.function.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "constant.character.escape.tpl" + } + }, + "end": "\\;", + "endCaptures": { + "1": { + "name": "disabled.tpl" + } + }, + "patterns": [ + { + "include": "#lines" + }, + { + "include": "#all_controls" + }, + { + "include": "#regex" + }, + { + "include": "#model_attribute_assignment" + } + ] + }, + "model_attribute_assignment": { + "comment": "Covers assignment of values to attributes within 'model' and 'model.rel' functions", + "match": "^\\s*(([a-zA-Z]\\w*)\\s*(\\:\\=)\\s*(\\w+))", + "name": "keyword.control.conditional.tpl", + "captures": { + "1": { + "name": "punctuation.accessor.tpl" + }, + "2": { + "name": "variable.parameter.tpl" + }, + "3": { + "name": "keyword.operator.assignment.tpl" + }, + "4": { + "name": "entity.name.type.tpl" + } + } + }, + "sync_rel_functions": { + "comment": "confirm all possible namespace functions and arguments", + "begin": "(sync)(\\.)(rel)(\\.)(BMC_Component)", + "beginCaptures": { + "1": { + "name": "entity.name.function.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "support.function.tpl" + }, + "4": { + "name": "punctuation.accessor.tpl" + }, + "5": { + "name": "constant.character.escape.tpl" + } + }, + "end": "\\;", + "endCaptures": { + "1": { + "name": "disabled.tpl" + } + }, + "patterns": [ + { + "include": "#lines" + }, + { + "include": "#all_controls" + }, + { + "include": "#regex" + }, + { + "include": "#model_attribute_assignment" + } + ] + }, + "sync_shared_functions": { + "comment": "confirm all possible namespace functions and arguments", + "begin": "(sync)(\\.)(shared)(\\.)(BMC_BusinessService)", + "beginCaptures": { + "1": { + "name": "entity.name.function.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "support.function.tpl" + }, + "4": { + "name": "punctuation.accessor.tpl" + }, + "5": { + "name": "constant.character.escape.tpl" + } + }, + "end": "\\;", + "endCaptures": { + "1": { + "name": "disabled.tpl" + } + }, + "patterns": [ + { + "include": "#lines" + }, + { + "include": "#all_controls" + }, + { + "include": "#regex" + }, + { + "include": "#model_attribute_assignment" + } + ] + }, + "log_functions": { + "match": "(log)(\\.)(debug|info|warn|error|critical)", + "captures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "storage.type.tpl" + } + } + }, + "text_functions": { + "match": "(text)(\\.)(lower|upper|toNumber|replace|join|split|strip|leftStrip|rightStrip|hash|ordinal)", + "captures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "variable.parameter.tpl" + } + } + }, + "number_functions": { + "match": "(number)(\\.)(toChar|toText|range)", + "captures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "variable.parameter.tpl" + } + } + }, + "table_functions": { + "match": "(table\\b)(\\s*\\(|(\\.)(remove))", + "captures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "punctuation.accessor.tpl" + }, + "4": { + "name": "variable.parameter.tpl" + } + } + }, + "regex_functions": { + "match": "(regex)(\\.)(extract|extractAll)", + "captures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "variable.parameter.tpl" + } + } + }, + "xpath_functions": { + "match": "(xpath)(\\.)(evaluate|openDocument|closeDocument)", + "captures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "variable.parameter.tpl" + } + } + }, + "discovery_manipulation_functions": { + "match": "(discovery)(\\.)(process|children|descendents|parent|allProcesses|access|startTime)", + "captures": { + "1": { + "name": "support.function.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "variable.parameter.tpl" + } + } + }, + "binary_functions": { + "match": "(binary)(\\.)(toHexString|toIPv4|toIPv4z|toIPv6|toIPv6z|toMACAddress|toValue|toWWN)", + "captures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "variable.parameter.tpl" + } + } + }, + "json_functions": { + "match": "(json)(\\.)(encode|decode)", + "captures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "variable.parameter.tpl" + } + } + }, + "related_functions": { + "match": "(related)(\\.)(detailContainer|host\\b|hostingNode)", + "captures": { + "1": { + "name": "support.function.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "variable.parameter.tpl" + } + } + }, + "mail_functions": { + "match": "(mail)(\\.)(send)", + "captures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "variable.parameter.tpl" + } + } + }, + "time_functions": { + "match": "(time)(\\.)(current|delta\\b|parseLocal|parseUTC|formatLocal|formatUTC|toTicks|fromTicks|deltaFromTicks)", + "captures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "variable.parameter.tpl" + } + } + }, + "system_functions": { + "match": "(system)(\\.)(getOption|cmdbSync)", + "captures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "variable.parameter.tpl" + } + } + }, + "inference_functions": { + "match": "(inference)(\\.)(associate|contributor|primary|relation|withdrawal|destruction)", + "captures": { + "1": { + "name": "support.function.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "variable.parameter.tpl" + } + } + }, + "ConversionFunctions": { + "match": "(ConversionFunctions)(\\.)(isValidSerialNumber|convertToCharString|convertToBytes)", + "captures": { + "1": { + "name": "entity.name.function.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "entity.name.function.tpl" + } + } + }, + "DiscoveryFunctions": { + "comment": "These functions come from the TKU Module 'Discovery Functions'", + "match": "(DiscoveryFunctions)(\\.)(pathNormalization|getCleanedupPath|getMaxVersion|getExeCwdPath|sortList|runActiveCommand|locateCommands|checkCommandList)", + "captures": { + "1": { + "name": "entity.name.function.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "entity.name.function.tpl" + } + } + }, + "RDBMSFunctions": { + "match": "(RDBMSFunctions)(\\.)(oracleOraFileParser|performRdbmsSisSearch|oracleNetServiceNameSearch|dsnRdbmsServers|parseJDBC)", + "captures": { + "1": { + "name": "entity.name.function.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "entity.name.function.tpl" + } + } + }, + "rdbms_functions": { + "match": "(rdbms_functions)(\\.)(oracle_ora_file_parser|perform_rdbms_sis_search|related_rdbms_sis_search|related_rdbms_sis_search_extended|oracle_net_service_name_search|dsn_rdbms_servers|parseJDBC|jdbc_search|find_db_server)", + "captures": { + "1": { + "name": "entity.name.function.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "entity.name.function.tpl" + } + } + }, + "SearchFunctions": { + "match": "(SearchFunctions)(\\.)(getNodeIp|getHostingNodes|getSoftwareNodes|getSI|identifyHostWithUuid|relatedSisSearchOnMultipleHosts)", + "captures": { + "1": { + "name": "entity.name.function.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "entity.name.function.tpl" + } + } + }, + "common_functions": { + "match": "(functions)(\\.)(validate_host_address_format|domain_lookup|identify_host_perform_search|identify_host_perform_search_in_scope|identify_host|identify_host_extended|related_sis_search\\b|related_sis_search_on_multiple_hosts|related_sis_search_on_multiple_hosts_extended|related_sis_search_extended|related_si_types_search|path_normalization|links_management|get_cleanedup_path|get_max_version|get_exe_cwd_path|sort_list|run_priv_cmd|has_process|isValidSerialNumber|convertToCharString|wmiFollowAssociations|checkForDecimal|convertToBytes|identify_host_with_uuid|locateCommands|find_server|checkCommandList)", + "captures": { + "1": { + "name": "entity.name.function.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "entity.name.function.tpl" + } + } + }, + "local_functions": { + "match": "(local_functions|localFunctions)(\\.)(\\w+)", + "captures": { + "1": { + "name": "entity.name.function.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "entity.name.function.tpl" + } + } + }, + "list_append": { + "match": "(list)(\\.)(append)", + "captures": { + "1": { + "name": "entity.name.function.tpl" + }, + "2": { + "name": "punctuation.accessor.tpl" + }, + "3": { + "name": "entity.name.function.tpl" + } + } + }, + "all_functions": { + "patterns": [ + { + "include": "#discovery_action_functions" + }, + { + "include": "#discovery_cloud_aws_functions" + }, + { + "include": "#discovery_cloud_azure_functions" + }, + { + "include": "#model_functions" + }, + { + "include": "#model_node_functions" + }, + { + "include": "#model_rel_functions" + }, + { + "include": "#sync_rel_functions" + }, + { + "include": "#sync_shared_functions" + }, + { + "include": "#log_functions" + }, + { + "include": "#text_functions" + }, + { + "include": "#number_functions" + }, + { + "include": "#table_functions" + }, + { + "include": "#regex_functions" + }, + { + "include": "#xpath_functions" + }, + { + "include": "#discovery_manipulation_functions" + }, + { + "include": "#binary_functions" + }, + { + "include": "#json_functions" + }, + { + "include": "#related_functions" + }, + { + "include": "#mail_functions" + }, + { + "include": "#time_functions" + }, + { + "include": "#inference_functions" + }, + { + "include": "#system_functions" + }, + { + "include": "#ConversionFunctions" + }, + { + "include": "#SearchFunctions" + }, + { + "include": "#rdbms_functions" + }, + { + "include": "#RDBMSFunctions" + }, + { + "include": "#DiscoveryFunctions" + }, + { + "include": "#common_functions" + }, + { + "include": "#local_functions" + }, + { + "include": "#list_append" + } + ] + }, + "node_relationships": { + "comment": "Covers node relationships, does not check for proper relationship names", + "match": "(?i)([a-z]*):([a-z]*):([a-z]*):([a-z]*)", + "name": "keyword.control.conditional.tpl", + "captures": { + "1": { + "name": "variable.parameter.tpl" + }, + "2": { + "name": "variable.parameter.tpl" + }, + "3": { + "name": "variable.parameter.tpl" + }, + "4": { + "name": "variable.parameter.tpl" + } + } + }, + "if_statement": { + "comment": "Match if ..values.. then and include usual lines. #lines can override some marks", + "begin": "(?!end if;|then)(\\bif)", + "beginCaptures": { + "1": { + "name": "keyword.control.conditional.tpl" + } + }, + "end": "(\\bthen)", + "endCaptures": { + "1": { + "name": "keyword.control.conditional.tpl" + } + }, + "patterns": [ + { + "include": "#all_controls" + }, + { + "include": "#regexes" + }, + { + "include": "#all_keys" + }, + { + "include": "#lines" + }, + { + "include": "#keywords_attrs" + }, + { + "include": "#all_functions" + } + ] + }, + "elif_statement": { + "comment": "Match elif ..values.. then and include usual lines. #lines can override some marks", + "begin": "(?!end if;|then)(\\belif)", + "beginCaptures": { + "1": { + "name": "keyword.control.conditional.tpl" + } + }, + "end": "(\\bthen)", + "endCaptures": { + "1": { + "name": "keyword.control.conditional.tpl" + } + }, + "patterns": [ + { + "include": "#all_controls" + }, + { + "include": "#regexes" + }, + { + "include": "#all_keys" + }, + { + "include": "#lines" + }, + { + "include": "#keywords_attrs" + }, + { + "include": "#all_functions" + } + ] + }, + "else_statement": { + "match": "\\belse\\b", + "name": "keyword.control.conditional.tpl" + }, + "end_if_statement": { + "match": "(?!;|if|else\\w)\\bend\\b\\s\\bif\\b;", + "name": "keyword.control.conditional.tpl" + }, + "for_statement": { + "comment": "TODO: Confirm logic is accurate", + "begin": "^\\s*(for\\b)(?!;)", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "end": "(\\bdo\\b)", + "endCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "patterns": [ + { + "include": "#regex" + }, + { + "include": "#lines" + }, + { + "include": "#all_controls" + } + ] + }, + "end_for_statement": { + "match": "\\bend\\sfor;\\s+?", + "name": "keyword.control.tpl" + }, + "keywords_all": { + "match": "\\b(size|in|aged|and|as|at|break|by|configuration|continue|created|default|desc|deleted|do|enumeration|exists|expand|explode|false|flags|from|has|in|is|locale|matches|modified|nodes|nodecount|none|not|on|or|order|out|overrides|processwith|relationship|removal|requires|search|show|step|stop|substring|subword|summary|tags|taxonomy|traverse|true|unconfirmed|where|with|void|has|substring)\\b", + "name": "storage.type.class.tpl" + }, + "keywords_cmd": { + "match": "\\b(unix_cmd|windows_cmd)", + "captures": { + "1": { + "name": "keyword.operator.tpl" + } + } + }, + "keywords_cmd_indentify_trigger": { + "match": "\\b(unix_cmd|windows_cmd|cmd|args)", + "captures": { + "1": { + "name": "keyword.operator.tpl" + } + } + }, + "if_ADDM_statement": { + "match": "(#IFADDM)\\s+(\\d+.\\d+)", + "captures": { + "1": { + "name": "keyword.control.conditional.tpl" + }, + "2": { + "name": "constant.numeric.tpl" + } + } + }, + "elif_ADDM_statement": { + "match": "(#ELIFADDM)\\s+(\\d+.\\d+)", + "captures": { + "1": { + "name": "keyword.control.conditional.tpl" + }, + "2": { + "name": "constant.numeric.tpl" + } + } + }, + "else_ADDM_statement": { + "match": "#ELSE\\s+?", + "name": "keyword.control.conditional.tpl" + }, + "end_if_ADDM_statement": { + "match": "#ENDIFADDM\\s+?", + "name": "keyword.control.conditional.tpl" + }, + "if_addm": { + "patterns": [ + { + "include": "#if_ADDM_statement" + }, + { + "include": "#elif_ADDM_statement" + }, + { + "include": "#else_ADDM_statement" + }, + { + "include": "#end_if_ADDM_statement" + } + ] + }, + "all_controls": { + "patterns": [ + { + "include": "#if_statement" + }, + { + "include": "#elif_statement" + }, + { + "include": "#else_statement" + }, + { + "include": "#end_if_statement" + }, + { + "include": "#for_statement" + }, + { + "include": "#end_for_statement" + }, + { + "include": "#if_addm" + }, + { + "include": "#keywords_all" + }, + { + "include": "#keywords_cmd" + }, + { + "include": "#all_keys" + }, + { + "include": "#node_relationships" + }, + { + "include": "#begin_variable_assignment" + }, + { + "include": "#end_variable_assignment" + }, + { + "include": "#temp_test" + } + ] + }, + "doc_strings_block_doulbe_q": { + "comment": "TRIGGER TYPE Working fine and DATE & TASK highlighted Adding SQL support via #lines", + "begin": "(\\\"{3})", + "beginCaptures": { + "1": { + "name": "comment.block.documentation.tpl" + } + }, + "end": "(\\\"{3})", + "contentName": "comment.block.documentation.tpl", + "endCaptures": { + "1": { + "name": "comment.block.documentation.tpl" + } + }, + "patterns": [ + { + "name": "variable.parameter.tpl", + "match": "(\\d{2,4}\\-\\d{2,4}\\-\\d{2,4}).*?(\\d{2,4}\\-\\d{2,4}\\-\\d{2,4})?", + "captures": { + "1": { + "name": "variable.other.tpl" + } + } + }, + { + "name": "variable.parameter.tpl", + "match": "\\((QM\\d+)\\)", + "captures": { + "1": { + "name": "variable.other.tpl" + } + } + }, + { + "name": "variable.parameter.tpl", + "match": "\\((DRDC1-\\d+)\\)", + "captures": { + "1": { + "name": "variable.other.tpl" + } + } + }, + { + "name": "variable.parameter.tpl", + "match": "\\((TKU-\\d+)\\)", + "captures": { + "1": { + "name": "variable.other.tpl" + } + } + }, + { + "name": "variable.parameter.tpl", + "match": "\\((esc\\s+?\\d+)\\)", + "captures": { + "1": { + "name": "variable.other.tpl" + } + } + } + ] + }, + "doc_strings_block_single_q": { + "comment": "TRIGGER TYPE Working fine and DATETASK highlighted", + "begin": "('{3})", + "beginCaptures": { + "1": { + "name": "comment.block.documentation.tpl" + } + }, + "end": "('{3})", + "contentName": "comment.block.documentation.tpl", + "endCaptures": { + "1": { + "name": "comment.block.documentation.tpl" + } + }, + "patterns": [ + { + "name": "variable.parameter.tpl", + "match": "(\\d{2,4}\\-\\d{2,4}\\-\\d{2,4}).*?(\\d{2,4}\\-\\d{2,4}\\-\\d{2,4})?", + "captures": { + "1": { + "name": "variable.other.tpl" + } + } + }, + { + "name": "variable.parameter.tpl", + "match": "\\((QM\\d+)\\)", + "captures": { + "1": { + "name": "variable.other.tpl" + } + } + }, + { + "name": "variable.parameter.tpl", + "match": "\\((DRDC1-\\d+)\\)", + "captures": { + "1": { + "name": "variable.other.tpl" + } + } + }, + { + "name": "variable.parameter.tpl", + "match": "\\((TKU-\\d+)\\)", + "captures": { + "1": { + "name": "variable.other.tpl" + } + } + }, + { + "name": "variable.parameter.tpl", + "match": "\\((esc\\s+?\\d+)\\)", + "captures": { + "1": { + "name": "variable.other.tpl" + } + } + } + ] + }, + "doc_strings_single_simple": { + "comment": "Adding SQL support via #lines", + "name": "comment.block.documentation.tpl", + "match": "('{3}([^']+?)'{3})", + "captures": { + "1": { + "name": "string.quoted.triple.tpl" + }, + "2": { + "name": "string.quoted.triple.tpl" + }, + "3": { + "name": "string.quoted.triple.tpl" + } + } + }, + "doc_strings": { + "patterns": [ + { + "include": "#doc_strings_block_doulbe_q" + }, + { + "include": "#doc_strings_block_single_q" + }, + { + "include": "#doc_strings_single_simple" + } + ] + }, + "define": { + "comment": "Adding SQL support via #lines", + "begin": "\\s*(define)\\s+(\\S+)\\((.*?)\\)(\\s\\S+\\s(.*))?", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "entity.name.function.tpl" + }, + "3": { + "name": "variable.parameter.tpl" + }, + "4": { + "name": "constant.numeric.tpl" + } + }, + "end": "\\s*(end\\sdefine;\\s+?$)", + "endCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "patterns": [ + { + "include": "#doc_strings" + }, + { + "include": "#all_functions" + }, + { + "include": "#all_controls" + }, + { + "include": "#if_addm" + }, + { + "include": "#dev_keys" + }, + { + "include": "#lines" + } + ] + }, + "alt_define": { + "comment": "Adding SQL support via #lines", + "begin": "\\s*(define)\\s+(\\S+)(\\s\\S+\\s(.*))?", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "entity.name.function.tpl" + }, + "3": { + "name": "variable.parameter.tpl" + } + }, + "end": "\\s*(end\\sdefine;)", + "endCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "patterns": [ + { + "include": "#doc_strings" + }, + { + "include": "#all_functions" + }, + { + "include": "#all_controls" + }, + { + "include": "#if_addm" + }, + { + "include": "#dev_keys" + }, + { + "include": "#lines" + } + ] + }, + "simple_identifiers": { + "begin": "\\b(identify)\\s+(\\w+)\\s(\\d+(?:\\.\\d+)+)", + "beginCaptures": { + "1": { + "name": "keyword.control.tpl" + }, + "2": { + "name": "entity.name.function" + }, + "3": { + "name": "constant.numeric.tpl" + } + }, + "end": "(end\\sidentify;)", + "endCaptures": { + "1": { + "name": "keyword.control.tpl" + } + }, + "patterns": [ + { + "include": "#lines" + }, + { + "include": "#keywords_cmd" + }, + { + "include": "#keywords_all" + }, + { + "include": "#regexes" + }, + { + "include": "#keywords_cmd_indentify_trigger" + }, + { + "include": "#if_addm" + } + ] + }, + "keywords_attributes": { + "patterns": [ + { + "include": "#keywords_attrs" + } + ] + }, + "keywords_attrs": { + "comment": "Changed disabled to 1 to turn off DEV highlighting.", + "disabled": 0, + "match": "\\b(key_group|key|name|type|version|short_name|install_root|listening_ports|product_version|full_version)\\b", + "captures": { + "1": { + "name": "storage.type.tpl" + } + } + }, + "traverse": { + "name": "entity.name.function.model", + "match": "\\#(?:(\\w+)(:|\\.))+(\\w+)", + "captures": { + "3": { + "name": "keyword.control.model" + } + } + }, + "regular_expressions": { + "comment": "Changed disabled to 1 to turn off syntax highlighting in “r” strings.", + "disabled": 0, + "patterns": [ + { + "include": "source.regexp.tpl" + } + ] + }, + "dev_keys": { + "patterns": [ + { + "include": "#keywords_attributes" + }, + { + "include": "#traverse" + } + ] + } + } +} \ No newline at end of file diff --git a/vsc-extension-quickstart.md b/vsc-extension-quickstart.md deleted file mode 100644 index f518bb8..0000000 --- a/vsc-extension-quickstart.md +++ /dev/null @@ -1,48 +0,0 @@ -# Welcome to your VS Code Extension - -## What's in the folder - -* This folder contains all of the files necessary for your extension. -* `package.json` - this is the manifest file in which you declare your extension and command. - * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. -* `src/extension.ts` - this is the main file where you will provide the implementation of your command. - * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. - * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. - -## Setup - -* install the recommended extensions (amodio.tsl-problem-matcher, ms-vscode.extension-test-runner, and dbaeumer.vscode-eslint) - - -## Get up and running straight away - -* Press `F5` to open a new window with your extension loaded. -* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. -* Set breakpoints in your code inside `src/extension.ts` to debug your extension. -* Find output from your extension in the debug console. - -## Make changes - -* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. -* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. - - -## Explore the API - -* You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. - -## Run tests - -* Install the [Extension Test Runner](https://marketplace.visualstudio.com/items?itemName=ms-vscode.extension-test-runner) -* Run the "watch" task via the **Tasks: Run Task** command. Make sure this is running, or tests might not be discovered. -* Open the Testing view from the activity bar and click the Run Test" button, or use the hotkey `Ctrl/Cmd + ; A` -* See the output of the test result in the Test Results view. -* Make changes to `src/test/extension.test.ts` or create new test files inside the `test` folder. - * The provided test runner will only consider files matching the name pattern `**.test.ts`. - * You can create folders inside the `test` folder to structure your tests any way you want. - -## Go further - -* Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). -* [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace. -* Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).