Skip to content

Add support for selection-based result filtering in result viewer#4362

Open
asgerf wants to merge 2 commits intogithub:mainfrom
asgerf:asgerf/filter-results-to-selection
Open

Add support for selection-based result filtering in result viewer#4362
asgerf wants to merge 2 commits intogithub:mainfrom
asgerf:asgerf/filter-results-to-selection

Conversation

@asgerf
Copy link
Copy Markdown
Contributor

@asgerf asgerf commented Apr 9, 2026

Adds support for selection-based result filtering via a checkbox in the result viewer.

  • When enabled, only results from the currently-viewed file are shown.
  • Additionally, if the editor selection is non-empty, only results within the selection range are shown.

A tuple matches the filter if any of its cells contain an entity whose location matches the filter.

selection-filter-cg.mp4

Implementation notes

The result viewer shows results in pages and the webview only has access to its current page. But if we simply apply the filtering on a page-by-page basis, the filtered results could end being scattered across thousands of mostly-empty pages.

The ideal might have been to support for filtering in codeql bqrs like we do for sorting, although that wouldn't work for interpreted results from SARIF files. This is the approach I took:

  • When the currently-viewed file changes, read the whole BQRS/SARIF in the extension and send the results for that file to the webview.
  • The webview then post-filters those to the current selection range (if the selection is non-empty).
  • There is no support for pagination, but the filtering will in practice reduce the number of results so they fit in the UI just fine.

The most complex part of the change is getting the extension <-> webview communication to work well and making sure we don't show stale results regardless of what order UI changes occur in.

The selectedTable state had to be lifted one component up in the hierarchy (from ResultTable to ResultApp) so it is available at the point where we request file-filtered results from the extension.

@asgerf asgerf force-pushed the asgerf/filter-results-to-selection branch from 24df5f9 to 86e3e25 Compare April 9, 2026 11:22
@asgerf asgerf changed the title Adds support for selection-based result filtering in result viewer Add support for selection-based result filtering in result viewer Apr 9, 2026
@asgerf asgerf force-pushed the asgerf/filter-results-to-selection branch from 195b0ac to 30ccbf3 Compare April 9, 2026 12:58
asgerf added 2 commits April 9, 2026 17:51
A new checkbox appears above the result viewer table. When checked, only
the results from the currently-viewed file are shown. Additionally, if
the selection range is non-empty, only results whose first line overlaps
within the selection range are shown.
@asgerf asgerf force-pushed the asgerf/filter-results-to-selection branch from 30ccbf3 to 2bca2f8 Compare April 9, 2026 15:58
@asgerf asgerf marked this pull request as ready for review April 10, 2026 08:42
@asgerf asgerf requested review from a team as code owners April 10, 2026 08:42
Copilot AI review requested due to automatic review settings April 10, 2026 08:42
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a selection-based filtering mode to the results viewer. When enabled, the results view is restricted to the currently viewed file (and further restricted to the current editor selection range when the selection is non-empty), with extension ↔ webview messaging to avoid stale/incorrect results.

Changes:

  • Introduces a “Filter results to current file or selection” checkbox and a dedicated empty-state message for selection-filtered views.
  • Lifts selectedTable state to ResultsApp and adds new messaging/state to request/receive file-filtered results from the extension.
  • Implements file/selection filtering helpers for raw rows and SARIF results, and updates UI to show filtered vs total counts and disable pagination while filtering.
Show a summary per file
File Description
extensions/ql-vscode/src/view/results/SelectionFilterNoResults.tsx New empty-state UI when selection filtering yields no matches.
extensions/ql-vscode/src/view/results/SelectionFilterCheckbox.tsx New checkbox control to toggle selection/file filtering.
extensions/ql-vscode/src/view/results/ResultTablesHeader.tsx Adds pagination-disable mode while selection filtering is active.
extensions/ql-vscode/src/view/results/ResultTables.tsx Wires checkbox state, disables pagination, computes filtered rows/results and filtered counts.
extensions/ql-vscode/src/view/results/ResultTable.tsx Applies filtered rows/results to child tables and shows a filter-specific no-results message.
extensions/ql-vscode/src/view/results/ResultsApp.tsx Lifts selected table state; requests/receives file-filtered results; tracks editor selection/filter state.
extensions/ql-vscode/src/view/results/ResultCount.tsx Displays filtered/total result counts when filtering is active.
extensions/ql-vscode/src/view/results/result-table-utils.ts Adds raw-row and SARIF filtering utilities and selection overlap logic.
extensions/ql-vscode/src/local-queries/results-view.ts Sends editor selection updates; handles file-filtered results requests (BQRS decode + SARIF filtering).
extensions/ql-vscode/src/databases/local-databases/locations.ts Returns revealed editor/location from jump helpers to support selection updates after navigation.
extensions/ql-vscode/src/common/sarif-utils.ts Adds helpers to normalize file URIs and extract all locations from SARIF results.
extensions/ql-vscode/src/common/interface-types.ts Adds shared types/messages for editor selection and file-filtered results.
extensions/ql-vscode/CHANGELOG.md Documents the new selection-based filtering feature.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (4)

