Compare commits
No commits in common. "00bdc8744de4fbaeaae1749f82f2315f4b6ab568" and "9299eb63872f21a5ff21be40d2aa9dcfd0c76d34" have entirely different histories.
00bdc8744d
...
9299eb6387
46
package.json
46
package.json
|
|
@ -35,63 +35,27 @@
|
|||
"main": "./dist/extension.js",
|
||||
"contributes": {
|
||||
"configuration": {
|
||||
"title": "BMC TPL Settings",
|
||||
"title": "BMC TPL Einstellungen",
|
||||
"properties": {
|
||||
"tpl.metadata.author": {
|
||||
"type": "string",
|
||||
"default": "Autor",
|
||||
"description": "Standard-Author for new generated TPL-Modules."
|
||||
"description": "Standard-Autor für neu generierte TPL-Module."
|
||||
},
|
||||
"tpl.metadata.origin": {
|
||||
"type": "string",
|
||||
"default": "Custom",
|
||||
"description": "Standard-Origin for new generated TPL-Modules."
|
||||
"description": "Standard-Origin für neu generierte TPL-Module."
|
||||
},
|
||||
"tpl.metadata.publisher": {
|
||||
"type": "string",
|
||||
"default": "IT-Operations",
|
||||
"description": "Standard-Publisher ffor new generated TPL-Modules."
|
||||
"description": "Standard-Publisher für neu generierte TPL-Module."
|
||||
},
|
||||
"tpl.metadata.treePathBase": {
|
||||
"type": "string",
|
||||
"default": "Company",
|
||||
"description": "First Part (Basis) of the tree_paths in the Metadata."
|
||||
},
|
||||
"tpl.metadata.require.origin": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Marks 'origin' as Required on module level."
|
||||
},
|
||||
"tpl.metadata.require.author": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Marks 'author' as Required on module level."
|
||||
},
|
||||
"tpl.metadata.require.products": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Marks 'products' aas Required on module level."
|
||||
},
|
||||
"tpl.metadata.require.publishers": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Marks 'publishers' as Required on module level."
|
||||
},
|
||||
"tpl.metadata.require.treePath": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Marks 'tree_path' as Required on module level."
|
||||
},
|
||||
"tpl.metadata.treePath.minLevels": {
|
||||
"type": "number",
|
||||
"default": 3,
|
||||
"description": "How many levels should the tree_path in Metadata at least have? (e.g. 3 for 'Base', 'Category', 'Subcategory')."
|
||||
},
|
||||
"tpl.metadata.treePath.staticNodes": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"default": ["default"],
|
||||
"description": "Fixed definitions for the tree_path left to right (e.g. ['Company', 'Software']). The rest of the levels are free to choose."
|
||||
"description": "Erster Teil (Basis) des tree_paths in den Metadaten."
|
||||
},
|
||||
"tpl.version": {
|
||||
"type": "number",
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export class LinterContext {
|
|||
public inModuleMetadata = false;
|
||||
public hasModuleMetadata = false;
|
||||
public moduleMetadataBlockLine = -1;
|
||||
public foundModuleMetadata = new Set<string>();
|
||||
public foundModuleMetadata = { origin: false, author: false, products: false, publishers: false, tree_path: false };
|
||||
|
||||
// Klammer-Tracking
|
||||
public parenStack: number[] = [];
|
||||
|
|
|
|||
|
|
@ -1,27 +1,36 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { LinterContext } from './LinterContext';
|
||||
import { LinterRule } from './rules/LinterRule';
|
||||
import { SyntaxRule } from './rules/SyntaxRule';
|
||||
import { BlockRule } from './rules/BlockRule';
|
||||
import { MetadataRule } from './rules/MetadataRule';
|
||||
import { IndentationRule } from './rules/IndentationRule'; // 1. Hier importieren
|
||||
|
||||
// ... (deine eventuellen anderen Hilfs-Imports wie trackStrings) ...
|
||||
|
||||
export function validateTplDocument(document: vscode.TextDocument, collection: vscode.DiagnosticCollection) {
|
||||
if (document.languageId !== 'tpl') return;
|
||||
if (document.languageId !== 'tpl') { return; }
|
||||
|
||||
const ctx = new LinterContext();
|
||||
const config = vscode.workspace.getConfiguration('tpl');
|
||||
ctx.configuredTplVersion = config.get<number>('version', 23);
|
||||
|
||||
const text = document.getText();
|
||||
const lines = text.split('\n');
|
||||
const ctx = new LinterContext();
|
||||
|
||||
// 2. Hier nur die 3 existierenden Regeln reinlegen (MetadataRule entfernt!)
|
||||
const rules = [
|
||||
// 1. Version prüfen
|
||||
const versionMatch = text.match(/tpl\s+1\.(\d+)\s+module/);
|
||||
if (versionMatch) {
|
||||
ctx.tplVersion = parseInt(versionMatch[1], 10);
|
||||
if (ctx.tplVersion < ctx.configuredTplVersion) {
|
||||
const linesToSearch = text.substring(0, versionMatch.index! + 30).split('\n');
|
||||
ctx.addDiag(linesToSearch.length - 1, 0, versionMatch[0].length, `Das Modul nutzt eine alte TPL Version (1.${ctx.tplVersion}). Aktuell in den Settings definiert ist 1.${ctx.configuredTplVersion}.`, vscode.DiagnosticSeverity.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Regeln laden
|
||||
const rules: LinterRule[] = [
|
||||
new SyntaxRule(),
|
||||
new MetadataRule(),
|
||||
new BlockRule(),
|
||||
new IndentationRule()
|
||||
new BlockRule()
|
||||
];
|
||||
|
||||
// 3. Zeilenweise iterieren
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const rawLine = lines[i];
|
||||
const line = rawLine.trim();
|
||||
|
|
@ -45,14 +54,18 @@ export function validateTplDocument(document: vscode.TextDocument, collection: v
|
|||
|
||||
// Regeln anwenden
|
||||
for (const rule of rules) {
|
||||
rule.checkLine(line, rawLine, i, ctx, lines);
|
||||
rule.checkLine(line, rawLine, i, ctx, lines); // Hier 'lines' ergänzen!
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Abschluss-Prüfungen durchführen
|
||||
for (const rule of rules) {
|
||||
if (rule.checkEnd) {
|
||||
rule.checkEnd(lines, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// Diagnostics an VS Code übergeben
|
||||
collection.set(document.uri, ctx.diagnostics);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,15 @@ export class BlockRule implements LinterRule {
|
|||
else { ctx.addDiag(lineIdx, 0, rawLine.length, "Jede Datei muss mit 'tpl 1.x module <name>;' beginnen.", vscode.DiagnosticSeverity.Error); ctx.hasModuleDef = true; }
|
||||
}
|
||||
|
||||
// NEU: Wenn wir im Metadata-Block sind, ignorieren wir den Rest. Die MetadataRule übernimmt!
|
||||
if (ctx.inModuleMetadata || (line.startsWith('metadata') && ctx.inBlock === 'none')) {
|
||||
if (line.startsWith('metadata') && ctx.inBlock === 'none') {
|
||||
ctx.inModuleMetadata = true;
|
||||
ctx.hasModuleMetadata = true;
|
||||
ctx.moduleMetadataBlockLine = lineIdx;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.inModuleMetadata) {
|
||||
this.trackModuleMetadata(line, lineIdx, ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -22,6 +29,8 @@ export class BlockRule implements LinterRule {
|
|||
}
|
||||
|
||||
this.handleBlockOpenings(line, rawLine, lineIdx, ctx);
|
||||
|
||||
// NEU: if, for und while tracken
|
||||
this.handleControlBlocks(line, rawLine, lineIdx, ctx);
|
||||
|
||||
if (ctx.inBlock === 'pattern') {
|
||||
|
|
@ -31,6 +40,16 @@ export class BlockRule implements LinterRule {
|
|||
this.checkEndBlocks(line, rawLine, lineIdx, ctx);
|
||||
}
|
||||
|
||||
public checkEnd(lines: string[], ctx: LinterContext): void {
|
||||
for (const unclosedLine of ctx.openDefines) {
|
||||
ctx.addDiag(unclosedLine, 0, lines[unclosedLine].length, "Block 'define' wurde nicht mit 'end define;' geschlossen!", vscode.DiagnosticSeverity.Error);
|
||||
}
|
||||
// NEU: Offene if/for/while Blöcke am Dateiende ankreiden
|
||||
for (const block of ctx.openControlBlocks) {
|
||||
ctx.addDiag(block.lineIdx, 0, lines[block.lineIdx].length, `Block '${block.type}' wurde nicht mit 'end ${block.type};' geschlossen!`, vscode.DiagnosticSeverity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private handleControlBlocks(line: string, rawLine: string, lineIdx: number, ctx: LinterContext) {
|
||||
// Block wird geöffnet
|
||||
if (/^if\b/.test(line)) { ctx.openControlBlocks.push({ type: 'if', lineIdx }); }
|
||||
|
|
@ -174,8 +193,4 @@ export class BlockRule implements LinterRule {
|
|||
else { ctx.addDiag(lineIdx, 0, line.length, "'end define' gefunden, ohne offenes 'define'.", vscode.DiagnosticSeverity.Error); }
|
||||
}
|
||||
}
|
||||
|
||||
public checkEnd(lines: string[], ctx: LinterContext): void {
|
||||
// empty
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { LinterRule } from './LinterRule';
|
||||
import { LinterContext } from '../LinterContext';
|
||||
|
||||
export class IndentationRule implements LinterRule {
|
||||
|
||||
public checkLine(line: string, rawLine: string, lineIdx: number, ctx: LinterContext, lines: string[]): void {
|
||||
// Leere Zeilen oder Zeilen, die nur aus Leerzeichen/Tabs bestehen, ignorieren
|
||||
if (rawLine.trim().length === 0) return;
|
||||
|
||||
// Wenn Tabs drin sind, meckert die SyntaxRule bereits. Das ignorieren wir hier.
|
||||
if (rawLine.includes('\t')) return;
|
||||
|
||||
const leadingSpacesMatch = rawLine.match(/^ +/);
|
||||
const spaceCount = leadingSpacesMatch ? leadingSpacesMatch[0].length : 0;
|
||||
|
||||
// Ist die Anzahl der Leerzeichen kein Vielfaches von 4?
|
||||
if (spaceCount > 0 && spaceCount % 4 !== 0) {
|
||||
ctx.addDiag(
|
||||
lineIdx,
|
||||
0,
|
||||
spaceCount, // Markiert genau den Bereich der Leerzeichen
|
||||
`Unsaubere Einrückung (${spaceCount} Leerzeichen). Nutze ein Vielfaches von 4. (Tipp: Shift+Alt+F zum Auto-Formatieren)`,
|
||||
vscode.DiagnosticSeverity.Warning // Warning reicht hier völlig, Error wäre beim Tippen zu aggressiv
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public checkEnd(lines: string[], ctx: LinterContext): void {
|
||||
// Hier muss am Ende der Datei nichts geprüft werden
|
||||
}
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { LinterRule } from './LinterRule';
|
||||
import { LinterContext } from '../LinterContext';
|
||||
|
||||
export class MetadataRule implements LinterRule {
|
||||
public checkLine(line: string, rawLine: string, lineIdx: number, ctx: LinterContext, lines: string[]): void {
|
||||
if (line.startsWith('metadata') && ctx.inBlock === 'none') {
|
||||
ctx.inModuleMetadata = true;
|
||||
ctx.hasModuleMetadata = true;
|
||||
ctx.moduleMetadataBlockLine = lineIdx;
|
||||
ctx.foundModuleMetadata.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.inModuleMetadata) {
|
||||
const config = vscode.workspace.getConfiguration('tpl');
|
||||
|
||||
// Felder erfassen
|
||||
if (line.match(/^origin\b/)) ctx.foundModuleMetadata.add('origin');
|
||||
if (line.match(/^author\b/)) ctx.foundModuleMetadata.add('author');
|
||||
if (line.match(/^products\b/)) ctx.foundModuleMetadata.add('products');
|
||||
if (line.match(/^publishers\b/)) ctx.foundModuleMetadata.add('publishers');
|
||||
|
||||
// Tree-Path Sonderprüfung
|
||||
if (line.match(/^tree_path\b/)) {
|
||||
ctx.foundModuleMetadata.add('tree_path');
|
||||
if (config.get<boolean>('metadata.require.treePath', true)) {
|
||||
this.checkTreePath(line, rawLine, lineIdx, ctx, config);
|
||||
}
|
||||
}
|
||||
|
||||
// Block-Ende: Pflichtfelder abgleichen
|
||||
if (line.startsWith('end metadata')) {
|
||||
ctx.inModuleMetadata = false;
|
||||
this.checkRequiredFields(ctx, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private checkTreePath(line: string, rawLine: string, lineIdx: number, ctx: LinterContext, config: vscode.WorkspaceConfiguration) {
|
||||
const minLevels = config.get<number>('metadata.treePath.minLevels', 3);
|
||||
const staticNodes = config.get<string[]>('metadata.treePath.staticNodes', ['Grafing']);
|
||||
|
||||
// Extrahiert die Strings aus tree_path := 'A', 'B';
|
||||
const match = line.match(/tree_path\s*:=\s*(.*);/);
|
||||
if (match) {
|
||||
const paths = match[1].split(',').map(s => s.trim().replace(/^['"]|['"]$/g, ''));
|
||||
|
||||
if (paths.length < minLevels) {
|
||||
ctx.addDiag(lineIdx, 0, rawLine.length, `Der tree_path muss mindestens ${minLevels} Ebenen haben!`, vscode.DiagnosticSeverity.Error);
|
||||
}
|
||||
|
||||
for (let i = 0; i < staticNodes.length; i++) {
|
||||
if (paths[i] !== staticNodes[i]) {
|
||||
ctx.addDiag(lineIdx, 0, rawLine.length, `Die tree_path Ebene ${i + 1} muss zwingend '${staticNodes[i]}' lauten!`, vscode.DiagnosticSeverity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private checkRequiredFields(ctx: LinterContext, config: vscode.WorkspaceConfiguration) {
|
||||
const requiredFields: string[] = [];
|
||||
if (config.get<boolean>('metadata.require.origin', true)) requiredFields.push('origin');
|
||||
if (config.get<boolean>('metadata.require.author', true)) requiredFields.push('author');
|
||||
if (config.get<boolean>('metadata.require.products', true)) requiredFields.push('products');
|
||||
if (config.get<boolean>('metadata.require.publishers', true)) requiredFields.push('publishers');
|
||||
if (config.get<boolean>('metadata.require.treePath', true)) requiredFields.push('tree_path');
|
||||
|
||||
const missing = requiredFields.filter(f => !ctx.foundModuleMetadata.has(f));
|
||||
|
||||
if (missing.length > 0 && ctx.moduleMetadataBlockLine !== -1) {
|
||||
const diag = ctx.addDiag(ctx.moduleMetadataBlockLine, 0, 8, `Fehlende Pflicht-Metadaten (Modul-Ebene): ${missing.join(', ')}`, vscode.DiagnosticSeverity.Error);
|
||||
diag.code = 'missing_module_metadata';
|
||||
}
|
||||
}
|
||||
|
||||
public checkEnd(lines: string[], ctx: LinterContext): void {
|
||||
// empty
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ export class SyntaxRule implements LinterRule {
|
|||
public checkLine(line: string, rawLine: string, lineIdx: number, ctx: LinterContext, lines: string[]): void {
|
||||
this.checkTabs(rawLine, lineIdx, ctx);
|
||||
|
||||
// TODOs finden (blauer Info-Hinweis)
|
||||
const todoMatch = rawLine.match(/\bTODO\s*:(.*)/i);
|
||||
if (todoMatch) {
|
||||
ctx.addDiag(lineIdx, todoMatch.index!, rawLine.length, `TODO: ${todoMatch[1].trim()}`, vscode.DiagnosticSeverity.Information);
|
||||
|
|
@ -102,7 +103,8 @@ export class SyntaxRule implements LinterRule {
|
|||
// "in" (für for-Schleifen) als gültigen Zeilenumbruch hinzugefügt
|
||||
const continuesOnNextLine = /(,|and|or|not|\+|-|\*|\/|=|:=|matches|in)$/i.test(codePart);
|
||||
|
||||
´const endsWithBlockOpener = /\b(then|do)$/.test(codePart);
|
||||
// HIER GEFIXT: Wenn die Zeile auf 'then' oder 'do' endet, verlangt sie kein Semikolon!
|
||||
const endsWithBlockOpener = /\b(then|do)$/.test(codePart);
|
||||
|
||||
if (!noSemicolonNeeded && !ctx.justOpenedString && ctx.parenStack.length === 0 && ctx.bracketStack.length === 0 && ctx.inBlock !== 'enumeration' && !ctx.inConditionBlock && !continuesOnNextLine && !endsWithBlockOpener) {
|
||||
if (codePart.length > 0 && !codePart.endsWith(';') && !codePart.endsWith('{') && !codePart.endsWith(',')) {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { tplBuiltIns } from '../data/TplBuiltIns';
|
||||
|
||||
export class TplHoverProvider implements vscode.HoverProvider {
|
||||
public async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.Hover | null> {
|
||||
const range = document.getWordRangeAtPosition(position, /[a-zA-Z0-9_.]+/);
|
||||
if (!range) return null;
|
||||
|
||||
const word = document.getText(range);
|
||||
const word = document.getText(range); // z.B. "modelling.testDefine"
|
||||
const text = document.getText();
|
||||
const lines = text.split('\n');
|
||||
|
||||
|
|
@ -14,26 +13,7 @@ export class TplHoverProvider implements vscode.HoverProvider {
|
|||
const importBase = parts[0];
|
||||
const funcName = parts.length > 1 ? parts[1] : word;
|
||||
|
||||
// 1. BMC BUILT-INS DYNAMISCH AUSLESEN
|
||||
const bmcNamespaces = Object.keys(tplBuiltIns);
|
||||
|
||||
if (bmcNamespaces.includes(importBase)) {
|
||||
// Wenn die Methode in der Datei existiert, zeige die echte Doku!
|
||||
if (funcName !== importBase && tplBuiltIns[importBase][funcName]) {
|
||||
const bmcDoc = tplBuiltIns[importBase][funcName];
|
||||
const md = new vscode.MarkdownString();
|
||||
|
||||
// Wir zeigen den echten Methodennamen als Code-Block
|
||||
md.appendCodeblock(`${importBase}.${funcName}`, 'tpl');
|
||||
md.appendMarkdown(`\n\n---\n\n${bmcDoc.description}`);
|
||||
|
||||
return new vscode.Hover(md);
|
||||
}
|
||||
// Fallback, wenn man nur über dem Namespace (z.B. "discovery") hovert
|
||||
return new vscode.Hover(new vscode.MarkdownString(`**${word}**\n\n*(BMC Built-in Namespace)*`));
|
||||
}
|
||||
|
||||
// 2. Lokale Defines
|
||||
// 1. Lokale Defines
|
||||
if (parts.length === 1 || parts.length === 2) {
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
|
|
@ -52,12 +32,13 @@ export class TplHoverProvider implements vscode.HoverProvider {
|
|||
}
|
||||
}
|
||||
|
||||
// 3. Cross-File Hover für from ... import
|
||||
// 2. Cross-File Hover für from ... import
|
||||
const fromImportRegex = new RegExp(`from\\s+([a-zA-Z0-9_.]+)\\s+import\\s+${importBase}\\b`);
|
||||
const fromMatch = text.match(fromImportRegex);
|
||||
if (fromMatch) {
|
||||
const moduleName = fromMatch[1];
|
||||
|
||||
// Wenn der User über die Methode fährt (modelling.XYZ)
|
||||
if (funcName !== importBase) {
|
||||
const files = await vscode.workspace.findFiles('**/*.tpl', '**/node_modules/**');
|
||||
for (const file of files) {
|
||||
|
|
@ -83,9 +64,14 @@ export class TplHoverProvider implements vscode.HoverProvider {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Wenn er nur über "modelling" fährt
|
||||
return new vscode.Hover(new vscode.MarkdownString(`Importierte Definitions **${importBase}** aus Modul \`${moduleName}\`.`));
|
||||
}
|
||||
|
||||
// 3. Hover für Built-Ins
|
||||
if (word.startsWith('log.')) { return new vscode.Hover(new vscode.MarkdownString(`TPL Logging-Modul`)); }
|
||||
if (word.startsWith('discovery.')) { return new vscode.Hover(new vscode.MarkdownString(`TPL Discovery-Modul`)); }
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -9,10 +9,10 @@ export class MetadataFix implements TplQuickFix {
|
|||
fix.edit = new vscode.WorkspaceEdit();
|
||||
|
||||
const config = vscode.workspace.getConfiguration('tpl');
|
||||
const author = config.get<string>('metadata.author', 'default');
|
||||
const author = config.get<string>('metadata.author', 'Kuro');
|
||||
const origin = config.get<string>('metadata.origin', 'Custom');
|
||||
const publisher = config.get<string>('metadata.publisher', 'IT-Operations');
|
||||
const treePathBase = config.get<string>('metadata.treePathBase', 'default');
|
||||
const treePathBase = config.get<string>('metadata.treePathBase', 'Grafing');
|
||||
|
||||
const missingFieldsStr = diagnostic.message.replace('Fehlende Pflicht-Metadaten (Modul-Ebene): ', '');
|
||||
const missingFields = missingFieldsStr.split(', ');
|
||||
|
|
@ -24,6 +24,7 @@ export class MetadataFix implements TplQuickFix {
|
|||
if (missingFields.includes('publishers')) insertText += ` publishers := '${publisher}';\n`;
|
||||
if (missingFields.includes('tree_path')) insertText += ` tree_path := '${treePathBase}', 'TODO_Category', 'TODO_Subcategory';\n`;
|
||||
|
||||
// HIER GEFIXT: Einfügen auf Zeile + 1, damit es IN den Block rutscht
|
||||
fix.edit.insert(document.uri, new vscode.Position(diagnostic.range.start.line + 1, 0), insertText);
|
||||
fix.diagnostics = [diagnostic];
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export class BuiltInCompletion implements CompletionStrategy {
|
|||
const items: vscode.CompletionItem[] = [];
|
||||
|
||||
// Prüft, ob wir im Namespace "log." oder "discovery." sind (auch wenn man danach weiter tippt!)
|
||||
const match = linePrefix.match(/\b(discovery|log|model|text|regex|time|xpath|json|hash|inference)\.([a-zA-Z0-9_]*)$/);
|
||||
const match = linePrefix.match(/\b(log|discovery)\.([a-zA-Z0-9_]*)$/);
|
||||
|
||||
if (match) {
|
||||
const namespace = match[1];
|
||||
|
|
|
|||
|
|
@ -1,32 +1,29 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { CompletionStrategy } from './CompletionStrategy';
|
||||
import { tplBuiltIns } from '../../data/TplBuiltIns';
|
||||
|
||||
export class LocalContextCompletion implements CompletionStrategy {
|
||||
public async provideCompletions(document: vscode.TextDocument, position: vscode.Position, linePrefix: string): Promise<vscode.CompletionItem[] | undefined> {
|
||||
const text = document.getText();
|
||||
const items: vscode.CompletionItem[] = [];
|
||||
|
||||
const dotMatch = linePrefix.match(/\b([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]*)$/);
|
||||
|
||||
// 1. WENN DER USER NACH EINEM PUNKT TIPPT (Cross-File Lookup)
|
||||
const dotMatch = linePrefix.match(/\b([a-zA-Z0-9_]+)\.$/);
|
||||
if (dotMatch) {
|
||||
const prefix = dotMatch[1];
|
||||
|
||||
const bmcNamespaces = Object.keys(tplBuiltIns);
|
||||
if (bmcNamespaces.includes(prefix)) {
|
||||
return undefined; // Überlässt das Feld komplett der BuiltInCompletion!
|
||||
}
|
||||
const prefix = dotMatch[1]; // z.B. "modelling"
|
||||
if (prefix === 'log' || prefix === 'discovery') return undefined; // Built-ins ignorieren
|
||||
|
||||
// Prüfen, ob der Präfix importiert wurde
|
||||
const importRegex = new RegExp(`from\\s+([a-zA-Z0-9_.]+)\\s+import\\s+${prefix}\\b`);
|
||||
const match = importRegex.exec(text);
|
||||
if (match) {
|
||||
const moduleName = match[1];
|
||||
const moduleName = match[1]; // z.B. "Company.software_modelling"
|
||||
const externalItems = await this.resolveImportedMethods(moduleName, prefix);
|
||||
return externalItems;
|
||||
return externalItems; // Nur externe Methoden zurückgeben
|
||||
}
|
||||
return undefined;
|
||||
return undefined; // Unbekannter Namespace
|
||||
}
|
||||
|
||||
// 2. LOKALE DEFINITIONS & IMPORTS PARSEN
|
||||
const lines = text.split('\n');
|
||||
let currentDefBlock = "";
|
||||
|
||||
|
|
@ -47,6 +44,7 @@ export class LocalContextCompletion implements CompletionStrategy {
|
|||
}
|
||||
}
|
||||
|
||||
// Imports für die globale Worterkennung bereitstellen
|
||||
const fromImportRegex = /from\s+([a-zA-Z0-9_.]+)\s+import\s+([a-zA-Z0-9_]+)/g;
|
||||
let matchVar;
|
||||
while ((matchVar = fromImportRegex.exec(text)) !== null) {
|
||||
|
|
@ -67,16 +65,20 @@ export class LocalContextCompletion implements CompletionStrategy {
|
|||
return items;
|
||||
}
|
||||
|
||||
// Hilfsmethode: Sucht im Workspace nach der anderen Datei!
|
||||
private async resolveImportedMethods(moduleName: string, defBlockName: string): Promise<vscode.CompletionItem[]> {
|
||||
const items: vscode.CompletionItem[] = [];
|
||||
// Sucht alle .tpl Dateien im Projekt
|
||||
const files = await vscode.workspace.findFiles('**/*.tpl', '**/node_modules/**');
|
||||
|
||||
for (const file of files) {
|
||||
const doc = await vscode.workspace.openTextDocument(file);
|
||||
const text = doc.getText();
|
||||
|
||||
// Ist das das gesuchte Modul? (Punkte escapen)
|
||||
const moduleRegex = new RegExp(`tpl\\s+1\\.\\d+\\s+module\\s+${moduleName.replace(/\./g, '\\.')}\\s*;`);
|
||||
if (moduleRegex.test(text)) {
|
||||
// Modul gefunden! Jetzt die Defines im entsprechenden Block auslesen
|
||||
const lines = text.split('\n');
|
||||
let inTargetBlock = false;
|
||||
for (const line of lines) {
|
||||
|
|
@ -94,7 +96,7 @@ export class LocalContextCompletion implements CompletionStrategy {
|
|||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
break; // Datei gefunden und geparst, wir können abbrechen
|
||||
}
|
||||
}
|
||||
return items;
|
||||
|
|
|
|||
|
|
@ -2,144 +2,92 @@ import * as vscode from 'vscode';
|
|||
import { CompletionStrategy } from './CompletionStrategy';
|
||||
|
||||
export class SnippetCompletion implements CompletionStrategy {
|
||||
public async provideCompletions(document: vscode.TextDocument, position: vscode.Position, linePrefix: string): Promise<vscode.CompletionItem[] | undefined> {
|
||||
// Bei Modul-Aufrufen (Punkt) keine Snippets feuern
|
||||
|
||||
public provideCompletions(document: vscode.TextDocument, position: vscode.Position, linePrefix: string): vscode.CompletionItem[] | undefined {
|
||||
if (linePrefix.match(/\.[a-zA-Z0-9_]*$/)) { return undefined; }
|
||||
|
||||
const items: vscode.CompletionItem[] = [];
|
||||
|
||||
// 1. Kontext herausfinden (Sind wir in einem Pattern oder Syncmapping?)
|
||||
const textBeforeCursor = document.getText(new vscode.Range(new vscode.Position(0, 0), position));
|
||||
const inPattern = textBeforeCursor.lastIndexOf('pattern ') > textBeforeCursor.lastIndexOf('end pattern;');
|
||||
const inSyncmapping = textBeforeCursor.lastIndexOf('syncmapping ') > textBeforeCursor.lastIndexOf('end syncmapping;');
|
||||
const inBlock = inPattern || inSyncmapping;
|
||||
|
||||
// 2. Settings laden
|
||||
const config = vscode.workspace.getConfiguration('tpl');
|
||||
const author = config.get<string>('metadata.author', 'Kuro');
|
||||
const origin = config.get<string>('metadata.origin', 'Custom');
|
||||
const publisher = config.get<string>('metadata.publisher', 'IT-Operations');
|
||||
const version = config.get<string>('version', '23');
|
||||
|
||||
// Dynamischer Tree-Path
|
||||
const minLevels = config.get<number>('metadata.treePath.minLevels', 3);
|
||||
const staticNodes = config.get<string[]>('metadata.treePath.staticNodes', ['Grafing']);
|
||||
// Kontext-Scanner: Wo befindet sich der Cursor gerade?
|
||||
let inBlock: 'none' | 'pattern' | 'definitions' = 'none';
|
||||
let inPhase = '';
|
||||
|
||||
let treePathStr = '';
|
||||
let snippetTabIdx = 1;
|
||||
for (let i = 0; i <= position.line; i++) {
|
||||
const text = document.lineAt(i).text.trim();
|
||||
if (text.match(/^(pattern|syncmapping)\b/)) { inBlock = 'pattern'; inPhase = ''; }
|
||||
else if (text.match(/^definitions?\b/)) { inBlock = 'definitions'; inPhase = ''; }
|
||||
else if (text.match(/^end (pattern|syncmapping|definitions?)\b/)) { inBlock = 'none'; inPhase = ''; }
|
||||
|
||||
for (let i = 0; i < minLevels; i++) {
|
||||
if (i < staticNodes.length) {
|
||||
treePathStr += `'${staticNodes[i]}'`;
|
||||
} else {
|
||||
treePathStr += `'\${${snippetTabIdx}:Level_${i+1}}'`;
|
||||
snippetTabIdx++;
|
||||
if (inBlock === 'pattern') {
|
||||
['metadata', 'overview', 'constants', 'triggers', 'mapping', 'removal', 'body'].forEach(p => {
|
||||
if (text.startsWith(p)) { inPhase = p; }
|
||||
else if (text.startsWith(`end ${p}`)) { inPhase = ''; }
|
||||
});
|
||||
}
|
||||
if (i < minLevels - 1) treePathStr += ', ';
|
||||
if (inBlock === 'definitions') {
|
||||
if (text.startsWith('define ')) { inPhase = 'define'; }
|
||||
else if (text.startsWith('end define')) { inPhase = ''; }
|
||||
}
|
||||
|
||||
// Dynamischer Metadata-String
|
||||
let metadataBlock = 'metadata\n';
|
||||
if (config.get<boolean>('metadata.require.origin', true)) metadataBlock += ` origin := '${origin}';\n`;
|
||||
if (config.get<boolean>('metadata.require.treePath', true)) metadataBlock += ` tree_path := ${treePathStr};\n`;
|
||||
if (config.get<boolean>('metadata.require.products', true)) metadataBlock += ` products := '\${${snippetTabIdx++}:Product}';\n`;
|
||||
if (config.get<boolean>('metadata.require.publishers', true)) metadataBlock += ` publishers := '${publisher}';\n`;
|
||||
if (config.get<boolean>('metadata.require.author', true)) metadataBlock += ` author := '${author}';\n`;
|
||||
metadataBlock += 'end metadata;';
|
||||
|
||||
// ==========================================
|
||||
// SNIPPETS FÜR DIE MODUL-EBENE (Außerhalb)
|
||||
// ==========================================
|
||||
if (!inBlock) {
|
||||
const skeleton = new vscode.CompletionItem('tpl_skeleton', vscode.CompletionItemKind.Snippet);
|
||||
skeleton.detail = "Generiert ein frisches TPL Modul";
|
||||
skeleton.insertText = new vscode.SnippetString(
|
||||
`tpl 1.\${1:${version}} module \${2:MyModule};\n\n` +
|
||||
`metadata\n` +
|
||||
` origin := '${origin}';\n` +
|
||||
` tree_path := '${treePathBase}', '\${3:Category}', '\${4:Subcategory}';\n` +
|
||||
` products := '\${5:Product}';\n` +
|
||||
` publishers := '${publisher}';\n` +
|
||||
` author := '${author}';\n` +
|
||||
`end metadata;\n\n` +
|
||||
`pattern \${6:MyPattern} 1.0\n` +
|
||||
` """\n \${7:Beschreibung des Patterns}\n """\n` +
|
||||
` metadata\n` +
|
||||
` publishers := '${publisher}';\n` +
|
||||
` tags := '\${8:Tags}';\n` +
|
||||
` end metadata;\n\n` +
|
||||
` overview\n` +
|
||||
` tags \${9:Tags};\n` +
|
||||
` end overview;\n\n` +
|
||||
` constants\n` +
|
||||
` \${10:const_name} := '\${11:value}';\n` +
|
||||
` end constants;\n\n` +
|
||||
` triggers\n` +
|
||||
` on process := SoftwareInstance created, confirmed where type = '\${12:Type}';\n` +
|
||||
` end triggers;\n\n` +
|
||||
` body\n` +
|
||||
` log.debug("Start pattern %pattern_name%");\n` +
|
||||
` $0\n` +
|
||||
` end body;\n` +
|
||||
`end pattern;`
|
||||
);
|
||||
items.push(skeleton);
|
||||
|
||||
const modMeta = new vscode.CompletionItem('metadata', vscode.CompletionItemKind.Snippet);
|
||||
modMeta.detail = "Modul-Metadaten Block (Pflichtfelder)";
|
||||
modMeta.insertText = new vscode.SnippetString(
|
||||
`metadata\n` +
|
||||
` origin := '${origin}';\n` +
|
||||
` tree_path := '${treePathBase}', '\${1:Category}', '\${2:Subcategory}';\n` +
|
||||
` products := '\${3:Product}';\n` +
|
||||
` publishers := '${publisher}';\n` +
|
||||
` author := '${author}';\n` +
|
||||
`end metadata;\n$0`
|
||||
);
|
||||
items.push(modMeta);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// SNIPPETS FÜR DIE PATTERN-EBENE (Innerhalb)
|
||||
// 1. GLOBAL (Top-Level: Vor dem Pattern / Import-Bereich)
|
||||
// ==========================================
|
||||
if (inBlock) {
|
||||
const patMeta = new vscode.CompletionItem('metadata', vscode.CompletionItemKind.Snippet);
|
||||
patMeta.detail = "Pattern-Metadaten Block";
|
||||
patMeta.insertText = new vscode.SnippetString(
|
||||
`metadata\n` +
|
||||
` publishers := '${publisher}';\n` +
|
||||
` tags := '\${1:Tags}';\n` +
|
||||
`end metadata;\n$0`
|
||||
);
|
||||
items.push(patMeta);
|
||||
if (inBlock === 'none') {
|
||||
const skel = new vscode.CompletionItem('tpl_skeleton', vscode.CompletionItemKind.Snippet);
|
||||
skel.insertText = new vscode.SnippetString(`tpl 1.23 module \${1:Name};\n\nmetadata\n origin := '\${2:Custom}';\n tree_path := 'Grafing', '\${3:Category}', '\${4:SubCategory}';\nend metadata;\n\npattern \${5:PatternName} 1.0\n """\n \${6:Beschreibung}\n """\n overview\n tags \${7:tag1};\n end overview;\n\n triggers\n on host := Host created, confirmed;\n end triggers;\n\n body\n log.debug("Start pattern \${5:PatternName}");\n $0\n end body;\nend pattern;`);
|
||||
items.push(skel);
|
||||
|
||||
['pattern', 'syncmapping', 'definitions'].forEach(block => {
|
||||
const item = new vscode.CompletionItem(block, vscode.CompletionItemKind.Snippet);
|
||||
item.insertText = new vscode.SnippetString(`${block} \${1:Name} \${2:1.0}\n """\n \${3:DocString}\n """\n $0\nend ${block};`);
|
||||
items.push(item);
|
||||
});
|
||||
|
||||
const imp = new vscode.CompletionItem('import', vscode.CompletionItemKind.Snippet);
|
||||
imp.insertText = new vscode.SnippetString('import ${1:module_name};');
|
||||
items.push(imp);
|
||||
|
||||
const fromImp = new vscode.CompletionItem('from import', vscode.CompletionItemKind.Snippet);
|
||||
fromImp.insertText = new vscode.SnippetString('from ${1:module_name} import${2:function_name};');
|
||||
items.push(fromImp);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// ALLGEMEINE SNIPPETS (Überall gültig)
|
||||
// 2. INNERHALB PATTERN (aber VOR dem Body)
|
||||
// ==========================================
|
||||
if (inBlock === 'pattern' && inPhase === '') {
|
||||
['metadata', 'overview', 'constants', 'triggers', 'mapping', 'removal', 'body'].forEach(sec => {
|
||||
const item = new vscode.CompletionItem(sec, vscode.CompletionItemKind.Snippet);
|
||||
item.insertText = new vscode.SnippetString(`${sec}\n $0\nend ${sec};`);
|
||||
items.push(item);
|
||||
});
|
||||
}
|
||||
|
||||
if (inBlock === 'definitions' && inPhase === '') {
|
||||
const item = new vscode.CompletionItem('define', vscode.CompletionItemKind.Snippet);
|
||||
item.insertText = new vscode.SnippetString(`define \${1:methodName}(\${2:params})\n """\n \${3:DocString}\n """\n $0\nend define;`);
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 3. INNERHALB VON BODY ODER DEFINE (Ausführbarer Code)
|
||||
// ==========================================
|
||||
if (inPhase === 'body' || inPhase === 'define') {
|
||||
['discovery', 'log', 'model', 'search', 'text', 'regex', 'time', 'hash', 'number', 'inference'].forEach(ns => {
|
||||
const item = new vscode.CompletionItem(ns, vscode.CompletionItemKind.Module);
|
||||
items.push(item);
|
||||
});
|
||||
|
||||
const ifSnippet = new vscode.CompletionItem('if', vscode.CompletionItemKind.Snippet);
|
||||
ifSnippet.insertText = new vscode.SnippetString(`if \${1:condition} then\n $0\nend if;`);
|
||||
ifSnippet.insertText = new vscode.SnippetString('if ${1:condition} then\n $0\nend if;');
|
||||
items.push(ifSnippet);
|
||||
|
||||
const elifSnippet = new vscode.CompletionItem('elif', vscode.CompletionItemKind.Snippet);
|
||||
elifSnippet.insertText = new vscode.SnippetString(`elif \${1:condition} then\n $0`);
|
||||
items.push(elifSnippet);
|
||||
|
||||
const elseSnippet = new vscode.CompletionItem('else', vscode.CompletionItemKind.Snippet);
|
||||
elseSnippet.insertText = new vscode.SnippetString(`else\n $0`);
|
||||
items.push(elseSnippet);
|
||||
|
||||
const forSnippet = new vscode.CompletionItem('for', vscode.CompletionItemKind.Snippet);
|
||||
forSnippet.insertText = new vscode.SnippetString(`for \${1:item} in \${2:list} do\n $0\nend for;`);
|
||||
forSnippet.insertText = new vscode.SnippetString('for ${1:item} in${2:list} do\n $0\nend for;');
|
||||
items.push(forSnippet);
|
||||
|
||||
const whileSnippet = new vscode.CompletionItem('while', vscode.CompletionItemKind.Snippet);
|
||||
whileSnippet.insertText = new vscode.SnippetString(`while \${1:condition} do\n $0\nend while;`);
|
||||
items.push(whileSnippet);
|
||||
|
||||
const defineSnippet = new vscode.CompletionItem('define', vscode.CompletionItemKind.Snippet);
|
||||
defineSnippet.insertText = new vscode.SnippetString(`define \${1:name}(\${2:params})\n """\n \${3:DocString}\n """\n $0\nend define;`);
|
||||
items.push(defineSnippet);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue