VS_CODE_TOOL_VIEW_PLAN

VS Code Tool Info Hover + Workflow Tools Tree + CodeLens — Plan

Date: 2026-04-21 Branch base: wf_tool_state Worktree: /Users/jxc755/projects/worktrees/galaxy-workflows-vscode/branch/wf_tool_state Companion doc: VS_CODE_ARCHITECTURE.md (same folder) — read first for the lay of the land.

Progress

Discoveries worth noting for Step 3

Goal

Expose already-cached parsed tool metadata (from @galaxy-tool-util/core) to the workflow author through three surfaces:

  1. Tool info hover/peek on tool_id — hover the tool_id value (both .ga and .gxwf.yml) shows tool name, description, version, license, EDAM ops/topics, xrefs, citations count, ToolShed link, truncated help.
  2. “Workflow Tools” tree view — VS Code TreeDataProvider in the Explorer listing steps of the active workflow with their tool metadata; click to reveal step, actions to open in ToolShed, flag stale/mismatched versions.
  3. CodeLens on tool_id — always-visible inline clickable annotation above each step’s tool_id line showing status + primary action (Open in ToolShed / Populate Tool Cache / retry).

Scope does not include ToolShed search or adding new steps — that is item 4, handled separately. Scope is only the two workflow formats (.ga, .gxwf.yml, .gxwf.yaml) — workflow-test files (-test.yml / -tests.yml) are excluded from all three surfaces.

Verified upstream facts

Context the implementer needs

What’s already wired (don’t rebuild)

Cross-cutting shared work (foundation for all three steps)

Graceful degradation phrasing

Match §19 of the architecture doc — same “run Populate Tool Cache” / “Could not resolve from ToolShed” phrasing reused across hover, tree tooltip, and CodeLens so the three surfaces read as one feature.

Step 1 — Tool info hover on tool_id ✅ DONE (commit 997bc95)

Server changes

  1. Shared helper server-common/src/providers/hover/toolIdHover.ts exporting buildToolIdHover(args: { nodeManager, offset, registry, toolShedBaseUrl? }): Promise<Hover | null>.
    • Detect cursor on a tool_id value: deepest node is a StringASTNode whose parent property key === "tool_id" AND path contains a "steps" segment. Works identically for .ga (step-dict) and .gxwf.yml (step-array) since ASTNodeManager is format-agnostic.
    • Walk up to the enclosing step ObjectASTNode and read sibling tool_version.
    • Call registry.getToolInfo(id, version); build markdown via buildToolInfoMarkdown.
    • Cache miss → “Tool not cached — run Populate Tool Cache”.
    • Resolution failed → “Could not resolve from ToolShed” with tool id.
    • Returns the hover with range = the tool_id string value range.
  2. Wire into both language services:
    • gx-workflow-ls-native/src/services/nativeHoverService.ts — branch before the existing findStateInPath check: if buildToolIdHover returns a hover, return early.
    • gx-workflow-ls-format2/src/services/hoverService.ts — same, but placed before schemaNodeResolver.resolveSchemaContext(location) so schema docs for the tool_id field don’t mask tool info.
  3. Version fallback matches existing behavior: missing tool_version → pass undefined → registry uses latest-cached.

Tests (red → green)

  1. Unit tests for parseToolShedRepoUrl (toolshed id → view URL; short id → null; malformed → null). Red. Implement. Green.
  2. Unit tests for buildToolInfoMarkdown (minimal tool, full tool, help truncation, missing optional fields, null description/version). Red. Implement. Green.
  3. Unit tests for ToolRegistryServiceImpl.getToolInfo: cached → ParsedTool; uncached → null; hasCached miss short-circuits without invoking the network accessor. Red. Implement. Green.
  4. Unit tests server-common/tests/unit/toolIdHover.test.ts — path detection: hover on tool_id value (native dict-of-steps, format2 array-of-steps), negative cases (key not value, unrelated strings, tool_id outside steps). Uses a mock registry. Red. Implement path detection. Green.
  5. Integration tests gx-workflow-ls-native/tests/integration/nativeToolIdHover.test.ts — mirrors nativeToolStateHover.test.ts. Mock registry returns ParsedTool with name/description/license/edam_operations. Assertions: cached → contains name, description, license; uncached → contains “Populate Tool Cache”; resolution-failed → contains “Could not resolve”; short tool id → no /view/ link. Red. Wire hover service + markdown body. Green.
  6. Integration tests gx-workflow-ls-format2/tests/integration/format2ToolIdHover.test.ts — same coverage plus a regression guard asserting the schema-fallback hover is not returned when the cursor is on tool_id. Red. Wire format2 hover service. Green.
  7. Integration tests use mock registries (same pattern as existing makeMockRegistry in nativeToolStateHover.test.ts). No filesystem cache or new fixtures needed.
  8. npm run test → commit: Add tool-info hover on tool_id for .ga and .gxwf.yml.