extensions/ql-vscode/src/view/results/ResultTables.tsx:172

  • When selection filtering disables pagination, offset is still computed from the current pageNumber. If the user enables filtering while on a later page, row numbering (and any offset-based logic in RawTable) will start at a non-zero index even though the view is now showing an unpaged, file-filtered set. Consider using offset = 0 when pagination is disabled / selection filtering is active.
  const offset = parsedResultSets.pageNumber * parsedResultSets.pageSize;

extensions/ql-vscode/src/local-queries/results-view.ts:1204

  • loadFileFilteredResults decodes the entire result set in one bqrsDecode call using pageSize: schema.rows. For large result sets this can be extremely slow/memory-heavy even if only a small subset matches the file. Consider decoding incrementally using the pagination offsets/page size (and short-circuit once you’ve collected enough matches for the UI), rather than decoding all rows at once.
      if (schema && schema.rows > 0) {
        const resultsPath = query.completedQuery.getResultsPath(selectedTable);
        const chunk = await this.cliServer.bqrsDecode(
          resultsPath,
          schema.name,
          {
            offset: schema.pagination?.offsets[0],
            pageSize: schema.rows,
          },
        );

extensions/ql-vscode/src/local-queries/results-view.ts:1211

  • The conditional if (schema && schema.rows > 0) means tables with zero rows result in rawRows staying undefined, which can lead to the webview treating file-filtered results as perpetually “loading” (since no concrete empty result is returned). Consider setting rawRows to an empty array when the schema exists but has 0 rows (and/or always returning a FileFilteredResults object for the requested (fileUri, selectedTable), even if both arrays are empty).
      const schema = resultSetSchemas.find((s) => s.name === selectedTable);

      if (schema && schema.rows > 0) {
        const resultsPath = query.completedQuery.getResultsPath(selectedTable);
        const chunk = await this.cliServer.bqrsDecode(
          resultsPath,
          schema.name,
          {
            offset: schema.pagination?.offsets[0],
            pageSize: schema.rows,
          },
        );
        const resultSet = bqrsToResultSet(schema, chunk);
        rawRows = filterRowsByFileUri(resultSet.rows, normalizedFilterUri);
        if (rawRows.length > RAW_RESULTS_LIMIT) {
          rawRows = rawRows.slice(0, RAW_RESULTS_LIMIT);
        }
      }
    } catch (e) {

extensions/ql-vscode/src/local-queries/results-view.ts:1209

  • rawRows is sliced to RAW_RESULTS_LIMIT before being sent to the webview. This can silently drop matching rows (including ones inside the selection range) and also prevents RawTable from showing its existing "Too many results to show at once… omitted" message because it never sees the full length. Consider sending truncation metadata (or collecting up to the limit while still tracking omitted count) so the UI can indicate that results were truncated.
        const resultSet = bqrsToResultSet(schema, chunk);
        rawRows = filterRowsByFileUri(resultSet.rows, normalizedFilterUri);
        if (rawRows.length > RAW_RESULTS_LIMIT) {
          rawRows = rawRows.slice(0, RAW_RESULTS_LIMIT);
        }
  • Files reviewed: 13/13 changed files
  • Comments generated: 4

Comment on lines +279 to 293
case "setFileFilteredResults":
if (msg.results != null) {
const results = msg.results;
setState((prev) => {
if (
results.fileUri === prev.editorSelection?.fileUri &&
results.selectedTable === prev.selectedTable &&
prev.fileFilteredResults === undefined
) {
return { ...prev, fileFilteredResults: results };
}
return prev;
});
}
break;
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setFileFilteredResults messages with results: undefined are ignored. When the extension can’t produce file-filtered results (e.g. selected table has 0 rows, or the graph table), the UI will stay in the "loading filtered results" state and the request effect can repeatedly re-request results on every render. Handle the undefined case by marking the request as completed (e.g. store a sentinel like null/status flag, or store an empty FileFilteredResults for the requested (fileUri, selectedTable)).

Copilot uses AI. Check for mistakes.
Comment on lines +199 to +201
// True if file-filtered results are still loading from the extension
const isLoadingFilteredResults =
selectionFilter != null && fileFilteredResults == null;
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isLoadingFilteredResults is derived solely from selectionFilter != null && fileFilteredResults == null. For result types that don’t support file-filtering (e.g. graph interpretation), fileFilteredResults will never be set and the UI can get stuck showing "Loading filtered results…". Consider making the loading state conditional on the current resultSet actually being filterable (RawResultSet or SARIF interpreted), or ensure a completion message is always sent/handled.

Suggested change
// True if file-filtered results are still loading from the extension
const isLoadingFilteredResults =
selectionFilter != null && fileFilteredResults == null;
const isFilterableResultSet = resultSet?.t === "RawResultSet";
// True if file-filtered results are still loading from the extension
const isLoadingFilteredResults =
isFilterableResultSet &&
selectionFilter != null &&
fileFilteredResults == null;

Copilot uses AI. Check for mistakes.
private computeEditorSelection(): EditorSelection | undefined {
const editor = window.activeTextEditor;
if (!editor) {
void this.logger.log("No active editor");
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

computeEditorSelection logs "No active editor" every time it runs without an active editor. Since this can happen frequently (e.g. focusing the webview or switching tabs), this risks spamming extension logs. Consider removing this log, lowering its level, or only logging once/throttling.

This issue also appears in the following locations of the same file:

  • line 1193
  • line 1195
  • line 1205
Suggested change
void this.logger.log("No active editor");

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants