Skip to content

Commit 2d32a22

Browse files
committed
Select a region of the source when clicking on MIR output
1 parent 9c8375f commit 2d32a22

File tree

8 files changed

+94
-4
lines changed

8 files changed

+94
-4
lines changed

ui/frontend/AdvancedEditor.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
22
import { connect } from 'react-redux';
33

44
import State from './state';
5-
import { CommonEditorProps, Crate, Edition, Focus, PairCharacters, Position } from './types';
5+
import { CommonEditorProps, Crate, Edition, Focus, PairCharacters, Position, Selection } from './types';
66

77
type Ace = typeof import('ace-builds');
88
type AceEditor = import('ace-builds').Ace.Editor;
@@ -64,6 +64,7 @@ interface AdvancedEditorProps {
6464
keybinding?: string;
6565
onEditCode: (_: string) => any;
6666
position: Position;
67+
selection: Selection;
6768
theme: string;
6869
crates: Crate[];
6970
focus?: Focus;
@@ -250,6 +251,28 @@ const AdvancedEditor: React.SFC<AdvancedEditorProps> = props => {
250251
editor.focus();
251252
}, []));
252253

254+
const selectionProps = useMemo(() => ({
255+
selection: props.selection,
256+
ace: props.ace,
257+
}), [props.selection, props.ace]);
258+
259+
useEditorProp(editor, selectionProps, useCallback((editor, { ace, selection }) => {
260+
if (selection.start && selection.end) {
261+
// Columns are zero-indexed in ACE, but why does the selection
262+
// API and `gotoLine` treat the row/line differently?
263+
const toPoint = ({ line, column }: Position) => ({ row: line - 1, column: column - 1 });
264+
265+
const start = toPoint(selection.start);
266+
const end = toPoint(selection.end);
267+
268+
const range = new ace.Range(start.row, start.column, end.row, end.column);
269+
270+
editor.selection.setRange(range);
271+
editor.renderer.scrollCursorIntoView(start);
272+
editor.focus();
273+
}
274+
}, []));
275+
253276
// There's a tricky bug with Ace:
254277
//
255278
// 1. Open the page
@@ -295,6 +318,7 @@ interface AdvancedEditorAsyncProps {
295318
keybinding?: string;
296319
onEditCode: (_: string) => any;
297320
position: Position;
321+
selection: Selection;
298322
theme: string;
299323
crates: Crate[];
300324
focus?: Focus;

ui/frontend/Editor.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useSelector, useDispatch } from 'react-redux';
33

44
import * as actions from './actions';
55
import AdvancedEditor from './AdvancedEditor';
6-
import { CommonEditorProps, Editor as EditorType, Position } from './types';
6+
import { CommonEditorProps, Editor as EditorType, Position, Selection } from './types';
77
import { State } from './reducers';
88

