Wire IDE symbol navigation panel
CI / python (push) Has been cancelled
CI / rust (push) Has been cancelled

This commit is contained in:
2026-05-16 20:48:31 +03:00
parent 1fe213c5ee
commit 523a756f5c
3 changed files with 89 additions and 48 deletions
@@ -106,7 +106,7 @@ const checks = [
{ {
name: "module mode", name: "module mode",
url: `${baseUrl}/editor?lang=ru&project=${projectId}&mode=module&routine=${routine}`, url: `${baseUrl}/editor?lang=ru&project=${projectId}&mode=module&routine=${routine}`,
mustInclude: ["data-ide-workspace", "data-left-navigation-panel", "data-right-context-inspector", "data-open-objects-bar", "data-open-document-pin", "data-open-document-close", "data-fallback-tree-search", "data-fallback-tree-filters", "Alt+1 Alt+2 Alt+3", "Редактор BSL", "Код модуля не загружен", "Выберите реальный модуль", "Основная конфигурация", "Расширение: <Имя>", "SFERA", "Среды"] mustInclude: ["data-ide-workspace", "data-active-mode=\"module\"", "data-left-navigation-panel", "data-right-context-inspector", "data-open-objects-bar", "data-open-document-pin", "data-open-document-close", "data-fallback-tree-search", "data-fallback-tree-filters", "data-fast-bsl-editor", "data-symbol-navigation-panel", "symbol-search-input", "Alt+1 Alt+2 Alt+3", "Основная конфигурация", "SFERA", "Среды"]
}, },
{ {
name: "form mode", name: "form mode",
@@ -186,16 +186,13 @@ try {
await restoredModuleTabs.first().click(); await restoredModuleTabs.first().click();
} }
await page.locator("[data-fast-bsl-editor]").waitFor({ state: "visible", timeout: 15000 }); await page.locator("[data-fast-bsl-editor]").waitFor({ state: "visible", timeout: 15000 });
const symbolNavigationPanel = page.locator("[data-symbol-navigation-panel]"); await page.locator("[data-symbol-navigation-panel]").waitFor({ state: "visible", timeout: 15000 });
if ((await symbolNavigationPanel.count()) > 0) {
await symbolNavigationPanel.waitFor({ state: "visible", timeout: 15000 });
}
const symbolSearchInput = page.locator("#symbol-search-input"); const symbolSearchInput = page.locator("#symbol-search-input");
if ((await symbolSearchInput.count()) > 0) { await symbolSearchInput.fill("demo");
await symbolSearchInput.fill("Проверить");
await symbolSearchInput.press("Enter"); await symbolSearchInput.press("Enter");
await page.locator("[data-symbol-result]").first().waitFor({ state: "visible", timeout: 15000 }); await page.locator("[data-symbol-result]").first().waitFor({ state: "visible", timeout: 15000 });
} await page.locator('button[data-editor-action="symbol-definition-row"]').first().click();
await page.locator("[data-symbol-definition]").waitFor({ state: "visible", timeout: 15000 });
const findUsagesButton = page.locator('button[data-editor-action="find-usages"]'); const findUsagesButton = page.locator('button[data-editor-action="find-usages"]');
if ((await findUsagesButton.count()) > 0 && await findUsagesButton.isEnabled()) { if ((await findUsagesButton.count()) > 0 && await findUsagesButton.isEnabled()) {
await findUsagesButton.click(); await findUsagesButton.click();
@@ -2255,6 +2255,19 @@ function EditorPanel({
value={monacoValue} value={monacoValue}
/> />
</div> </div>
<SymbolNavigationPanel
definition={symbolDefinition}
language={language}
message={symbolMessage}
onDefinition={handleDefinition}
onQueryChange={setSymbolQuery}
onReferences={handleReferences}
onSearch={handleSymbolSearch}
query={symbolQuery}
references={symbolReferences}
results={symbolResults}
state={symbolState}
/>
</section> </section>
); );
} }
@@ -2262,28 +2275,56 @@ function EditorPanel({
function SymbolNavigationPanel({ function SymbolNavigationPanel({
definition, definition,
language, language,
message,
onDefinition, onDefinition,
onQueryChange,
onReferences, onReferences,
onSearch,
query,
references, references,
results results,
state
}: Readonly<{ }: Readonly<{
definition: SymbolResult | null; definition: SymbolResult | null;
language: UiLanguage; language: UiLanguage;
message: string;
onDefinition: (lineageId?: string) => void; onDefinition: (lineageId?: string) => void;
onQueryChange: (value: string) => void;
onReferences: (lineageId?: string) => void; onReferences: (lineageId?: string) => void;
onSearch: () => void;
query: string;
references: SymbolReferences | null; references: SymbolReferences | null;
results: SymbolResult[]; results: SymbolResult[];
state: "idle" | "loading" | "error";
}>) { }>) {
const t = messages[language]; const t = messages[language];
const referenceRows = references?.references.slice(0, 8) ?? []; const referenceRows = references?.references.slice(0, 8) ?? [];
return ( return (
<div className="border-t border-border p-3" data-symbol-navigation-panel> <div className="max-h-56 shrink-0 overflow-auto border-t border-border bg-card p-3" data-symbol-navigation-panel>
<div className="flex items-center justify-between gap-2"> <div className="flex flex-wrap items-center gap-2">
<div className="text-xs font-semibold uppercase text-muted-foreground">{t.referencesPanel}</div> <label className="text-xs font-semibold uppercase text-muted-foreground" htmlFor="symbol-search-input">{t.referencesPanel}</label>
<Badge tone="neutral">{referenceRows.length}</Badge> <input
className="h-8 min-w-48 flex-1 border border-border bg-background px-2 text-xs outline-none focus:border-primary"
id="symbol-search-input"
onChange={(event) => onQueryChange(event.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter") {
event.preventDefault();
onSearch();
}
}}
placeholder={language === "ru" ? "Найти символ" : "Find symbol"}
value={query}
/>
<ActionButton actionId="symbol-search-panel" disabled={state === "loading"} label={state === "loading" ? t.loading : t.search} onClick={onSearch} />
<Badge tone={state === "error" ? "danger" : referenceRows.length > 0 ? "success" : "neutral"}>{referenceRows.length}</Badge>
</div> </div>
<div className="mt-2 space-y-2"> {message ? (
<div className={["mt-2 truncate text-xs", state === "error" ? "text-destructive" : "text-muted-foreground"].join(" ")} data-symbol-message>{message}</div>
) : null}
<div className="mt-3 grid gap-3 lg:grid-cols-[minmax(0,1fr)_minmax(280px,0.8fr)]">
<div className="min-w-0 space-y-2">
{results.length === 0 ? ( {results.length === 0 ? (
<div className="text-xs text-muted-foreground">{language === "ru" ? "Выполните поиск символа или откройте использования выбранного объекта." : "Search a symbol or open references for the selected object."}</div> <div className="text-xs text-muted-foreground">{language === "ru" ? "Выполните поиск символа или откройте использования выбранного объекта." : "Search a symbol or open references for the selected object."}</div>
) : ( ) : (
@@ -2303,14 +2344,15 @@ function SymbolNavigationPanel({
)) ))
)} )}
</div> </div>
<div className="min-w-0 space-y-2">
{definition ? ( {definition ? (
<div className="mt-3 border border-primary/30 bg-primary/5 p-2 text-xs" data-symbol-definition> <div className="border border-primary/30 bg-primary/5 p-2 text-xs" data-symbol-definition>
<div className="font-medium">{definition.node.qualified_name}</div> <div className="font-medium">{definition.node.qualified_name}</div>
<div className="mt-1 text-muted-foreground">{formatSourceLocation(definition.source, language)}</div> <div className="mt-1 text-muted-foreground">{formatSourceLocation(definition.source, language)}</div>
</div> </div>
) : null} ) : null}
{referenceRows.length > 0 ? ( {referenceRows.length > 0 ? (
<div className="mt-3 space-y-1" data-symbol-references> <div className="space-y-1" data-symbol-references>
{referenceRows.map((reference) => ( {referenceRows.map((reference) => (
<div className="border border-border bg-muted/30 p-2 text-xs" key={reference.edge_id}> <div className="border border-border bg-muted/30 p-2 text-xs" key={reference.edge_id}>
<div className="truncate font-medium">{reference.source?.qualified_name ?? reference.target?.qualified_name ?? reference.kind}</div> <div className="truncate font-medium">{reference.source?.qualified_name ?? reference.target?.qualified_name ?? reference.kind}</div>
@@ -2320,6 +2362,8 @@ function SymbolNavigationPanel({
</div> </div>
) : null} ) : null}
</div> </div>
</div>
</div>
); );
} }