chrome-devtools-frontend 1.0.927127 → 1.0.927419
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/gni/devtools_grd_files.gni +3 -0
- package/front_end/core/host/UserMetrics.ts +0 -1
- package/front_end/core/i18n/locales/en-US.json +27 -9
- package/front_end/core/i18n/locales/en-XL.json +27 -9
- package/front_end/core/platform/keyboard-utilities.ts +1 -0
- package/front_end/entrypoints/main/MainImpl.ts +0 -1
- package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +0 -6
- package/front_end/panels/snippets/SnippetsQuickOpen.ts +8 -3
- package/front_end/panels/sources/sources-meta.ts +22 -7
- package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
- package/front_end/third_party/codemirror.next/package.json +4 -4
- package/front_end/ui/components/code_highlighter/CodeHighlighter.ts +137 -0
- package/front_end/ui/components/code_highlighter/codeHighlighter.css +51 -0
- package/front_end/ui/components/code_highlighter/code_highlighter.ts +11 -0
- package/front_end/ui/components/docs/text_editor/basic.html +28 -0
- package/front_end/ui/components/docs/text_editor/basic.ts +14 -0
- package/front_end/ui/components/docs/text_prompt/basic.html +35 -0
- package/front_end/ui/components/docs/text_prompt/basic.ts +19 -0
- package/front_end/ui/components/render_coordinator/RenderCoordinator.ts +17 -0
- package/front_end/ui/components/text_editor/TextEditor.ts +161 -0
- package/front_end/ui/components/text_editor/config.ts +264 -0
- package/front_end/ui/components/text_editor/text_editor.ts +6 -0
- package/front_end/ui/components/text_editor/theme.ts +113 -0
- package/front_end/ui/components/text_prompt/TextPrompt.ts +144 -0
- package/front_end/ui/components/text_prompt/textPrompt.css +33 -0
- package/front_end/ui/components/text_prompt/text_prompt.ts +9 -0
- package/front_end/ui/legacy/components/quick_open/CommandMenu.ts +8 -3
- package/front_end/ui/legacy/components/quick_open/FilteredListWidget.ts +38 -38
- package/front_end/ui/legacy/components/quick_open/HelpQuickOpen.ts +10 -4
- package/front_end/ui/legacy/components/quick_open/QuickOpen.ts +23 -6
- package/front_end/ui/legacy/components/quick_open/filteredListWidget.css +7 -8
- package/front_end/ui/legacy/filter.css +1 -0
- package/inspector_overlay/main.ts +2 -1
- package/inspector_overlay/tool_screenshot.ts +8 -1
- package/package.json +1 -1
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// Copyright 2021 The Chromium Authors. All rights reserved.
|
|
2
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
3
|
+
// found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
import * as Common from '../../../core/common/common.js';
|
|
6
|
+
import * as i18n from '../../../core/i18n/i18n.js';
|
|
7
|
+
import * as CM from '../../../third_party/codemirror.next/codemirror.next.js';
|
|
8
|
+
import * as CodeHighlighter from '../code_highlighter/code_highlighter.js';
|
|
9
|
+
|
|
10
|
+
import {editorTheme} from './theme.js';
|
|
11
|
+
|
|
12
|
+
const LINES_TO_SCAN_FOR_INDENTATION_GUESSING = 1000;
|
|
13
|
+
|
|
14
|
+
const UIStrings = {
|
|
15
|
+
/**
|
|
16
|
+
*@description Label text for the editor
|
|
17
|
+
*/
|
|
18
|
+
codeEditor: 'Code editor',
|
|
19
|
+
};
|
|
20
|
+
const str_ = i18n.i18n.registerUIStrings('ui/components/text_editor/config.ts', UIStrings);
|
|
21
|
+
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
22
|
+
|
|
23
|
+
const empty: CM.Extension = [];
|
|
24
|
+
|
|
25
|
+
export const dynamicSetting = CM.Facet.define<DynamicSetting<unknown>>();
|
|
26
|
+
|
|
27
|
+
// The code below is used to wire up dynamic settings to editors. When
|
|
28
|
+
// you include one of these objects in an editor configuration, the
|
|
29
|
+
// TextEditor class will take care of listening to changes in the
|
|
30
|
+
// setting, and updating the configuration as appropriate.
|
|
31
|
+
|
|
32
|
+
export class DynamicSetting<T> {
|
|
33
|
+
compartment = new CM.Compartment();
|
|
34
|
+
extension: CM.Extension;
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
readonly settingName: string,
|
|
38
|
+
private readonly getExtension: (value: T, state: CM.EditorState) => CM.Extension,
|
|
39
|
+
) {
|
|
40
|
+
this.extension = [this.compartment.of(empty), dynamicSetting.of(this as DynamicSetting<unknown>)];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
sync(state: CM.EditorState, value: T): CM.StateEffect<unknown>|null {
|
|
44
|
+
const cur = this.compartment.get(state);
|
|
45
|
+
const needed = this.getExtension(value, state);
|
|
46
|
+
return cur === needed ? null : this.compartment.reconfigure(needed);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static bool(name: string, enabled: CM.Extension, disabled: CM.Extension = empty): DynamicSetting<boolean> {
|
|
50
|
+
return new DynamicSetting<boolean>(name, val => val ? enabled : disabled);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const tabMovesFocus = DynamicSetting.bool('textEditorTabMovesFocus', CM.keymap.of([{
|
|
55
|
+
key: 'Tab',
|
|
56
|
+
run: (view: CM.EditorView): boolean => view.state.doc.length ? CM.indentMore(view) : false,
|
|
57
|
+
shift: (view: CM.EditorView): boolean => view.state.doc.length ? CM.indentLess(view) : false,
|
|
58
|
+
}]));
|
|
59
|
+
|
|
60
|
+
export const bracketMatching = DynamicSetting.bool('textEditorBracketMatching', CM.bracketMatching());
|
|
61
|
+
|
|
62
|
+
export function guessIndent(doc: CM.Text): string {
|
|
63
|
+
const values: {[indent: string]: number} = Object.create(null);
|
|
64
|
+
let scanned = 0;
|
|
65
|
+
for (let cur = doc.iterLines(1, Math.min(doc.lines + 1, LINES_TO_SCAN_FOR_INDENTATION_GUESSING)); !cur.next().done;) {
|
|
66
|
+
let space = (/^\s*/.exec(cur.value) as string[])[0];
|
|
67
|
+
if (space.length === cur.value.length || !space.length) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (space[0] === '\t') {
|
|
71
|
+
space = '\t';
|
|
72
|
+
} else if (/[^ ]/.test(space)) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
scanned++;
|
|
76
|
+
values[space] = (values[space] || 0) + 1;
|
|
77
|
+
}
|
|
78
|
+
const minOccurrence = scanned * 0.05;
|
|
79
|
+
const sorted = Object.entries(values).filter(e => e[1] > minOccurrence).sort((a, b) => a[1] - b[1]);
|
|
80
|
+
return sorted.length ? sorted[0][0] : Common.Settings.Settings.instance().moduleSetting('textEditorIndent').get();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const cachedIndentUnit: {[indent: string]: CM.Extension} = Object.create(null);
|
|
84
|
+
|
|
85
|
+
function getIndentUnit(indent: string): CM.Extension {
|
|
86
|
+
let value = cachedIndentUnit[indent];
|
|
87
|
+
if (!value) {
|
|
88
|
+
value = cachedIndentUnit[indent] = CM.indentUnit.of(indent);
|
|
89
|
+
}
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const autoDetectIndent = new DynamicSetting<boolean>('textEditorAutoDetectIndent', (on, state) => {
|
|
94
|
+
return on ? CM.Prec.override(getIndentUnit(guessIndent(state.doc))) : empty;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
function matcher(decorator: CM.MatchDecorator): CM.Extension {
|
|
98
|
+
return CM.ViewPlugin.define(
|
|
99
|
+
view => ({
|
|
100
|
+
decorations: decorator.createDeco(view),
|
|
101
|
+
update(u): void {
|
|
102
|
+
this.decorations = decorator.updateDeco(u, this.decorations);
|
|
103
|
+
},
|
|
104
|
+
}),
|
|
105
|
+
{
|
|
106
|
+
decorations: v => v.decorations,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const WhitespaceDeco = new Map<string, CM.Decoration>();
|
|
111
|
+
|
|
112
|
+
function getWhitespaceDeco(space: string): CM.Decoration {
|
|
113
|
+
const cached = WhitespaceDeco.get(space);
|
|
114
|
+
if (cached) {
|
|
115
|
+
return cached;
|
|
116
|
+
}
|
|
117
|
+
const result = CM.Decoration.mark({
|
|
118
|
+
attributes: space === '\t' ? {
|
|
119
|
+
class: 'cm-highlightedTab',
|
|
120
|
+
} :
|
|
121
|
+
{class: 'cm-highlightedSpaces', 'data-display': '·'.repeat(space.length)},
|
|
122
|
+
});
|
|
123
|
+
WhitespaceDeco.set(space, result);
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const showAllWhitespace = matcher(new CM.MatchDecorator({
|
|
128
|
+
regexp: /\t| +/g,
|
|
129
|
+
decoration: (match: RegExpExecArray): CM.Decoration => getWhitespaceDeco(match[0]),
|
|
130
|
+
boundary: /\S/,
|
|
131
|
+
}));
|
|
132
|
+
|
|
133
|
+
const showTrailingWhitespace = matcher(new CM.MatchDecorator({
|
|
134
|
+
regexp: /\s+$/g,
|
|
135
|
+
decoration: CM.Decoration.mark({class: 'cm-trailingWhitespace'}),
|
|
136
|
+
boundary: /\S/,
|
|
137
|
+
}));
|
|
138
|
+
|
|
139
|
+
export const showWhitespace = new DynamicSetting<string>('showWhitespacesInEditor', value => {
|
|
140
|
+
if (value === 'all') {
|
|
141
|
+
return showAllWhitespace;
|
|
142
|
+
}
|
|
143
|
+
if (value === 'trailing') {
|
|
144
|
+
return showTrailingWhitespace;
|
|
145
|
+
}
|
|
146
|
+
return empty;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
export const allowScrollPastEof = DynamicSetting.bool('allowScrollPastEof', CM.scrollPastEnd());
|
|
150
|
+
|
|
151
|
+
export const indentUnit = new DynamicSetting<string>('textEditorIndent', getIndentUnit);
|
|
152
|
+
|
|
153
|
+
export const domWordWrap = DynamicSetting.bool('domWordWrap', CM.EditorView.lineWrapping);
|
|
154
|
+
|
|
155
|
+
function detectLineSeparator(text: string): CM.Extension {
|
|
156
|
+
if (/\r\n/.test(text) && !/(^|[^\r])\n/.test(text)) {
|
|
157
|
+
return CM.EditorState.lineSeparator.of('\r\n');
|
|
158
|
+
}
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const baseKeymap = CM.keymap.of([
|
|
163
|
+
{key: 'Ctrl-m', run: CM.cursorMatchingBracket, shift: CM.selectMatchingBracket},
|
|
164
|
+
{key: 'Mod-/', run: CM.toggleComment},
|
|
165
|
+
{key: 'Mod-d', run: CM.selectNextOccurrence},
|
|
166
|
+
{key: 'Alt-ArrowLeft', mac: 'Ctrl-ArrowLeft', run: CM.cursorSubwordBackward, shift: CM.selectSubwordBackward},
|
|
167
|
+
{key: 'Alt-ArrowRight', mac: 'Ctrl-ArrowRight', run: CM.cursorSubwordForward, shift: CM.selectSubwordForward},
|
|
168
|
+
...CM.closeBracketsKeymap,
|
|
169
|
+
...CM.standardKeymap,
|
|
170
|
+
...CM.historyKeymap,
|
|
171
|
+
]);
|
|
172
|
+
|
|
173
|
+
function themeIsDark(): boolean {
|
|
174
|
+
const setting = Common.Settings.Settings.instance().moduleSetting('uiTheme').get();
|
|
175
|
+
return setting === 'systemPreferred' ? window.matchMedia('(prefers-color-scheme: dark)').matches : setting === 'dark';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const dummyDarkTheme = CM.EditorView.theme({}, {dark: true});
|
|
179
|
+
|
|
180
|
+
export function baseConfiguration(text: string): CM.Extension {
|
|
181
|
+
return [
|
|
182
|
+
editorTheme,
|
|
183
|
+
themeIsDark() ? dummyDarkTheme : [],
|
|
184
|
+
CM.highlightSpecialChars(),
|
|
185
|
+
CM.history(),
|
|
186
|
+
CM.drawSelection(),
|
|
187
|
+
CM.EditorState.allowMultipleSelections.of(true),
|
|
188
|
+
CM.indentOnInput(),
|
|
189
|
+
CodeHighlighter.CodeHighlighter.getHighlightStyle(CM),
|
|
190
|
+
CM.closeBrackets(),
|
|
191
|
+
baseKeymap,
|
|
192
|
+
tabMovesFocus,
|
|
193
|
+
bracketMatching,
|
|
194
|
+
indentUnit,
|
|
195
|
+
CM.Prec.fallback(CM.EditorView.contentAttributes.of({'aria-label': i18nString(UIStrings.codeEditor)})),
|
|
196
|
+
detectLineSeparator(text),
|
|
197
|
+
CM.tooltips({position: 'absolute'}),
|
|
198
|
+
];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
class CompletionHint extends CM.WidgetType {
|
|
202
|
+
constructor(readonly text: string) {
|
|
203
|
+
super();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
eq(other: CompletionHint): boolean {
|
|
207
|
+
return this.text === other.text;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
toDOM(): HTMLElement {
|
|
211
|
+
const span = document.createElement('span');
|
|
212
|
+
span.className = 'cm-completionHint';
|
|
213
|
+
span.textContent = this.text;
|
|
214
|
+
return span;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export const showCompletionHint = CM.ViewPlugin.fromClass(class {
|
|
219
|
+
decorations: CM.DecorationSet = CM.Decoration.none;
|
|
220
|
+
currentHint: string|null = null;
|
|
221
|
+
|
|
222
|
+
update(update: CM.ViewUpdate): void {
|
|
223
|
+
const top = this.currentHint = this.topCompletion(update.state);
|
|
224
|
+
if (!top) {
|
|
225
|
+
this.decorations = CM.Decoration.none;
|
|
226
|
+
} else {
|
|
227
|
+
this.decorations = CM.Decoration.set(
|
|
228
|
+
[CM.Decoration.widget({widget: new CompletionHint(top), side: 1}).range(update.state.selection.main.head)]);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
topCompletion(state: CM.EditorState): string|null {
|
|
233
|
+
const completions = CM.currentCompletions(state);
|
|
234
|
+
if (!completions.length) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
const {label} = completions[0];
|
|
238
|
+
if (label.length > 100 || label.indexOf('\n') > -1) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const pos = state.selection.main.head;
|
|
242
|
+
const lineBefore = state.doc.lineAt(pos);
|
|
243
|
+
if (pos !== lineBefore.to) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
const textBefore = lineBefore.text.slice(0, pos - lineBefore.from);
|
|
247
|
+
for (let i = label.length - 1; i > 0; i--) {
|
|
248
|
+
if (textBefore.endsWith(label.slice(0, i)) && !/\w/.test(textBefore.charAt(textBefore.length - i - 1))) {
|
|
249
|
+
return label.slice(i);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
}, {decorations: p => p.decorations});
|
|
255
|
+
|
|
256
|
+
export function contentIncludingHint(view: CM.EditorView): string {
|
|
257
|
+
const plugin = view.plugin(showCompletionHint);
|
|
258
|
+
let content = view.state.doc.toString();
|
|
259
|
+
if (plugin && plugin.currentHint) {
|
|
260
|
+
const {head} = view.state.selection.main;
|
|
261
|
+
content = content.slice(0, head) + plugin.currentHint + content.slice(head);
|
|
262
|
+
}
|
|
263
|
+
return content;
|
|
264
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Copyright 2021 The Chromium Authors. All rights reserved.
|
|
2
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
3
|
+
// found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
import * as CM from '../../../third_party/codemirror.next/codemirror.next.js';
|
|
6
|
+
|
|
7
|
+
export const editorTheme = CM.EditorView.theme({
|
|
8
|
+
'&.cm-editor': {
|
|
9
|
+
color: 'color: var(--color-text-primary)',
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
'.cm-scroller': {
|
|
13
|
+
lineHeight: '1.2em',
|
|
14
|
+
fontFamily: 'var(--source-code-font-family)',
|
|
15
|
+
fontSize: 'var(--source-code-font-size)',
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
'.cm-panels, .cm-tooltip': {
|
|
19
|
+
backgroundColor: 'var(--color-background-elevation-1)',
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
'.cm-selectionMatch': {
|
|
23
|
+
backgroundColor: 'var(--color-selection-highlight)',
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
'.cm-cursor': {
|
|
27
|
+
borderLeft: '1px solid var(--color-background-inverted)',
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
'&.cm-readonly .cm-cursor': {
|
|
31
|
+
display: 'none',
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
'.cm-cursor-secondary': {
|
|
35
|
+
borderLeft: '1px solid var(--color-secondary-cursor)',
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
'.cm-selectionBackground': {
|
|
39
|
+
background: 'var(--color-editor-selection-selection)',
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
'&.cm-focused .cm-selectionBackground': {
|
|
43
|
+
background: 'var(--color-editor-selection)',
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
'.cm-gutters': {
|
|
47
|
+
borderRight: '1px solid var(--color-details-hairline)',
|
|
48
|
+
whiteSpace: 'nowrap',
|
|
49
|
+
backgroundColor: 'var(--color-background)',
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
'.cm-lineNumbers .cm-gutterElement': {
|
|
53
|
+
color: 'var(--color-line-number)',
|
|
54
|
+
padding: '0 3px 0 9px',
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
'&:focus-within .cm-matchingBracket': {
|
|
58
|
+
color: 'inherit',
|
|
59
|
+
backgroundColor: 'var(--color-matching-bracket-background)',
|
|
60
|
+
borderBottom: '1px solid var(--color-matching-bracket-underline)',
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
'&:focus-within .cm-nonmatchingBracket': {
|
|
64
|
+
backgroundColor: 'var(--color-nonmatching-bracket-background)',
|
|
65
|
+
borderBottom: '1px solid var(--color-nonmatching-bracket-underline)',
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
'.cm-trailingWhitespace': {
|
|
69
|
+
backgroundColor: 'var(--color-error-text)',
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
'.cm-highlightedTab': {
|
|
73
|
+
display: 'inline-block',
|
|
74
|
+
position: 'relative',
|
|
75
|
+
'&:before': {
|
|
76
|
+
content: '""',
|
|
77
|
+
borderBottom: '1px solid var(--color-text-secondary)',
|
|
78
|
+
position: 'absolute',
|
|
79
|
+
left: '5%',
|
|
80
|
+
bottom: '50%',
|
|
81
|
+
width: '90%',
|
|
82
|
+
pointerEvents: 'none',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
'.cm-highlightedSpaces:before': {
|
|
87
|
+
color: 'var(--color-text-secondary)',
|
|
88
|
+
content: 'attr(data-display)',
|
|
89
|
+
position: 'absolute',
|
|
90
|
+
pointerEvents: 'none',
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
'.cm-placeholder': {
|
|
94
|
+
color: 'var(--color-text-secondary)',
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
'.cm-completionHint': {
|
|
98
|
+
color: 'var(--color-text-secondary)',
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
'.cm-highlightedLine': {
|
|
102
|
+
animation: 'cm-fading-highlight 2s 0s',
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
'@keyframes cm-fading-highlight': {
|
|
106
|
+
from: {
|
|
107
|
+
backgroundColor: 'var(--color-highlighted-line)',
|
|
108
|
+
},
|
|
109
|
+
to: {
|
|
110
|
+
backgroundColor: 'transparent',
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Copyright 2021 The Chromium Authors. All rights reserved.
|
|
2
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
3
|
+
// found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
import * as Platform from '../../../core/platform/platform.js';
|
|
6
|
+
import * as ComponentHelpers from '../../components/helpers/helpers.js';
|
|
7
|
+
import * as LitHtml from '../../lit-html/lit-html.js';
|
|
8
|
+
|
|
9
|
+
import textPromptStyles from './textPrompt.css.js';
|
|
10
|
+
|
|
11
|
+
export interface TextPromptData {
|
|
12
|
+
ariaLabel: string;
|
|
13
|
+
prefix: string;
|
|
14
|
+
suggestion: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class PromptInputEvent extends Event {
|
|
18
|
+
static readonly eventName = 'promptinputchanged';
|
|
19
|
+
data: string;
|
|
20
|
+
|
|
21
|
+
constructor(value: string) {
|
|
22
|
+
super(PromptInputEvent.eventName);
|
|
23
|
+
this.data = value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class TextPrompt extends HTMLElement {
|
|
28
|
+
static readonly litTagName = LitHtml.literal`devtools-text-prompt`;
|
|
29
|
+
private readonly shadow = this.attachShadow({mode: 'open'});
|
|
30
|
+
private ariaLabelText = '';
|
|
31
|
+
private prefixText = '';
|
|
32
|
+
private suggestionText = '';
|
|
33
|
+
|
|
34
|
+
connectedCallback(): void {
|
|
35
|
+
this.shadow.adoptedStyleSheets = [textPromptStyles];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
set data(data: TextPromptData) {
|
|
39
|
+
this.ariaLabelText = data.ariaLabel;
|
|
40
|
+
this.prefixText = data.prefix;
|
|
41
|
+
this.suggestionText = data.suggestion;
|
|
42
|
+
this.render();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get data(): TextPromptData {
|
|
46
|
+
return {
|
|
47
|
+
ariaLabel: this.ariaLabelText,
|
|
48
|
+
prefix: this.prefixText,
|
|
49
|
+
suggestion: this.suggestionText,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
focus(): void {
|
|
54
|
+
this.input().focus();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private input(): HTMLElement {
|
|
58
|
+
const inputElement = this.shadow.querySelector('.text-prompt-input');
|
|
59
|
+
if (!inputElement) {
|
|
60
|
+
throw new Error('Expected an input element!');
|
|
61
|
+
}
|
|
62
|
+
return /** @type {!HTMLElement} */ inputElement as HTMLElement;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
moveCaretToEndOfInput(): void {
|
|
66
|
+
this.setSelectedRange(this.text().length, this.text().length);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
onInput(): void {
|
|
70
|
+
this.dispatchEvent(new PromptInputEvent(this.text().trim()));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
onKeyDown(event: KeyboardEvent): void {
|
|
74
|
+
if (event.key === Platform.KeyboardUtilities.ENTER_KEY) {
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
setSelectedRange(startIndex: number, endIndex: number): void {
|
|
80
|
+
if (startIndex < 0) {
|
|
81
|
+
throw new RangeError('Selected range start must be a nonnegative integer');
|
|
82
|
+
}
|
|
83
|
+
const textContentLength = this.text().length;
|
|
84
|
+
if (endIndex > textContentLength) {
|
|
85
|
+
endIndex = textContentLength;
|
|
86
|
+
}
|
|
87
|
+
if (endIndex < startIndex) {
|
|
88
|
+
endIndex = startIndex;
|
|
89
|
+
}
|
|
90
|
+
const inputBox = this.input();
|
|
91
|
+
const range = document.createRange();
|
|
92
|
+
range.setStart(inputBox, startIndex);
|
|
93
|
+
range.setEnd(inputBox, endIndex);
|
|
94
|
+
const selection = window.getSelection();
|
|
95
|
+
if (selection) {
|
|
96
|
+
selection.removeAllRanges();
|
|
97
|
+
selection.addRange(range);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
setPrefix(prefix: string): void {
|
|
102
|
+
this.prefixText = prefix;
|
|
103
|
+
this.render();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
setSuggestion(suggestion: string): void {
|
|
107
|
+
this.suggestionText = suggestion;
|
|
108
|
+
this.render();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
setText(text: string): void {
|
|
112
|
+
this.input().textContent = text;
|
|
113
|
+
if (this.input().hasFocus()) {
|
|
114
|
+
this.moveCaretToEndOfInput();
|
|
115
|
+
this.input().scrollIntoView();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private text(): string {
|
|
120
|
+
return this.input().textContent || '';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private render(): void {
|
|
124
|
+
const output = LitHtml.html`
|
|
125
|
+
<span class="prefix">${this.prefixText}</span>
|
|
126
|
+
<input aria-label=${this.ariaLabelText}>
|
|
127
|
+
<div class="text-prompt-input" spellcheck="false" contenteditable="plaintext-only" @keydown=${
|
|
128
|
+
this.onKeyDown} @input=${this.onInput} suggestion=" ${this.suggestionText}"></div>
|
|
129
|
+
</input>`;
|
|
130
|
+
LitHtml.render(output, this.shadow, {host: this});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
ComponentHelpers.CustomElements.defineComponent('devtools-text-prompt', TextPrompt);
|
|
135
|
+
|
|
136
|
+
declare global {
|
|
137
|
+
interface HTMLElementTagNameMap {
|
|
138
|
+
'devtools-text-prompt': TextPrompt;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
interface HTMLElementEventMap {
|
|
142
|
+
'promptinputchanged': PromptInputEvent;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2021 The Chromium Authors. All rights reserved.
|
|
3
|
+
* Use of this source code is governed by a BSD-style license that can be
|
|
4
|
+
* found in the LICENSE file.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
:host {
|
|
8
|
+
white-space: pre;
|
|
9
|
+
overflow: hidden;
|
|
10
|
+
display: inline-flex;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.prefix {
|
|
14
|
+
color: var(--color-primary);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
input {
|
|
18
|
+
width: 0;
|
|
19
|
+
border: none;
|
|
20
|
+
outline: none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.text-prompt-input {
|
|
24
|
+
border: none;
|
|
25
|
+
outline: none;
|
|
26
|
+
display: inline;
|
|
27
|
+
flex: 1 0 auto;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.text-prompt-input::after {
|
|
31
|
+
content: attr(suggestion);
|
|
32
|
+
color: var(--color-background-highlight);
|
|
33
|
+
}
|
|
@@ -22,9 +22,13 @@ const UIStrings = {
|
|
|
22
22
|
*/
|
|
23
23
|
noCommandsFound: 'No commands found',
|
|
24
24
|
/**
|
|
25
|
-
* @description Text
|
|
25
|
+
* @description Text for command prefix of run a command
|
|
26
|
+
*/
|
|
27
|
+
run: 'Run',
|
|
28
|
+
/**
|
|
29
|
+
* @description Text for command suggestion of run a command
|
|
26
30
|
*/
|
|
27
|
-
|
|
31
|
+
command: 'Command',
|
|
28
32
|
};
|
|
29
33
|
const str_ = i18n.i18n.registerUIStrings('ui/legacy/components/quick_open/CommandMenu.ts', UIStrings);
|
|
30
34
|
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
@@ -388,6 +392,7 @@ export class ShowActionDelegate implements UI.ActionRegistration.ActionDelegate
|
|
|
388
392
|
registerProvider({
|
|
389
393
|
prefix: '>',
|
|
390
394
|
iconName: 'ic_command_run_command',
|
|
391
|
-
title: (): Common.UIString.LocalizedString => i18nString(UIStrings.runCommand),
|
|
392
395
|
provider: () => Promise.resolve(CommandMenuProvider.instance()),
|
|
396
|
+
titlePrefix: (): Common.UIString.LocalizedString => i18nString(UIStrings.run),
|
|
397
|
+
titleSuggestion: (): Common.UIString.LocalizedString => i18nString(UIStrings.command),
|
|
393
398
|
});
|