11import type { Location , Result , Run } from "sarif" ;
2- import type { WebviewPanel , TextEditorSelectionChangeEvent } from "vscode" ;
2+ import type {
3+ WebviewPanel ,
4+ TextEditorSelectionChangeEvent ,
5+ Range ,
6+ } from "vscode" ;
37import {
48 Diagnostic ,
59 DiagnosticRelatedInformation ,
@@ -18,6 +22,10 @@ import type {
1822 DatabaseManager ,
1923} from "../databases/local-databases" ;
2024import { DatabaseEventKind } from "../databases/local-databases" ;
25+ import {
26+ decodeSourceArchiveUri ,
27+ zipArchiveScheme ,
28+ } from "../common/vscode/archive-filesystem-provider" ;
2129import {
2230 asError ,
2331 assertNever ,
@@ -35,6 +43,7 @@ import type {
3543 InterpretedResultsSortState ,
3644 RawResultsSortState ,
3745 ParsedResultSets ,
46+ EditorSelection ,
3847} from "../common/interface-types" ;
3948import {
4049 SortDirection ,
@@ -197,6 +206,12 @@ export class ResultsView extends AbstractWebview<
197206 ) ,
198207 ) ;
199208
209+ this . disposableEventListeners . push (
210+ window . onDidChangeActiveTextEditor ( ( ) => {
211+ this . sendEditorSelectionToWebview ( ) ;
212+ } ) ,
213+ ) ;
214+
200215 this . disposableEventListeners . push (
201216 this . databaseManager . onDidChangeDatabaseItem ( ( { kind } ) => {
202217 if ( kind === DatabaseEventKind . Remove ) {
@@ -573,6 +588,9 @@ export class ResultsView extends AbstractWebview<
573588 queryName : this . labelProvider . getLabel ( fullQuery ) ,
574589 queryPath : fullQuery . initialInfo . queryPath ,
575590 } ) ;
591+
592+ // Send the current editor selection so the webview can apply filtering immediately
593+ this . sendEditorSelectionToWebview ( ) ;
576594 }
577595
578596 /**
@@ -1021,7 +1039,10 @@ export class ResultsView extends AbstractWebview<
10211039 }
10221040
10231041 private handleSelectionChange ( event : TextEditorSelectionChangeEvent ) : void {
1024- if ( event . kind === TextEditorSelectionChangeKind . Command ) {
1042+ const wasFromUserInteraction =
1043+ event . kind !== TextEditorSelectionChangeKind . Command ;
1044+ this . sendEditorSelectionToWebview ( wasFromUserInteraction ) ;
1045+ if ( ! wasFromUserInteraction ) {
10251046 return ; // Ignore selection events we caused ourselves.
10261047 }
10271048 const editor = window . activeTextEditor ;
@@ -1031,6 +1052,77 @@ export class ResultsView extends AbstractWebview<
10311052 }
10321053 }
10331054
1055+ /**
1056+ * Sends the current editor selection to the webview so it can filter results.
1057+ * Does not send when there is no active text editor (e.g. when the webview
1058+ * gains focus), so the webview retains the last known selection.
1059+ */
1060+ private sendEditorSelectionToWebview ( wasFromUserInteraction = false ) : void {
1061+ if ( ! this . isShowingPanel ) {
1062+ return ;
1063+ }
1064+ const selection = this . computeEditorSelection ( ) ;
1065+ if ( selection === undefined ) {
1066+ return ;
1067+ }
1068+ void this . postMessage ( {
1069+ t : "setEditorSelection" ,
1070+ selection,
1071+ wasFromUserInteraction,
1072+ } ) ;
1073+ }
1074+
1075+ /**
1076+ * Computes the current editor selection in a format compatible with result locations.
1077+ */
1078+ private computeEditorSelection ( ) : EditorSelection | undefined {
1079+ const editor = window . activeTextEditor ;
1080+ if ( ! editor ) {
1081+ return undefined ;
1082+ }
1083+
1084+ return this . rangeToEditorSelection ( editor . document . uri , editor . selection ) ;
1085+ }
1086+
1087+ private rangeToEditorSelection ( uri : Uri , range : Range ) {
1088+ const fileUri = this . getEditorFileUri ( uri ) ;
1089+ if ( fileUri == null ) {
1090+ return undefined ;
1091+ }
1092+ return {
1093+ fileUri,
1094+ // VS Code selections are 0-based; result locations are 1-based
1095+ startLine : range . start . line + 1 ,
1096+ endLine : range . end . line + 1 ,
1097+ startColumn : range . start . character + 1 ,
1098+ endColumn : range . end . character + 1 ,
1099+ isEmpty : range . isEmpty ,
1100+ } ;
1101+ }
1102+
1103+ /**
1104+ * Gets a file URI from the editor that can be compared with result location URIs.
1105+ *
1106+ * Result URIs (in BQRS and SARIF) use the original source file paths.
1107+ * For `file:` scheme editors, the URI already matches.
1108+ * For source archive editors, we extract the path within the archive,
1109+ * which corresponds to the original source file path.
1110+ */
1111+ private getEditorFileUri ( editorUri : Uri ) : string | undefined {
1112+ if ( editorUri . scheme === "file" ) {
1113+ return editorUri . toString ( ) ;
1114+ }
1115+ if ( editorUri . scheme === zipArchiveScheme ) {
1116+ try {
1117+ const { pathWithinSourceArchive } = decodeSourceArchiveUri ( editorUri ) ;
1118+ return `file://${ pathWithinSourceArchive } ` ;
1119+ } catch {
1120+ return undefined ;
1121+ }
1122+ }
1123+ return undefined ;
1124+ }
1125+
10341126 dispose ( ) {
10351127 super . dispose ( ) ;
10361128
0 commit comments