bmc-tpl-code-all-around/src/TplHoverProvider.ts

123 lines
5.1 KiB
TypeScript
Raw Normal View History

import * as vscode from 'vscode';
export class TplHoverProvider implements vscode.HoverProvider {
async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.Hover | null> {
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;
}
}