Skip to content

Commit 88bfd19

Browse files
committed
Switch commands to up/down/left/right semantics
1 parent 125f638 commit 88bfd19

7 files changed

Lines changed: 168 additions & 155 deletions

File tree

extensions/ql-vscode/package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -595,19 +595,19 @@
595595
},
596596
{
597597
"command": "codeQLQueryResults.nextPathStep",
598-
"title": "CodeQL: Show Next Step on Path"
598+
"title": "CodeQL: Navigate down in result viewer"
599599
},
600600
{
601601
"command": "codeQLQueryResults.previousPathStep",
602-
"title": "CodeQL: Show Previous Step on Path"
602+
"title": "CodeQL: Navigate up in result viewer"
603603
},
604604
{
605-
"command": "codeQLQueryResults.nextAlert",
606-
"title": "CodeQL: Show Next Alert"
605+
"command": "codeQLQueryResults.right",
606+
"title": "CodeQL: Navigate right in result viewer"
607607
},
608608
{
609-
"command": "codeQLQueryResults.previousAlert",
610-
"title": "CodeQL: Show Previous Alert"
609+
"command": "codeQLQueryResults.left",
610+
"title": "CodeQL: Navigate left in result viewer"
611611
},
612612
{
613613
"command": "codeQL.restartQueryServer",

extensions/ql-vscode/src/interface.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
ALERTS_TABLE_NAME,
2828
GRAPH_TABLE_NAME,
2929
RawResultsSortState,
30+
NavigationDirection,
3031
} from './pure/interface-types';
3132
import { Logger } from './logging';
3233
import { commandRunner } from './commandRunner';
@@ -144,26 +145,26 @@ export class ResultsView extends AbstractWebview<IntoResultsViewMsg, FromResults
144145
void logger.log('Registering path-step navigation commands.');
145146
this.push(
146147
commandRunner(
147-
'codeQLQueryResults.nextPathStep',
148-
this.navigatePathStep.bind(this, 1)
148+
'codeQLQueryResults.nextPathStep', // TODO: deprecate the old commands and make them forward to new commands named 'up/down'
149+
this.navigateResultView.bind(this, NavigationDirection.down)
149150
)
150151
);
151152
this.push(
152153
commandRunner(
153154
'codeQLQueryResults.previousPathStep',
154-
this.navigatePathStep.bind(this, -1)
155+
this.navigateResultView.bind(this, NavigationDirection.up)
155156
)
156157
);
157158
this.push(
158159
commandRunner(
159-
'codeQLQueryResults.nextAlert',
160-
this.navigateAlert.bind(this, 1)
160+
'codeQLQueryResults.right',
161+
this.navigateResultView.bind(this, NavigationDirection.right)
161162
)
162163
);
163164
this.push(
164165
commandRunner(
165-
'codeQLQueryResults.previousAlert',
166-
this.navigateAlert.bind(this, -1)
166+
'codeQLQueryResults.left',
167+
this.navigateResultView.bind(this, NavigationDirection.left)
167168
)
168169
);
169170

@@ -181,12 +182,8 @@ export class ResultsView extends AbstractWebview<IntoResultsViewMsg, FromResults
181182
);
182183
}
183184

