feat: add 'Go to App' command and enhance YAML app parsing
This commit is contained in:
@@ -51,6 +51,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"commands": [
|
"commands": [
|
||||||
|
{
|
||||||
|
"command": "appdaemon.goToApp",
|
||||||
|
"title": "AppDaemon: Go to App",
|
||||||
|
"icon": "$(symbol-class)"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "appdaemon.restartCurrentFileApps",
|
"command": "appdaemon.restartCurrentFileApps",
|
||||||
"title": "AppDaemon: Restart Apps in Current File",
|
"title": "AppDaemon: Restart Apps in Current File",
|
||||||
@@ -85,6 +90,13 @@
|
|||||||
"command": "appdaemon.clearErrors",
|
"command": "appdaemon.clearErrors",
|
||||||
"title": "AppDaemon: Clear Error Diagnostics"
|
"title": "AppDaemon: Clear Error Diagnostics"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"keybindings": [
|
||||||
|
{
|
||||||
|
"command": "appdaemon.goToApp",
|
||||||
|
"key": "ctrl+shift+a",
|
||||||
|
"mac": "cmd+shift+a"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -17,28 +17,101 @@ const NON_APP_KEYS = new Set([
|
|||||||
'appdaemon', 'http', 'hadashboard', 'admin', 'api', 'plugins'
|
'appdaemon', 'http', 'hadashboard', 'admin', 'api', 'plugins'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
// ── App parsing helpers ───────────────────────────────────────────────────────
|
||||||
* Parse AppDaemon app names from a YAML document.
|
|
||||||
* Returns only top-level keys whose block contains a `module:` line.
|
type AppEntry = { appName: string; line: number; moduleName: string };
|
||||||
*/
|
|
||||||
function parseAppsFromDocument(doc: vscode.TextDocument): string[] {
|
function parseAppEntries(text: string): AppEntry[] {
|
||||||
if (doc.languageId !== 'yaml') { return []; }
|
const lines = text.split('\n');
|
||||||
const lines = doc.getText().split('\n');
|
const entries: AppEntry[] = [];
|
||||||
const apps: string[] = [];
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const m = lines[i].match(/^([a-z][a-z0-9_]*):\s*$/);
|
const m = lines[i].match(/^([a-z][a-z0-9_]*):\s*$/);
|
||||||
if (!m) { continue; }
|
if (!m) { continue; }
|
||||||
const key = m[1];
|
const key = m[1];
|
||||||
if (NON_APP_KEYS.has(key)) { continue; }
|
if (NON_APP_KEYS.has(key)) { continue; }
|
||||||
// Scan the indented block below for a `module:` key
|
let moduleName = '';
|
||||||
for (let j = i + 1; j < Math.min(i + 20, lines.length); j++) {
|
for (let j = i + 1; j < Math.min(i + 20, lines.length); j++) {
|
||||||
if (lines[j].match(/^\S/)) { break; } // end of block
|
if (lines[j].match(/^\S/)) { break; }
|
||||||
if (lines[j].match(/^\s+module\s*:/)) { apps.push(key); break; }
|
const mod = lines[j].match(/^\s+module\s*:\s*(\S+)/);
|
||||||
|
if (mod) { moduleName = mod[1]; entries.push({ appName: key, line: i, moduleName }); break; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return apps;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse AppDaemon app names from a YAML document.
|
||||||
|
*/
|
||||||
|
function parseAppsFromDocument(doc: vscode.TextDocument): string[] {
|
||||||
|
if (doc.languageId !== 'yaml') { return []; }
|
||||||
|
return parseAppEntries(doc.getText()).map(e => e.appName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan all YAML files in the workspace and return app definitions with location.
|
||||||
|
*/
|
||||||
|
async function findAllApps(): Promise<Array<{
|
||||||
|
appName: string;
|
||||||
|
uri: vscode.Uri;
|
||||||
|
line: number;
|
||||||
|
fileName: string;
|
||||||
|
moduleName: string;
|
||||||
|
}>> {
|
||||||
|
const yamlFiles = await vscode.workspace.findFiles('**/*.yaml', '**/node_modules/**');
|
||||||
|
const results: Array<{ appName: string; uri: vscode.Uri; line: number; fileName: string; moduleName: string }> = [];
|
||||||
|
|
||||||
|
for (const uri of yamlFiles) {
|
||||||
|
let doc: vscode.TextDocument;
|
||||||
|
try {
|
||||||
|
doc = await vscode.workspace.openTextDocument(uri);
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const fileName = vscode.workspace.asRelativePath(uri);
|
||||||
|
for (const entry of parseAppEntries(doc.getText())) {
|
||||||
|
results.push({ ...entry, uri, fileName });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Symbol Providers ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class ADDocumentSymbolProvider implements vscode.DocumentSymbolProvider {
|
||||||
|
provideDocumentSymbols(doc: vscode.TextDocument): vscode.DocumentSymbol[] {
|
||||||
|
if (doc.languageId !== 'yaml') { return []; }
|
||||||
|
return parseAppEntries(doc.getText()).map(entry => {
|
||||||
|
const pos = new vscode.Position(entry.line, 0);
|
||||||
|
const range = new vscode.Range(pos, pos);
|
||||||
|
const sym = new vscode.DocumentSymbol(
|
||||||
|
entry.appName,
|
||||||
|
`module: ${entry.moduleName}`,
|
||||||
|
vscode.SymbolKind.Class,
|
||||||
|
range,
|
||||||
|
range
|
||||||
|
);
|
||||||
|
return sym;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ADWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider {
|
||||||
|
async provideWorkspaceSymbols(query: string): Promise<vscode.SymbolInformation[]> {
|
||||||
|
const apps = await findAllApps();
|
||||||
|
const lq = query.toLowerCase();
|
||||||
|
return apps
|
||||||
|
.filter(a => !lq || a.appName.toLowerCase().includes(lq))
|
||||||
|
.map(a => new vscode.SymbolInformation(
|
||||||
|
a.appName,
|
||||||
|
vscode.SymbolKind.Class,
|
||||||
|
`module: ${a.moduleName}`,
|
||||||
|
new vscode.Location(a.uri, new vscode.Position(a.line, 0))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function updateContextualButton(editor: vscode.TextEditor | undefined) {
|
function updateContextualButton(editor: vscode.TextEditor | undefined) {
|
||||||
if (!editor) { statusBar.updateContextualApps([]); return; }
|
if (!editor) { statusBar.updateContextualApps([]); return; }
|
||||||
const apps = parseAppsFromDocument(editor.document);
|
const apps = parseAppsFromDocument(editor.document);
|
||||||
@@ -102,12 +175,48 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
vscode.languages.registerHoverProvider(
|
vscode.languages.registerHoverProvider(
|
||||||
selector,
|
selector,
|
||||||
new EntityHoverProvider(haClient)
|
new EntityHoverProvider(haClient)
|
||||||
|
),
|
||||||
|
vscode.languages.registerDocumentSymbolProvider(
|
||||||
|
{ scheme: 'file', language: 'yaml' },
|
||||||
|
new ADDocumentSymbolProvider()
|
||||||
|
),
|
||||||
|
vscode.languages.registerWorkspaceSymbolProvider(
|
||||||
|
new ADWorkspaceSymbolProvider()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// ── Commands ─────────────────────────────────────────────────────────────
|
// ── Commands ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand('appdaemon.goToApp', async () => {
|
||||||
|
const apps = await findAllApps();
|
||||||
|
if (apps.length === 0) {
|
||||||
|
vscode.window.showWarningMessage('AppDaemon: No app definitions found in workspace');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppItem = vscode.QuickPickItem & { app: typeof apps[0] };
|
||||||
|
const items: AppItem[] = apps.map(app => ({
|
||||||
|
label: `$(symbol-class) ${app.appName}`,
|
||||||
|
description: app.fileName,
|
||||||
|
detail: `Line ${app.line + 1}`,
|
||||||
|
app
|
||||||
|
}));
|
||||||
|
|
||||||
|
const picked = await vscode.window.showQuickPick(items, {
|
||||||
|
placeHolder: 'Go to AppDaemon app…',
|
||||||
|
matchOnDescription: true,
|
||||||
|
matchOnDetail: false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!picked) { return; }
|
||||||
|
const doc = await vscode.workspace.openTextDocument(picked.app.uri);
|
||||||
|
const editor = await vscode.window.showTextDocument(doc);
|
||||||
|
const pos = new vscode.Position(picked.app.line, 0);
|
||||||
|
editor.selection = new vscode.Selection(pos, pos);
|
||||||
|
editor.revealRange(new vscode.Range(pos, pos), vscode.TextEditorRevealType.InCenter);
|
||||||
|
}),
|
||||||
|
|
||||||
vscode.commands.registerCommand('appdaemon.restartCurrentFileApps', async () => {
|
vscode.commands.registerCommand('appdaemon.restartCurrentFileApps', async () => {
|
||||||
const editor = vscode.window.activeTextEditor;
|
const editor = vscode.window.activeTextEditor;
|
||||||
if (!editor) {
|
if (!editor) {
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user