Step 2 — Workflow Tools tree view ✅ DONE (commit 1284d92)

Shared types (new in shared/src/requestsDefinitions.ts)

LSRequestIdentifiers.GET_WORKFLOW_TOOLS = "galaxy-workflows-ls.getWorkflowTools"

interface GetWorkflowToolsParams { uri: string }
interface WorkflowToolEntry {
  stepId: string;          // native: numeric-string key; format2: step.id or array index
  stepLabel?: string;      // from step.label / annotation / doc
  toolId: string;
  toolVersion?: string;
  cached: boolean;
  resolutionFailed: boolean;
  name?: string;           // populated when cached
  description?: string | null;
  toolshedUrl?: string;    // via parseToolShedRepoUrl
  range: Range;            // tool_id value range — used by reveal command
}
interface GetWorkflowToolsResult { tools: WorkflowToolEntry[] }

Server changes

  1. Extend server-common/src/services/toolCacheService.ts with a handler for GET_WORKFLOW_TOOLS. Implement extractStepSummariesFromDocument(doc): Array<{stepId, label?, toolId?, toolVersion?, range}> (step ordering preserved, with AST range for the tool_id node). For each step with a toolId, enrich via registry.getToolInfo + hasCached + hasResolutionFailed + parseToolShedRepoUrl. Preferred over a new WorkflowToolsService class because this file already owns step/tool extraction + ServiceBase registration.
  2. No changes to server-common/src/server.ts; ToolCacheService.register already wires all its requests.

Client changes

  1. New provider client/src/providers/workflowToolsTreeProvider.ts implementing vscode.TreeDataProvider<WorkflowToolItem>. Holds last-fetched WorkflowToolEntry[] keyed by active URI. On refresh(): if the active editor is a workflow language (galaxyworkflow | gxformat2), send GET_WORKFLOW_TOOLS; fire _onDidChangeTreeData.
  2. Tree item presentation: label = name || toolId; description = toolVersion; icon by state ($(check) cached, $(error) failed, $(info) uncached); tooltip = client-built markdown from entry fields (name + description + toolshed link) — no extra LSP round-trip.
  3. client/src/common/index.ts — register the provider via vscode.window.createTreeView("galaxyWorkflows.toolsView", { treeDataProvider }). Refresh triggers: onDidChangeActiveTextEditor, onDidSaveTextDocument, onDidChangeTextDocument debounced 500 ms, and TOOL_RESOLUTION_FAILED notifications (piggyback the existing subscription).
  4. New commands:
    • galaxy-workflows.refreshToolsView
    • galaxy-workflows.revealToolStep(entry) — uses entry.range: editor.revealRange, editor.selection = new Selection(range.start, range.start).
    • galaxy-workflows.openToolInToolShed(entry)vscode.env.openExternal(entry.toolshedUrl).
  5. package.json contributions:
    • contributes.views.explorer: { id: "galaxyWorkflows.toolsView", name: "Workflow Tools", when: "resourceExtname == .ga || resourceExtname == .gxwf.yml || resourceExtname == .gxwf.yaml" }. Explorer placement (not a dedicated activity-bar container) — the view is inherently tied to the active workflow editor, so living next to the file tree matches how it’s used.
    • contributes.viewsWelcome: “Open a Galaxy workflow to see its tools.”
    • contributes.commands: three new commands with Galaxy Workflows category.
    • contributes.menus.view/title: refresh command with icon.
    • contributes.menus.view/item/context: reveal + open in ToolShed scoped by viewItem == workflowTool.

Tests (red → green)

  1. Add GET_WORKFLOW_TOOLS types + identifier. Compile checkpoint.
  2. Server unit server-common/tests/unit/toolCacheService.workflowTools.test.ts: fixture workflow text → call handler → assert entries preserve step order and populate cached/resolutionFailed/name/toolshedUrl/range. Mock registry. Red. Implement extractor + handler. Green.
  3. Server integration gx-workflow-ls-native/tests/integration/workflowTools.test.ts on test-data/sample_workflow_1.ga with a mock registry. Red. Green.
  4. Server integration gx-workflow-ls-format2/tests/integration/workflowTools.test.ts on a format2 fixture in test-data/yaml/. Red. Green.
  5. Client unit client/tests/unit/workflowToolsTreeProvider.test.ts (Jest): mocked LSP client returning entries → getChildren produces ordered items; icon selection logic. Red. Implement provider + tree-item class. Green.
  6. Client E2E client/tests/e2e/suite/toolsView.e2e.ts: open workflow, wait for view, assert label text + count. Execute galaxy-workflows.revealToolStep and confirm active-editor selection moved. Uses existing cacheHelpers.ts + populateTestCache.ts to pre-seed a cached tool so name is populated; reuse a tool id already used by extension.ga.e2e.ts. Red. Wire contributions + refresh debounce + commands. Green.
  7. npm run test → commit: Add Workflow Tools tree view powered by GET_WORKFLOW_TOOLS.

