diff --git a/package.json b/package.json index 858dec0..6ddd7ef 100644 --- a/package.json +++ b/package.json @@ -128,6 +128,12 @@ "title": "View as Table", "category": "Data Explorer", "icon": "$(table)" + }, + { + "command": "dataExplorer.searchDataSources", + "title": "Search Data Source Entries", + "category": "Data Explorer", + "icon": "$(search)" } ], "menus": { @@ -152,8 +158,22 @@ "command": "dataExplorer.viewAsTable", "when": "resourceExtname == .sldd" } + ], + "view/title": [ + { + "command": "dataExplorer.searchDataSources", + "when": "view == dataExplorer.sections", + "group": "navigation@1" + } ] - } + }, + "keybindings": [ + { + "command": "dataExplorer.searchDataSources", + "key": "ctrl+alt+e", + "mac": "cmd+alt+e" + } + ] }, "scripts": { "build:webview": "vite build", diff --git a/src/extension.ts b/src/extension.ts index 68fbd0a..794cd36 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -8,8 +8,10 @@ import { BinarySlddEditorProvider } from './host/BinarySlddEditorProvider.js'; import { HealthDecorationProvider } from './host/HealthDecorationProvider.js'; import { invalidate, findNode } from './host/SlddModel.js'; import { isEditableJsonSlddBytes, exceedsTextSyncLimit, isZipBytes } from './host/slddFormat.js'; -import { handleNavigate } from './host/navigate.js'; +import { handleNavigate, requestSelect } from './host/navigate.js'; import { invalidateUsageGraph } from './host/usageGraph.js'; +import { searchDataSources } from './host/searchSources.js'; +import { listEntries, reindexFile, removeFile } from './host/nameIndex.js'; import { isSectionRowId } from './common/sectionRowId.js'; const SUPPORTED_RE = /\.(sldd|mat|slx|prj)$/; @@ -158,13 +160,24 @@ export function activate(context: vscode.ExtensionContext): void { piProvider, ), watcher, - // Files added/removed change the root list. - watcher.onDidCreate(refreshAll), - watcher.onDidDelete(refreshAll), + // Files added/removed change the root list. Also keep the name index in sync: + // reindex the new file / drop the removed file's bucket. Both index ops are + // no-ops until the index is first built (by the first search), so they're + // cheap when search has never been opened. + watcher.onDidCreate((uri) => { + void reindexFile(uri); + refreshAll(); + }), + watcher.onDidDelete((uri) => { + removeFile(uri.toString()); + refreshAll(); + }), // A file's contents changed: drop its cached model (table) and rebuild the - // reference index (tree), since edits may add or remove references. + // reference index (tree), since edits may add or remove references. Also + // reindex its entry names (no-op until the index is first built). watcher.onDidChange((uri) => { invalidate(uri.toString()); + void reindexFile(uri); refreshAll(); }), // Live edits in an open editor: invalidate the cached model and refresh. @@ -173,8 +186,11 @@ export function activate(context: vscode.ExtensionContext): void { invalidate(e.document.uri.toString()); } // A dirty-state transition on any supported file changes the "modified" - // health badge, so refresh decorations for supported docs. + // health badge, so refresh decorations for supported docs. Also re-sync + // the name index for live entry-name edits (e.g. renaming an entry in an + // open .sldd); reindexFile is a no-op until the index is first built. if (SUPPORTED_RE.test(e.document.uri.path)) { + void reindexFile(e.document.uri); refreshAll(); } }), @@ -222,6 +238,14 @@ export function activate(context: vscode.ExtensionContext): void { /* ignore */ } }), + // Global entry-name search overlay: pick an entry by name across all data + // sources, then open its source file and select the matching row. + vscode.commands.registerCommand('dataExplorer.searchDataSources', () => + searchDataSources(listEntries, async (sourceUri, entryName) => { + requestSelect(sourceUri, entryName); + await openInBestEditor(vscode.Uri.parse(sourceUri), { preview: true }); + }), + ), ); } diff --git a/src/host/BinaryEditorProvider.ts b/src/host/BinaryEditorProvider.ts index 89a44a7..22f0c0a 100644 --- a/src/host/BinaryEditorProvider.ts +++ b/src/host/BinaryEditorProvider.ts @@ -1,6 +1,6 @@ // Copyright 2026 The MathWorks, Inc. import * as vscode from 'vscode'; -import { renderWebviewHtml } from './webviewHtml.js'; +import { renderWebviewHtml, LOADING_OVERLAY_HTML } from './webviewHtml.js'; import { getModelFromBytes, getProjectModel, invalidate } from './SlddModel.js'; import { buildRows, @@ -14,7 +14,7 @@ import { buildMatRows } from './matRowBuilder.js'; import { readProjectStore } from './projectStore.js'; import { isEditableJsonSlddBytes, exceedsTextSyncLimit, exceedsStringDecodeLimit, isZipBytes } from './slddFormat.js'; import { annotateDataRows, annotateModelRows } from './usageGraph.js'; -import { wireNavigateSelect, consumePendingSelect } from './navigate.js'; +import { wireNavigateSelect, drainNavigateSelect } from './navigate.js'; import { basename } from '../common/pathUtil.js'; import { toArrayBuffer } from '../common/bytes.js'; import type { TableToHostMessage } from '../common/protocol.js'; @@ -157,12 +157,6 @@ export class BinaryEditorProvider implements vscode.CustomReadonlyEditorProvider return toArrayBuffer(await vscode.workspace.fs.readFile(document.uri)); }; - // If a cross-tab navigation targeted this file (e.g. it was just opened by a - // Usage-link click), select the requested row now that rows exist. - const drainNavSelect = (): void => { - const navName = consumePendingSelect(uriString); - if (navName) webview.postMessage({ type: 'selectByName', name: navName }); - }; // Read/parse the file host-side and push rows to the webview. On failure, // drop the cached model and post a banner. @@ -182,7 +176,7 @@ export class BinaryEditorProvider implements vscode.CustomReadonlyEditorProvider columnLabels: PROJECT_COLUMN_LABELS, editable: false, }); - drainNavSelect(); + drainNavigateSelect(webview, uriString); return; } @@ -211,7 +205,7 @@ export class BinaryEditorProvider implements vscode.CustomReadonlyEditorProvider editable: false, notice, }); - drainNavSelect(); + drainNavigateSelect(webview, uriString); } catch (err) { invalidate(uriString); webview.postMessage({ @@ -287,14 +281,10 @@ export class BinaryEditorProvider implements vscode.CustomReadonlyEditorProvider return renderWebviewHtml(webview, distRoot, { scriptFile: 'table.js', title: 'Data Explorer', - body: ` - + body: ` -
-
-
Loading…
-
+${LOADING_OVERLAY_HTML} `, }); } diff --git a/src/host/BinarySlddEditorProvider.ts b/src/host/BinarySlddEditorProvider.ts index 044fe2e..1be0814 100644 --- a/src/host/BinarySlddEditorProvider.ts +++ b/src/host/BinarySlddEditorProvider.ts @@ -18,7 +18,7 @@ // cached model of the same file. import * as vscode from 'vscode'; import { unzipSync, zipSync } from 'fflate'; -import { renderWebviewHtml } from './webviewHtml.js'; +import { renderWebviewHtml, LOADING_OVERLAY_HTML } from './webviewHtml.js'; import { buildRows, COLUMNS, COLUMN_LABELS, COLUMN_GROUPS, type ClipMark } from './rowBuilder.js'; import { sectionRules } from './sectionRules.js'; import { parseBinarySlddParts } from '../dex/datamodel/parser/BinarySlddParser.js'; @@ -49,6 +49,7 @@ import { deleteFromSource, } from './editorHub.js'; import { basename } from '../common/pathUtil.js'; +import { wireNavigateSelect, drainNavigateSelect } from './navigate.js'; import type { TableToHostMessage } from '../common/protocol.js'; // srcId prefix so the editable model never collides with the read-only @@ -185,6 +186,7 @@ export class BinarySlddEditorProvider implements vscode.CustomEditorProvider