chrome-devtools-frontend 1.0.925655 → 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/.stylelintignore +1 -0
- package/AUTHORS +1 -0
- package/config/gni/devtools_grd_files.gni +10 -3
- package/front_end/core/common/Color.ts +6 -0
- package/front_end/core/common/SettingRegistration.ts +8 -0
- package/front_end/core/host/InspectorFrontendHost.ts +3 -0
- package/front_end/core/host/InspectorFrontendHostAPI.ts +3 -0
- package/front_end/core/host/UserMetrics.ts +7 -3
- package/front_end/core/i18n/locales/en-US.json +71 -14
- package/front_end/core/i18n/locales/en-XL.json +71 -14
- package/front_end/core/platform/keyboard-utilities.ts +1 -0
- package/front_end/core/root/Runtime.ts +1 -0
- package/front_end/core/sdk/ConsoleModel.ts +3 -0
- package/front_end/core/sdk/DebuggerModel.ts +2 -0
- package/front_end/core/sdk/NetworkManager.ts +12 -2
- package/front_end/core/sdk/NetworkRequest.ts +20 -5
- package/front_end/core/sdk/OverlayModel.ts +21 -0
- package/front_end/core/sdk/OverlayPersistentHighlighter.ts +55 -3
- package/front_end/devtools_compatibility.js +11 -1
- package/front_end/entrypoints/main/MainImpl.ts +4 -2
- package/front_end/entrypoints/main/main-meta.ts +16 -0
- package/front_end/generated/InspectorBackendCommands.js +8 -7
- package/front_end/generated/SupportedCSSProperties.js +7 -1
- package/front_end/generated/protocol-mapping.d.ts +5 -24
- package/front_end/generated/protocol-proxy-api.d.ts +6 -29
- package/front_end/generated/protocol.d.ts +51 -46
- package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +0 -6
- package/front_end/models/issues_manager/CorsIssue.ts +4 -0
- package/front_end/models/logs/LogManager.ts +1 -0
- package/front_end/models/persistence/WorkspaceSettingsTab.ts +6 -4
- package/front_end/models/persistence/workspaceSettingsTab.css +18 -18
- package/front_end/models/timeline_model/TimelineFrameModel.ts +107 -28
- package/front_end/panels/application/ReportingApiReportsView.ts +89 -0
- package/front_end/panels/application/ReportingApiTreeElement.ts +3 -3
- package/front_end/panels/application/ReportingApiView.ts +27 -0
- package/front_end/panels/application/application.ts +2 -0
- package/front_end/panels/application/components/EndpointsGrid.ts +55 -0
- package/front_end/panels/application/components/ReportsGrid.ts +144 -0
- package/front_end/panels/application/components/components.ts +4 -2
- package/front_end/panels/application/components/reportingApiGrid.css +35 -0
- package/front_end/panels/application/reportingApiReportsView.css +13 -0
- package/front_end/panels/console/ConsoleView.ts +17 -0
- package/front_end/panels/console/console-meta.ts +26 -0
- package/front_end/panels/elements/ElementsTreeElement.ts +19 -0
- package/front_end/panels/elements/PropertiesWidget.ts +1 -2
- package/front_end/panels/elements/StylePropertyTreeElement.ts +28 -0
- package/front_end/panels/elements/StylePropertyUtils.ts +13 -0
- package/front_end/panels/elements/components/nodeText.css +4 -4
- package/front_end/panels/elements/elements.ts +2 -0
- package/front_end/panels/elements/layoutPane.css +1 -1
- package/front_end/panels/issues/CorsIssueDetailsView.ts +4 -2
- package/front_end/panels/network/RequestCookiesView.ts +13 -4
- package/front_end/panels/screencast/screencastView.css +2 -6
- package/front_end/panels/search/SearchResultsPane.ts +1 -1
- package/front_end/panels/settings/SettingsScreen.ts +3 -0
- package/front_end/panels/snippets/SnippetsQuickOpen.ts +8 -3
- package/front_end/panels/sources/CallStackSidebarPane.ts +1 -10
- package/front_end/panels/sources/GoToLineQuickOpen.ts +50 -10
- package/front_end/panels/sources/UISourceCodeFrame.ts +0 -13
- package/front_end/panels/sources/sources-legacy.ts +0 -11
- package/front_end/panels/sources/sources-meta.ts +22 -20
- package/front_end/panels/sources/sources.ts +0 -2
- package/front_end/third_party/codemirror.next/LICENSE +21 -0
- package/front_end/third_party/codemirror.next/README.chromium +18 -0
- package/front_end/third_party/codemirror.next/bundle-tsconfig.json +21 -0
- package/front_end/third_party/codemirror.next/bundle.ts +87 -0
- package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -0
- package/front_end/third_party/codemirror.next/chunk/cpp.js +2 -0
- package/front_end/third_party/codemirror.next/chunk/css.js +2 -0
- package/front_end/third_party/codemirror.next/chunk/html.js +4 -0
- package/front_end/third_party/codemirror.next/chunk/java.js +2 -0
- package/front_end/third_party/codemirror.next/chunk/javascript.js +2 -0
- package/front_end/third_party/codemirror.next/chunk/json.js +2 -0
- package/front_end/third_party/codemirror.next/chunk/legacy.js +1 -0
- package/front_end/third_party/codemirror.next/chunk/markdown.js +6 -0
- package/front_end/third_party/codemirror.next/chunk/php.js +6 -0
- package/front_end/third_party/codemirror.next/chunk/python.js +2 -0
- package/front_end/third_party/codemirror.next/chunk/wast.js +2 -0
- package/front_end/third_party/codemirror.next/chunk/xml.js +2 -0
- package/front_end/third_party/codemirror.next/codemirror.next.d.ts +5467 -0
- package/front_end/third_party/codemirror.next/codemirror.next.js +2 -0
- package/front_end/third_party/codemirror.next/package.json +39 -0
- package/front_end/third_party/codemirror.next/rebuild.sh +6 -0
- package/front_end/third_party/codemirror.next/rollup.config.js +45 -0
- package/front_end/ui/components/buttons/Button.ts +33 -5
- package/front_end/ui/components/buttons/button.css +32 -2
- 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/button/basic.html +1 -0
- package/front_end/ui/components/docs/button/basic.ts +47 -4
- 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/UIUtils.ts +9 -1
- package/front_end/ui/legacy/components/quick_open/CommandMenu.ts +8 -3
- package/front_end/ui/legacy/components/quick_open/FilteredListWidget.ts +39 -39
- package/front_end/ui/legacy/components/quick_open/HelpQuickOpen.ts +10 -4
- package/front_end/ui/legacy/components/quick_open/QuickOpen.ts +31 -14
- package/front_end/ui/legacy/components/quick_open/filteredListWidget.css +7 -8
- package/front_end/ui/legacy/components/source_frame/source_frame-legacy.ts +0 -6
- package/front_end/ui/legacy/components/source_frame/source_frame.ts +0 -2
- package/front_end/ui/legacy/components/utils/JSPresentationUtils.ts +11 -9
- package/front_end/ui/legacy/filter.css +1 -0
- package/front_end/ui/legacy/inspectorSyntaxHighlight.css +3 -8
- package/front_end/ui/legacy/inspectorSyntaxHighlightDark.css +11 -16
- package/front_end/ui/legacy/themeColors.css +60 -0
- package/inspector_overlay/debug/tool_persistent_isolated_element.html +75 -0
- package/inspector_overlay/drag_resize_handler.ts +142 -0
- package/inspector_overlay/highlight_isolated_element.ts +62 -0
- package/inspector_overlay/main.ts +4 -1
- package/inspector_overlay/tool_highlight.ts +6 -0
- package/inspector_overlay/tool_paused.ts +2 -0
- package/inspector_overlay/tool_persistent.ts +110 -0
- package/inspector_overlay/tool_screenshot.ts +8 -1
- package/package.json +1 -1
- package/front_end/panels/application/components/ReportingApiView.ts +0 -24
- package/front_end/panels/sources/GutterDiffPlugin.ts +0 -282
- package/front_end/ui/legacy/components/source_frame/SourceCodeDiff.ts +0 -140
|
@@ -0,0 +1,161 @@
|
|
|
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 CodeMirror from '../../../third_party/codemirror.next/codemirror.next.js';
|
|
7
|
+
import * as LitHtml from '../../lit-html/lit-html.js';
|
|
8
|
+
import * as CodeHighlighter from '../code_highlighter/code_highlighter.js';
|
|
9
|
+
import * as ComponentHelpers from '../helpers/helpers.js';
|
|
10
|
+
|
|
11
|
+
import {baseConfiguration, dynamicSetting} from './config.js';
|
|
12
|
+
|
|
13
|
+
declare global {
|
|
14
|
+
interface HTMLElementTagNameMap {
|
|
15
|
+
'devtools-text-editor': TextEditor;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class TextEditor extends HTMLElement {
|
|
20
|
+
static readonly litTagName = LitHtml.literal`devtools-text-editor`;
|
|
21
|
+
|
|
22
|
+
private readonly shadow = this.attachShadow({mode: 'open'});
|
|
23
|
+
private activeEditor: CodeMirror.EditorView|undefined = undefined;
|
|
24
|
+
private activeSettingListeners: [Common.Settings.Setting<unknown>, (event: {data: unknown}) => void][] = [];
|
|
25
|
+
private pendingState: CodeMirror.EditorState|undefined;
|
|
26
|
+
|
|
27
|
+
constructor(pendingState?: CodeMirror.EditorState) {
|
|
28
|
+
super();
|
|
29
|
+
this.pendingState = pendingState;
|
|
30
|
+
this.shadow.adoptedStyleSheets = [CodeHighlighter.Style.default];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private createEditor(): CodeMirror.EditorView {
|
|
34
|
+
this.activeEditor = new CodeMirror.EditorView({
|
|
35
|
+
state: this.updateDynamicSettings(this.state),
|
|
36
|
+
parent: this.shadow,
|
|
37
|
+
root: this.shadow,
|
|
38
|
+
});
|
|
39
|
+
return this.activeEditor;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get editor(): CodeMirror.EditorView {
|
|
43
|
+
return this.activeEditor || this.createEditor();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get state(): CodeMirror.EditorState {
|
|
47
|
+
if (this.activeEditor) {
|
|
48
|
+
return this.activeEditor.state;
|
|
49
|
+
}
|
|
50
|
+
if (!this.pendingState) {
|
|
51
|
+
this.pendingState = CodeMirror.EditorState.create({extensions: baseConfiguration('')});
|
|
52
|
+
}
|
|
53
|
+
return this.pendingState;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
set state(state: CodeMirror.EditorState) {
|
|
57
|
+
if (this.activeEditor) {
|
|
58
|
+
this.activeEditor.setState(state);
|
|
59
|
+
} else {
|
|
60
|
+
this.pendingState = state;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
connectedCallback(): void {
|
|
65
|
+
if (!this.activeEditor) {
|
|
66
|
+
this.createEditor();
|
|
67
|
+
}
|
|
68
|
+
this.registerSettingHandlers();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
disconnectedCallback(): void {
|
|
72
|
+
if (this.activeEditor) {
|
|
73
|
+
this.pendingState = this.activeEditor.state;
|
|
74
|
+
this.activeEditor.destroy();
|
|
75
|
+
this.activeEditor = undefined;
|
|
76
|
+
}
|
|
77
|
+
for (const [setting, listener] of this.activeSettingListeners) {
|
|
78
|
+
setting.removeChangeListener(listener);
|
|
79
|
+
}
|
|
80
|
+
this.activeSettingListeners = [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private updateDynamicSettings(state: CodeMirror.EditorState): CodeMirror.EditorState {
|
|
84
|
+
const settings = Common.Settings.Settings.instance();
|
|
85
|
+
const changes = [];
|
|
86
|
+
for (const opt of state.facet(dynamicSetting)) {
|
|
87
|
+
const mustUpdate = opt.sync(state, settings.moduleSetting(opt.settingName).get());
|
|
88
|
+
if (mustUpdate) {
|
|
89
|
+
changes.push(mustUpdate);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return changes.length ? state.update({effects: changes}).state : state;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private registerSettingHandlers(): void {
|
|
96
|
+
const settings = Common.Settings.Settings.instance();
|
|
97
|
+
for (const opt of this.state.facet(dynamicSetting)) {
|
|
98
|
+
const handler = ({data}: {data: unknown}): void => {
|
|
99
|
+
const change = opt.sync(this.state, data);
|
|
100
|
+
if (change && this.activeEditor) {
|
|
101
|
+
this.activeEditor.dispatch({effects: change});
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const setting = settings.moduleSetting(opt.settingName);
|
|
105
|
+
setting.addChangeListener(handler);
|
|
106
|
+
this.activeSettingListeners.push([setting, handler]);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
revealPosition(position: number): void {
|
|
111
|
+
const view = this.activeEditor;
|
|
112
|
+
if (!view) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const line = view.state.doc.lineAt(position);
|
|
117
|
+
view.dispatch({
|
|
118
|
+
selection: CodeMirror.EditorSelection.cursor(position),
|
|
119
|
+
scrollIntoView: true,
|
|
120
|
+
effects:
|
|
121
|
+
[view.state.field(highlightState, false) ?
|
|
122
|
+
setHighlightLine.of(line.from) :
|
|
123
|
+
CodeMirror.StateEffect.appendConfig.of(highlightState.init(() => highlightDeco(line.from)))],
|
|
124
|
+
});
|
|
125
|
+
const {id} = view.state.field(highlightState);
|
|
126
|
+
// Reset the highlight state if, after 2 seconds (the animation
|
|
127
|
+
// duration) it is still showing this highlight.
|
|
128
|
+
setTimeout(() => {
|
|
129
|
+
if (view.state.field(highlightState).id === id) {
|
|
130
|
+
view.dispatch({effects: setHighlightLine.of(null)});
|
|
131
|
+
}
|
|
132
|
+
}, 2000);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
ComponentHelpers.CustomElements.defineComponent('devtools-text-editor', TextEditor);
|
|
137
|
+
|
|
138
|
+
const setHighlightLine = CodeMirror.StateEffect.define<number|null>(
|
|
139
|
+
{map: (value, mapping) => value === null ? null : mapping.mapPos(value)});
|
|
140
|
+
|
|
141
|
+
function highlightDeco(position: number): {deco: CodeMirror.DecorationSet, id: number} {
|
|
142
|
+
const deco = CodeMirror.Decoration.set(
|
|
143
|
+
[CodeMirror.Decoration.line({attributes: {class: 'cm-highlightedLine'}}).range(position)]);
|
|
144
|
+
return {deco, id: Math.floor(Math.random() * 0xfffff)};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const highlightState = CodeMirror.StateField.define<{deco: CodeMirror.DecorationSet, id: number}>({
|
|
148
|
+
create: () => ({deco: CodeMirror.Decoration.none, id: 0}),
|
|
149
|
+
update(value, tr) {
|
|
150
|
+
if (!tr.changes.empty && value.deco.size) {
|
|
151
|
+
value = {deco: value.deco.map(tr.changes), id: value.id};
|
|
152
|
+
}
|
|
153
|
+
for (const effect of tr.effects) {
|
|
154
|
+
if (effect.is(setHighlightLine)) {
|
|
155
|
+
value = effect.value === null ? {deco: CodeMirror.Decoration.none, id: 0} : highlightDeco(effect.value);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return value;
|
|
159
|
+
},
|
|
160
|
+
provide: field => CodeMirror.EditorView.decorations.from(field, value => value.deco),
|
|
161
|
+
});
|
|
@@ -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
|
+
}
|