Step 3 — CodeLens on tool_id 🔲 PENDING

Always-visible inline affordance. Uses the standard LSP textDocument/codeLens capability — no custom protocol.

What it shows

One CodeLens per tool step, anchored to the tool_id line. Title composes a state icon + tool name (fallback toolId) + version + one primary action word:

One CodeLens per step — secondary “Reveal in Workflow Tools view” stays in the right-click context menu to avoid visual noise.

Per-tool retry (POPULATE_TOOL_CACHE_FOR_TOOL)

The batch populateToolCache is the wrong hammer for a single flaky tool — it refetches every other tool in the document, which is noisy and slow when the user is reacting to one failed lens. Add a per-tool variant:

Server changes

  1. server-common/src/languageTypes.ts — add abstract doCodeLens(doc): Promise<CodeLens[]> on LanguageServiceBase with default []; advertise codeLensProvider: { resolveProvider: false } in initialize() capabilities.
  2. New server-common/src/providers/codeLensHandler.tsServerEventHandler subclass registering connection.onCodeLens. Looks up document context and delegates.
  3. server-common/src/server.ts — register CodeLensHandler in registerHandlers(); add capability.
  4. New shared builder server-common/src/providers/toolIdCodeLens.ts exporting buildToolIdCodeLenses(nodeManager, registry, toolShedBaseUrl): Promise<CodeLens[]>. Iterates nodeManager.getStepNodes(), extracts the tool_id string node + version, checks hasCached / hasResolutionFailed / getToolInfo, emits one CodeLens per step with range = tool_id value range, command chosen per state. Early-exit if no step has a tool_id.
  5. Override doCodeLens in both language services (gx-workflow-ls-native/src/languageService.ts, gx-workflow-ls-format2/src/languageService.ts) to call buildToolIdCodeLenses.

Client changes

Command ids referenced in the CodeLens must already be registered on the client:

No tree-provider-specific wiring; CodeLens is stateless per file.

Tests (red → green)

  1. Unit server-common/tests/unit/toolIdCodeLens.test.ts: cached toolshed → “Open in ToolShed” with openToolInToolShed; cached built-in (short id) → command-less lens with plain title $(check) name version; uncached → “Populate Tool Cache” with batch command; failed → retry with populateToolCacheForTool({toolId, toolVersion}) argument payload. Asserts range is the tool_id string value range. Red. Implement builder. Green.
  2. Unit server-common/tests/unit/toolCacheService.populateForTool.test.ts: handler delegates to registry.populateCache with a one-element array; clears markResolutionFailed on success. Red. Implement handler. Green.
  3. Integration gx-workflow-ls-native/tests/integration/toolIdCodeLens.test.ts + gx-workflow-ls-format2/tests/integration/toolIdCodeLens.test.ts: parse fixture workflow, call languageService.doCodeLens, assert one CodeLens per tool step with correct title + command (including command-less built-in case). Red. Wire doCodeLens override + CodeLensHandler. Green.
  4. Client E2E client/tests/e2e/suite/codeLens.e2e.ts: open workflow, request vscode.executeCodeLensProvider, assert lens count + title contains tool name; cached-built-in lens has no command. Red. Advertise capability. Green.
  5. npm run test + npm run test:e2e → commit: Add CodeLens on tool_id with per-tool retry.

Reuses Step 2 fixtures and mock registries; no new test data.

Cross-cutting decisions

Commit sequencing

  1. Step 1 — shared registry/markdown helpers + tool-info hover. One commit.
  2. Step 2 — GET_WORKFLOW_TOOLS + tree view + commands + client contributions. One commit.
  3. Step 3 — CodeLens on tool_id. One commit.

npm run test between each step. Don’t skip tests, don’t modify test data to make tests pass, and don’t git rebase.

Testing checklist (full plan)

Open questions (for the implementing agent to flag back, not resolve silently)

  1. Regression-sanity check: hover on tool_id key while the step also has a state: block — confirm findStateInPath does not match since tool_id is a sibling of state, not inside it. (Verified by inspection; flagging.)
  2. Tree tooltip: reuse server-side buildToolInfoMarkdown via an additional LSP request, or build a lighter client-side string from the already-returned entry fields? Recommend client-side (no second round-trip).
  3. Reveal command scope: support only top-level tool_id for v1? Format2 subworkflow steps embed another workflow — defer.
  4. Commit granularity: shared helpers + Step 1 in one commit, or split shared helpers into their own commit? Recommend bundled per step.
  5. CodeLens title style: include state icon glyph ($(check) / $(error) / $(info)) or keep title plain text? VS Code renders $(…) as theme icons inline — recommend use them.
  6. CodeLens perf on huge workflows: is early-exit + cache-only reads sufficient, or do we need an additional debounce in the server handler? Recommend start without extra debounce; measure if it becomes a problem.

Out of scope (explicit)