99
class CodeByteOffsets {
@@ -24,6 +24,17 @@ class CodeByteOffsets {
2424
return [precedingBytes, precedingBytes + highlightedBytes];
2525
}
2626

27+
public rangeToOffsets(start: Position, end: Position) {
28+
const startBytes = this.positionToBytes(start);
29+
const endBytes = this.positionToBytes(end);
30+
return [startBytes, endBytes];
31+
}
32+
33+
private positionToBytes(position: Position) {
34+
// Subtract one as this logic is zero-based and the columns are one-based
35+
return this.bytesBeforeLine(position.line) + position.column - 1;
36+
}
37+
2738
private bytesBeforeLine(line: number) {
2839
// Subtract one as this logic is zero-based and the lines are one-based
2940
line -= 1;
@@ -64,6 +75,7 @@ class SimpleEditor extends React.PureComponent<CommonEditorProps> {
6475

6576
public componentDidUpdate(prevProps, _prevState) {
6677
this.gotoPosition(prevProps.position, this.props.position);
78+
this.setSelection(prevProps.selection, this.props.selection);
6779
}
6880

6981
private gotoPosition(oldPosition: Position, newPosition: Position) {
@@ -78,12 +90,26 @@ class SimpleEditor extends React.PureComponent<CommonEditorProps> {
7890
editor.focus();
7991
editor.setSelectionRange(startBytes, endBytes);
8092
}
93+
94+
private setSelection(oldSelection: Selection, newSelection: Selection) {
95+
const editor = this._editor;
96+
97+
if (!newSelection || !editor) { return; }
98+
if (newSelection === oldSelection) { return; }
99+
100+
const offsets = new CodeByteOffsets(this.props.code);
101+
const [startBytes, endBytes] = offsets.rangeToOffsets(newSelection.start, newSelection.end);
102+
103+
editor.focus();
104+
editor.setSelectionRange(startBytes, endBytes);
105+
}
81106
}
82107

83108
const Editor: React.SFC = () => {
84109
const code = useSelector((state: State) => state.code);
85110
const editor = useSelector((state: State) => state.configuration.editor);
86111
const position = useSelector((state: State) => state.position);
112+
const selection = useSelector((state: State) => state.selection);
87113
const crates = useSelector((state: State) => state.crates);
88114

89115
const dispatch = useDispatch();
@@ -96,6 +122,7 @@ const Editor: React.SFC = () => {
96122
<div className="editor">
97123
<SelectedEditor code={code}
98124
position={position}
125+
selection={selection}
99126
crates={crates}
100127
onEditCode={onEditCode}
101128
execute={execute} />

ui/frontend/actions.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
PrimaryActionAuto,
2828
PrimaryActionCore,
2929
ProcessAssembly,
30+
Position,
3031
makePosition,
3132
} from './types';
3233

@@ -93,6 +94,7 @@ export enum ActionType {
9394
AddImport = 'ADD_IMPORT',
9495
EnableFeatureGate = 'ENABLE_FEATURE_GATE',
9596
GotoPosition = 'GOTO_POSITION',
97+
SelectText = 'SELECT_TEXT',
9698
RequestFormat = 'REQUEST_FORMAT',
9799
FormatSucceeded = 'FORMAT_SUCCEEDED',
98100
FormatFailed = 'FORMAT_FAILED',
@@ -433,6 +435,9 @@ export const enableFeatureGate = (featureGate: string) =>
433435
export const gotoPosition = (line: string | number, column: string | number) =>
434436
createAction(ActionType.GotoPosition, makePosition(line, column));
435437

438+
export const selectText = (start: Position, end: Position) =>
439+
createAction(ActionType.SelectText, { start, end });
440+
436441
const requestFormat = () =>
437442
createAction(ActionType.RequestFormat);
438443

@@ -793,6 +798,7 @@ export type Action =
793798
| ReturnType<typeof addImport>
794799
| ReturnType<typeof enableFeatureGate>
795800
| ReturnType<typeof gotoPosition>
801+
| ReturnType<typeof selectText>
796802
| ReturnType<typeof requestFormat>
797803
| ReturnType<typeof receiveFormatSuccess>
798804
| ReturnType<typeof receiveFormatFailure>

ui/frontend/highlighting.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import Prism from 'prismjs';
2+
import { makePosition } from './types';
23

34
export function configureRustErrors({
45
enableFeatureGate,
56
getChannel,
67
gotoPosition,
8+
selectText,
79
addImport,
810
reExecuteWithBacktrace,
911
}) {
@@ -158,10 +160,13 @@ export function configureRustErrors({
158160

159161
const mirSourceLinks = env.element.querySelectorAll('.mir-source');
160162
Array.from(mirSourceLinks).forEach((link: HTMLAnchorElement) => {
161-
const { startLine, startCol } = link.dataset;
163+
const { startLine, startCol, endLine, endCol } = link.dataset;
164+
const start = makePosition(startLine, startCol);
165+
const end = makePosition(endLine, endCol);
166+
162167
link.onclick = e => {
163168
e.preventDefault();
164-
gotoPosition(startLine, startCol);
169+
selectText(start, end);
165170
};
166171
});
167172
});

ui/frontend/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
editCode,
1515
enableFeatureGate,
1616
gotoPosition,
17+
selectText,
1718
addImport,
1819
performCratesLoad,
1920
performVersionsLoad,
@@ -49,6 +50,7 @@ const store = createStore(playgroundApp, initialState, enhancers);
4950
configureRustErrors({
5051
enableFeatureGate: featureGate => store.dispatch(enableFeatureGate(featureGate)),
5152
gotoPosition: (line, col) => store.dispatch(gotoPosition(line, col)),
53+
selectText: (start, end) => store.dispatch(selectText(start, end)),
5254
addImport: (code) => store.dispatch(addImport(code)),
5355
reExecuteWithBacktrace: () => store.dispatch(reExecuteWithBacktrace()),
5456
getChannel: () => store.getState().configuration.channel,

ui/frontend/reducers/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import notifications from './notifications';
88
import output from './output';
99
import page from './page';
1010
import position from './position';
11+
import selection from './selection';
1112
import versions from './versions';
1213

1314
const playgroundApp = combineReducers({
@@ -19,6 +20,7 @@ const playgroundApp = combineReducers({
1920
output,
2021
page,
2122
position,
23+
selection,
2224
versions,
2325
});
2426

ui/frontend/reducers/selection.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Action, ActionType } from '../actions';
2+
import { Selection } from '../types';
3+
4+
const DEFAULT: Selection = {
5+
start: null,
6+
end: null,
7+
};
8+
9+
export default function position(state = DEFAULT, action: Action) {
10+
switch (action.type) {
11+
case ActionType.SelectText: {
12+
const { start, end } = action;
13+
return { ...state, start, end };
14+
}
15+
default:
16+
return state;
17+
}
18+
}

ui/frontend/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ export interface Position {
88
export const makePosition = (line: string | number, column: string | number): Position =>
99
({ line: +line, column: +column });
1010

11+
export interface Selection {
12+
start?: Position;
13+
end?: Position;
14+
}
15+
1116
export interface Crate {
1217
id: string;
1318
name: string;
@@ -25,6 +30,7 @@ export interface CommonEditorProps {
2530
execute: () => any;
2631
onEditCode: (_: string) => any;
2732
position: Position;
33+
selection: Selection;
2834
crates: Crate[];
2935
}
3036

0 commit comments

Comments
 (0)