-
Notifications
You must be signed in to change notification settings - Fork 412
/
Copy pathcodemirror-shared.js
164 lines (148 loc) · 5.76 KB
/
codemirror-shared.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// @flow
import {
EditorView,
Decoration,
GutterMarker,
gutter,
gutterLineClass,
} from '@codemirror/view';
import {
EditorState,
StateField,
StateEffect,
RangeSet,
} from '@codemirror/state';
import type { LineTimings } from 'firefox-profiler/types';
import { emptyLineTimings } from 'firefox-profiler/profile-logic/line-timings';
// This gutter marker applies the "cm-nonZeroLine" class to gutter elements.
const nonZeroLineGutterMarker = new (class extends GutterMarker {
elementClass = 'cm-nonZeroLine';
})();
// This "decoration" applies the "cm-nonZeroLine" class to the line of assembly
// code in the main editor contents (not the gutter).
const nonZeroLineDecoration = Decoration.line({ class: 'cm-nonZeroLine' });
// An "effect" is like a redux action. This effect is used to replace the value
// of the timingsField state field.
export const updateTimingsEffect = StateEffect.define<LineTimings>();
// A "state field" for the timings.
const timingsField = StateField.define<LineTimings>({
create() {
return emptyLineTimings;
},
update(timings, transaction) {
// This is like a reducer. Find an updateTimingsEffect in the transaction
// and set this field to the timings in it.
let newTimings = timings;
for (const effect of transaction.effects) {
if (effect.is(updateTimingsEffect)) {
newTimings = effect.value;
}
}
return newTimings;
},
});
// Finds all lines with non-zero line timings, for the highlight line.
// The line numbers are then converted into "positions", i.e. character offsets
// in the document, for the start of the line.
// Then they are sorted, because our caller wants to have a sorted list.
function getSortedStartPositionsOfNonZeroLines(state: EditorState): number[] {
const timings = state.field(timingsField);
const nonZeroLines = new Set();
for (const lineNumber of timings.totalLineHits.keys()) {
nonZeroLines.add(lineNumber);
}
for (const lineNumber of timings.selfLineHits.keys()) {
nonZeroLines.add(lineNumber);
}
const lineCount = state.doc.lines;
const positions = [...nonZeroLines]
.filter((l) => l >= 1 && l <= lineCount)
.map((lineNumber) => state.doc.line(lineNumber).from);
positions.sort((a, b) => a - b);
return positions;
}
// This is an "extension" which applies the "cm-nonZeroLine" class to all gutter
// elements for lines with non-zero timings. It is like a piece of derived state;
// it needs to be recomputed whenever one of the input states change. The input
// states are the editor contents ("doc") and the value of the timings field.
// The editor contents are relevant because the output is expressed in terms of
// positions, i.e. character offsets from the document start, and those positions
// need to be updated if the amount of text in a line changes. This happens when
// we replace the file placeholder content with the actual file content.
const nonZeroLineGutterHighlighter = gutterLineClass.compute(
['doc', timingsField],
(state) => {
const positions = getSortedStartPositionsOfNonZeroLines(state);
return RangeSet.of(positions.map((p) => nonZeroLineGutterMarker.range(p)));
}
);
// Same as the previous extension, but this one is for the main editor. There
// doesn't seem to be a way to set a class for the entire line, i.e. both the
// gutter elements and the main editor elements of that line.
const nonZeroLineDecorationHighlighter = EditorView.decorations.compute(
['doc', timingsField],
(state) => {
const positions = getSortedStartPositionsOfNonZeroLines(state);
return RangeSet.of(positions.map((p) => nonZeroLineDecoration.range(p)));
}
);
// This is a "gutter marker" which renders just a string and nothing else.
// It is used for the AddressTimings annotations, i.e. for the numbers in the
// gutter.
export class StringMarker extends GutterMarker {
_s: string;
constructor(s: string) {
super();
this._s = s;
}
toDOM() {
return document.createTextNode(this._s);
}
}
// The "extension" which manages the elements in the gutter for the "total"
// column.
const totalTimingsGutter = gutter({
class: 'cm-total-timings-gutter',
lineMarker(view, line) {
// Return a gutter marker for this line, or null.
const lineNumber = view.state.doc.lineAt(line.from).number;
const timings = view.state.field(timingsField);
const totalTime = timings.totalLineHits.get(lineNumber);
return totalTime !== undefined ? new StringMarker(totalTime) : null;
},
lineMarkerChange(update) {
// Return true if the update affects the total timings in the gutter.
return update.transactions.some((t) =>
t.effects.some((e) => e.is(updateTimingsEffect))
);
},
});
// The "extension" which manages the elements in the gutter for the "self"
// column.
const selfTimingsGutter = gutter({
class: 'cm-self-timings-gutter',
lineMarker(view, line) {
// Return a gutter marker for this line, or null.
const lineNumber = view.state.doc.lineAt(line.from).number;
const timings = view.state.field(timingsField);
const selfTime = timings.selfLineHits.get(lineNumber);
return selfTime !== undefined ? new StringMarker(selfTime) : null;
},
lineMarkerChange(update) {
// Return true if the update affects the self timings in the gutter.
return update.transactions.some((t) =>
t.effects.some((e) => e.is(updateTimingsEffect))
);
},
});
// All extensions which have to do with timings, grouped into one extension.
export const timingsExtension = [
timingsField,
totalTimingsGutter,
selfTimingsGutter,
nonZeroLineGutterHighlighter,
nonZeroLineDecorationHighlighter,
];