184-
async navigatePathStep(direction: number): Promise<void> {
185-
await this.postMessage({ t: 'navigatePath', direction });
186-
}
187-
188-
async navigateAlert(direction: number): Promise<void> {
189-
await this.postMessage({ t: 'navigateAlert', direction });
185+
async navigateResultView(direction: NavigationDirection): Promise<void> {
186+
await this.postMessage({ t: 'navigate', direction });
190187
}
191188

192189
protected getPanelConfig(): WebviewPanelConfig {

extensions/ql-vscode/src/pure/interface-types.ts

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -141,20 +141,17 @@ export interface ShowInterpretedPageMsg {
141141
queryPath: string;
142142
}
143143

144-
/** Advance to the next or previous path step in the path viewer */
145-
export interface NavigatePathMsg {
146-
t: 'navigatePath';
147-
148-
/** 1 for next, -1 for previous */
149-
direction: number;
144+
export const enum NavigationDirection {
145+
up = 'up',
146+
down = 'down',
147+
left = 'left',
148+
right = 'right',
150149
}
151150

152-
/** Advance to the next or previous alert in the path viewer */
153-
export interface NavigateAlertMsg {
154-
t: 'navigateAlert';
155-
156-
/** 1 for next, -1 for previous */
157-
direction: number;
151+
/** Move up, down, left, or right in the result viewer. */
152+
export interface NavigateMsg {
153+
t: 'navigate';
154+
direction: NavigationDirection;
158155
}
159156

160157
/**
@@ -172,8 +169,7 @@ export type IntoResultsViewMsg =
172169
| ResultsUpdatingMsg
173170
| SetStateMsg
174171
| ShowInterpretedPageMsg
175-
| NavigatePathMsg
176-
| NavigateAlertMsg
172+
| NavigateMsg
177173
| UntoggleShowProblemsMsg;
178174

179175
/**

extensions/ql-vscode/src/view/results/RawTableRow.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
import * as React from 'react';
22
import { ResultRow } from '../../pure/bqrs-cli-types';
3-
import { selectableZebraStripe } from './result-table-utils';
3+
import { selectedRowClassName, zebraStripe } from './result-table-utils';
44
import RawTableValue from './RawTableValue';
55

66
interface Props {
77
rowIndex: number;
88
row: ResultRow;
99
databaseUri: string;
1010
className?: string;
11-
isSelected?: boolean;
11+
selectedColumn?: number;
1212
onSelected?: (row: number, column: number) => void;
1313
}
1414

1515
export default function RawTableRow(props: Props) {
1616
return (
17-
<tr key={props.rowIndex} {...selectableZebraStripe(props.isSelected ?? false, props.rowIndex, props.className || '')}>
17+
<tr key={props.rowIndex} {...zebraStripe(props.rowIndex, props.className || '')}>
1818
<td key={-1}>{props.rowIndex + 1}</td>
1919

2020
{props.row.map((value, columnIndex) => (
21-
<td key={columnIndex}>
21+
<td key={columnIndex} {...props.selectedColumn === columnIndex ? { className: selectedRowClassName } : {}}>
2222
<RawTableValue
2323
value={value}
2424
databaseUri={props.databaseUri}

extensions/ql-vscode/src/view/results/alert-table.tsx

Lines changed: 81 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import * as React from 'react';
33
import * as Sarif from 'sarif';
44
import * as Keys from '../../pure/result-keys';
55
import * as octicons from './octicons';
6-
import { className, renderLocation, ResultTableProps, zebraStripe, selectableZebraStripe, jumpToLocation, nextSortDirection, emptyQueryResultsMessage } from './result-table-utils';
7-
import { onNavigation, NavigationEvent } from './results';
8-
import { InterpretedResultSet, NavigateAlertMsg, NavigatePathMsg, SarifInterpretationData } from '../../pure/interface-types';
6+
import { className, renderLocation, ResultTableProps, selectableZebraStripe, jumpToLocation, nextSortDirection, emptyQueryResultsMessage } from './result-table-utils';
7+
import { onNavigation } from './results';
8+
import { InterpretedResultSet, NavigateMsg, NavigationDirection, SarifInterpretationData } from '../../pure/interface-types';
99
import {
1010
parseSarifPlainTextMessage,
1111
parseSarifLocation,
@@ -18,7 +18,7 @@ import { isWholeFileLoc, isLineColumnLoc } from '../../pure/bqrs-utils';
1818
export type PathTableProps = ResultTableProps & { resultSet: InterpretedResultSet<SarifInterpretationData> };
1919
export interface PathTableState {
2020
expanded: Set<string>;
21-
selectedItem: undefined | Keys.PathNode | Keys.Result;
21+
selectedItem: undefined | Keys.ResultKey;
2222
}
2323

2424
export class PathTable extends React.Component<PathTableProps, PathTableState> {
@@ -246,8 +246,9 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
246246
const currentPathExpanded = this.state.expanded.has(Keys.keyToString(pathKey));
247247
if (currentResultExpanded) {
248248
const indicator = currentPathExpanded ? octicons.chevronDown : octicons.chevronRight;
249+
const isPathSpecificallySelected = Keys.equalsNotUndefined(pathKey, selectedItem);
249250
rows.push(
250-
<tr {...zebraStripe(resultIndex)} key={`${resultIndex}-${pathIndex}`}>
251+
<tr {...selectableZebraStripe(isPathSpecificallySelected, resultIndex)} key={`${resultIndex}-${pathIndex}`}>
251252
<td className="vscode-codeql__icon-cell"><span className="vscode-codeql__vertical-rule"></span></td>
252253
<td className="vscode-codeql__icon-cell vscode-codeql__dropdown-cell" onMouseDown={toggler([pathKey])}>{indicator}</td>
253254
<td className="vscode-codeql__text-center" colSpan={3}>
@@ -302,82 +303,89 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
302303
</table>;
303304
}
304305

305-
private handleNavigationEvent(event: NavigationEvent) {
306-
switch (event.t) {
307-
case 'navigatePath': {
308-
this.handleNavigatePathStepEvent(event);
309-
break;
310-
}
311-
case 'navigateAlert': {
312-
this.handleNavigateAlertEvent(event);
313-
break;
314-
}
315-
}
316-
}
317-
318-
private handleNavigatePathStepEvent(event: NavigatePathMsg) {
306+
private handleNavigationEvent(event: NavigateMsg) {
319307
this.setState(prevState => {
320-
const { selectedItem } = prevState;
321-
if (selectedItem === undefined) return prevState;
322-
323-
const selectedPath = selectedItem.pathIndex !== undefined
324-
? selectedItem
325-
: { ...selectedItem, pathIndex: 0 };
326-
327-
const path = Keys.getPath(this.props.resultSet.interpretation.data, selectedPath);
328-
if (path === undefined) return prevState;
329-
330-
const nextIndex = selectedItem.pathNodeIndex !== undefined
331-
? (selectedItem.pathNodeIndex + event.direction)
332-
: 0;
333-
if (nextIndex < 0 || nextIndex >= path.locations.length) return prevState;
334-
335-
const sarifLoc = path.locations[nextIndex].location;
336-
if (sarifLoc === undefined) {
337-
return prevState;
308+
const key = this.getNewSelection(prevState.selectedItem, event.direction);
309+
const data = this.props.resultSet.interpretation.data;
310+
311+
// Check if the selected node actually exists (bounds check) and get its location if relevant
312+
let jumpLocation: Sarif.Location | undefined;
313+
if (key.pathNodeIndex != null) {
314+
jumpLocation = Keys.getPathNode(data, key);
315+
if (jumpLocation == null) {
316+
return prevState; // Result does not exist
317+
}
318+
} else if (key.pathIndex != null) {
319+
if (Keys.getPath(data, key) == null) {
320+
return prevState; // Path does not exist
321+
}
322+
jumpLocation = undefined; // When selecting a 'path', don't jump anywhere.
323+
} else {
324+
jumpLocation = Keys.getResult(data, key)?.locations?.[0];
325+
if (jumpLocation == null) {
326+
return prevState; // Path step does not exist.
327+
}
338328
}
339-
340-
const loc = parseSarifLocation(sarifLoc, this.props.resultSet.interpretation.sourceLocationPrefix);
341-
if (isNoLocation(loc)) {
342-
return prevState;
329+
if (jumpLocation !== undefined) {
330+
const parsedLocation = parseSarifLocation(jumpLocation, this.props.resultSet.interpretation.sourceLocationPrefix);
331+
if (!isNoLocation(parsedLocation)) {
332+
jumpToLocation(parsedLocation, this.props.databaseUri);
333+
}
343334
}
344335

345-
jumpToLocation(loc, this.props.databaseUri);
346-
const newSelection = { ...selectedPath, pathNodeIndex: nextIndex };
347-
const newExpanded = new Set(prevState.expanded);
348-
// In case we're jumping from the main alert row to its first path step, expand the enclosing nodes so the selected
349-
// node is actually visible.
350-
newExpanded.add(Keys.keyToString({ resultIndex: newSelection.resultIndex }));
351-
newExpanded.add(Keys.keyToString({ resultIndex: newSelection.resultIndex, pathIndex: newSelection.pathIndex }));
352-
return { ...prevState, selectedItem: newSelection, expanded: newExpanded };
336+
const expanded = new Set(prevState.expanded);
337+
if (event.direction === NavigationDirection.right) {
338+
// When stepping right, expand to ensure the selected node is visible
339+
expanded.add(Keys.keyToString({ resultIndex: key.resultIndex }));
340+
if (key.pathIndex != null) {
341+
expanded.add(Keys.keyToString({ resultIndex: key.resultIndex, pathIndex: key.pathIndex }));
342+
}
343+
} else if (event.direction === NavigationDirection.left) {
344+
// When stepping left, collapse immediately
345+
expanded.delete(Keys.keyToString(key));
346+
}
347+
return {
348+
...prevState,
349+
expanded,
350+
selectedItem: key
351+
};
353352
});
354353
}
355354

356-
private handleNavigateAlertEvent(event: NavigateAlertMsg) {
357-
this.setState(prevState => {
358-
const { selectedItem } = prevState;
359-
if (selectedItem === undefined) return prevState;
360-
361-
const nextIndex = selectedItem.resultIndex + event.direction;
362-
const nextSelection = { resultIndex: nextIndex };
363-
const result = Keys.getResult(this.props.resultSet.interpretation.data, nextSelection);
364-
if (result === undefined) {
365-
return prevState;
366-
}
367-
368-
const sarifLoc = result.locations?.[0];
369-
if (sarifLoc === undefined) {
370-
return prevState;
371-
}
372-
373-
const loc = parseSarifLocation(sarifLoc, this.props.resultSet.interpretation.sourceLocationPrefix);
374-
if (isNoLocation(loc)) {
375-
return prevState;
355+
private getNewSelection(key: Keys.ResultKey | undefined, direction: NavigationDirection): Keys.ResultKey {
356+
if (key === undefined) {
357+
return { resultIndex: 0 };
358+
}
359+
const { resultIndex, pathIndex, pathNodeIndex } = key;
360+
switch (direction) {
361+
case NavigationDirection.up:
362+
case NavigationDirection.down: {
363+
const delta = direction === NavigationDirection.up ? -1 : 1;
364+
if (key.pathNodeIndex !== undefined) {
365+
return { resultIndex, pathIndex: key.pathIndex, pathNodeIndex: key.pathNodeIndex + delta };
366+
} else if (pathIndex !== undefined) {
367+
return { resultIndex, pathIndex: pathIndex + delta };
368+
} else {
369+
return { resultIndex: resultIndex + delta };
370+
}
376371
}
377-
378-
jumpToLocation(loc, this.props.databaseUri);
379-
return { ...prevState, selectedItem: nextSelection };
380-
});
372+
case NavigationDirection.left:
373+
if (key.pathNodeIndex !== undefined) {
374+
return { resultIndex, pathIndex: key.pathIndex };
375+
} else if (pathIndex !== undefined) {
376+
return { resultIndex };
377+
} else {
378+
return key;
379+
}
380+
case NavigationDirection.right:
381+
if (pathIndex === undefined) {
382+
return { resultIndex, pathIndex: 0 };
383+
} else if (pathNodeIndex === undefined) {
384+
return { resultIndex, pathIndex, pathNodeIndex: 0 };
385+
} else {
386+
return key;
387+
}
388+
}
381389
}
382390

383391
componentDidMount() {

0 commit comments

Comments
 (0)