diff --git a/vscode-appdaemon/src/extension.ts b/vscode-appdaemon/src/extension.ts index 6d670b0..50684fb 100644 --- a/vscode-appdaemon/src/extension.ts +++ b/vscode-appdaemon/src/extension.ts @@ -244,7 +244,7 @@ class ADVirtualSensorDefinitionProvider implements vscode.DefinitionProvider { invalidate() { this.cache = undefined; } - private async getSensors(): Promise { + async getSensors(): Promise { const now = Date.now(); if (this.cache && now < this.cacheExpiry) { return this.cache; } this.cache = await findAllVirtualSensors(); @@ -282,6 +282,46 @@ class ADVirtualSensorDefinitionProvider implements vscode.DefinitionProvider { } } +class ADVirtualSensorHoverProvider implements vscode.HoverProvider { + constructor( + private haClient: HAClient, + private defProvider: ADVirtualSensorDefinitionProvider + ) {} + + async provideHover( + document: vscode.TextDocument, + position: vscode.Position + ): Promise { + // Find a virtual sensor whose definition is on this exact line + const sensors = await this.defProvider.getSensors(); + const match = sensors.find( + s => s.uri.fsPath === document.uri.fsPath && s.line === position.line + ); + if (!match) { return undefined; } + + const entity = this.haClient.getEntity(match.sensorId); + if (!entity) { return undefined; } + + const md = new vscode.MarkdownString(); + md.isTrusted = true; + const friendly = entity.attributes?.friendly_name as string | undefined; + md.appendMarkdown(`### ${friendly || entity.entity_id}\n\n`); + md.appendMarkdown(`| | |\n|---|---|\n`); + md.appendMarkdown(`| **Entity** | \`${entity.entity_id}\` |\n`); + md.appendMarkdown(`| **State** | **\`${entity.state}\`** |\n`); + if (entity.attributes) { + const skip = new Set(['friendly_name', 'icon', 'entity_picture', 'supported_features', 'supported_color_modes']); + for (const [key, value] of Object.entries(entity.attributes).filter(([k]) => !skip.has(k)).slice(0, 12)) { + const display = typeof value === 'object' ? JSON.stringify(value) : String(value); + const truncated = display.length > 60 ? display.substring(0, 57) + '...' : display; + md.appendMarkdown(`| ${key.replace(/\|/g, '\\|')} | \`${truncated.replace(/\|/g, '\\|')}\` |\n`); + } + } + md.appendMarkdown(`\n*Last changed: ${entity.last_changed}*`); + return new vscode.Hover(md); + } +} + // ───────────────────────────────────────────────────────────────────────────── function updateContextualButton(editor: vscode.TextEditor | undefined) { @@ -367,6 +407,10 @@ export async function activate(context: vscode.ExtensionContext) { vscode.languages.registerDefinitionProvider( { scheme: 'file', language: 'yaml' }, virtualSensorDefProvider + ), + vscode.languages.registerHoverProvider( + { scheme: 'file', language: 'yaml' }, + new ADVirtualSensorHoverProvider(haClient, virtualSensorDefProvider) ) ); diff --git a/vscode-appdaemon/vscode-appdaemon-0.1.0.vsix b/vscode-appdaemon/vscode-appdaemon-0.1.0.vsix index 99eb3b6..daa294b 100644 Binary files a/vscode-appdaemon/vscode-appdaemon-0.1.0.vsix and b/vscode-appdaemon/vscode-appdaemon-0.1.0.vsix differ