Bug 1913189 - [devtools] Use CodeMirror 6 in conditional breakpoint. r=bomsy.

Differential Revision: https://phabricator.services.mozilla.com/D220685
This commit is contained in:
Nicolas Chevobbe 2024-09-06 05:12:26 +00:00
Родитель f3480bca05
Коммит 1aa477dd15
5 изменённых файлов: 197 добавлений и 61 удалений

Просмотреть файл

@ -47,3 +47,13 @@
/* Match the color of the placeholder text to existing inputs in the Debugger */ /* Match the color of the placeholder text to existing inputs in the Debugger */
color: var(--theme-text-color-alt); color: var(--theme-text-color-alt);
} }
/* cm6 style */
.conditional-breakpoint-panel .inline-codemirror-container {
flex: 1 1 100%;
/* We already set an outline on the conditional panel, so hide the default codemirror one */
.cm-editor.cm-focused {
outline: none;
}
}

Просмотреть файл

@ -34,6 +34,7 @@ export class ConditionalPanel extends PureComponent {
constructor() { constructor() {
super(); super();
this.cbPanel = null; this.cbPanel = null;
this.breakpointPanelEditor = null;
} }
static get propTypes() { static get propTypes() {
@ -49,28 +50,68 @@ export class ConditionalPanel extends PureComponent {
}; };
} }
removeBreakpointPanelEditor() {
if (this.breakpointPanelEditor) {
this.breakpointPanelEditor.destroy();
}
this.breakpointPanelEditor = null;
}
keepFocusOnInput() { keepFocusOnInput() {
if (this.input) { if (this.input) {
this.input.focus(); this.input.focus();
} else if (this.breakpointPanelEditor) {
this.breakpointPanelEditor.focus();
} }
} }
saveAndClose = () => { /**
if (this.input) { * Set the breakpoint/logpoint if expression isn't empty, and close the panel.
this.setBreakpoint(this.input.value.trim()); *
* @param {String} expression: The expression that will be used for setting the
* conditional breakpoint/logpoint
*/
saveAndClose = (expression = null) => {
if (expression) {
this.setBreakpoint(expression.trim());
} }
this.props.closeConditionalPanel(); this.props.closeConditionalPanel();
}; };
/**
* Handle inline editor keydown event
*
* @param {Event} e: The keydown event
*/
onKey = e => { onKey = e => {
if (e.key === "Enter" && !e.shiftKey) { if (e.key === "Enter" && !e.shiftKey) {
this.saveAndClose(); this.saveAndClose(this.input?.value);
} else if (e.key === "Escape") { } else if (e.key === "Escape") {
this.props.closeConditionalPanel(); this.props.closeConditionalPanel();
} }
}; };
/**
* Handle inline editor blur event
*
* @param {Event} e: The blur event
*/
onBlur = e => {
if (
// if there is no event
// or if the focus is the conditional panel
// do not close the conditional panel
!e ||
(e?.relatedTarget &&
e.relatedTarget.closest(".conditional-breakpoint-panel"))
) {
return;
}
this.props.closeConditionalPanel();
};
setBreakpoint(value) { setBreakpoint(value) {
const { log, breakpoint } = this.props; const { log, breakpoint } = this.props;
// If breakpoint is `pending`, props will not contain a breakpoint. // If breakpoint is `pending`, props will not contain a breakpoint.
@ -105,17 +146,20 @@ export class ConditionalPanel extends PureComponent {
}; };
showConditionalPanel(prevProps) { showConditionalPanel(prevProps) {
const { location, editor, breakpoint, selectedSource } = this.props; const { location, log, editor, breakpoint, selectedSource } = this.props;
if (!selectedSource || !location) { if (!selectedSource || !location) {
this.removeBreakpointPanelEditor();
return; return;
} }
// When breakpoint is removed // When breakpoint is removed
if (prevProps?.breakpoint && !breakpoint) { if (prevProps?.breakpoint && !breakpoint) {
editor.removeLineContentMarker(markerTypes.CONDITIONAL_BP_MARKER); editor.removeLineContentMarker(markerTypes.CONDITIONAL_BP_MARKER);
this.removeBreakpointPanelEditor();
return; return;
} }
if (selectedSource.id !== location.source.id) { if (selectedSource.id !== location.source.id) {
editor.removeLineContentMarker(markerTypes.CONDITIONAL_BP_MARKER); editor.removeLineContentMarker(markerTypes.CONDITIONAL_BP_MARKER);
this.removeBreakpointPanelEditor();
return; return;
} }
const line = toEditorLine(location.source.id, location.line || 0); const line = toEditorLine(location.source.id, location.line || 0);
@ -124,12 +168,42 @@ export class ConditionalPanel extends PureComponent {
lines: [{ line }], lines: [{ line }],
renderAsBlock: true, renderAsBlock: true,
createLineElementNode: () => { createLineElementNode: () => {
// Create a Codemirror 5 editor for the breakpoint panel // Create a Codemirror editor for the breakpoint panel
// TODO: Switch to use Codemirror 6 version Bug 1890205
const breakpointPanelEditor = createEditor(); const onEnterKeyMapConfig = {
breakpointPanelEditor.appendToLocalElement( preventDefault: true,
document.createElement("div") stopPropagation: true,
); run: () => this.saveAndClose(breakpointPanelEditor.getText(null)),
};
const breakpointPanelEditor = createEditor({
cm6: features.codemirrorNext,
readOnly: false,
lineNumbers: false,
placeholder: L10N.getStr(
log
? "editor.conditionalPanel.logPoint.placeholder2"
: "editor.conditionalPanel.placeholder2"
),
keyMap: [
{
key: "Enter",
...onEnterKeyMapConfig,
},
{
key: "Mod-Enter",
...onEnterKeyMapConfig,
},
{
key: "Escape",
preventDefault: true,
stopPropagation: true,
run: () => this.props.closeConditionalPanel(),
},
],
});
this.breakpointPanelEditor = breakpointPanelEditor;
return this.renderConditionalPanel(this.props, breakpointPanelEditor); return this.renderConditionalPanel(this.props, breakpointPanelEditor);
}, },
}); });
@ -168,6 +242,7 @@ export class ConditionalPanel extends PureComponent {
} else { } else {
this.clearConditionalPanel(); this.clearConditionalPanel();
} }
this.removeBreakpointPanelEditor();
} }
renderToWidget(props) { renderToWidget(props) {
@ -209,57 +284,55 @@ export class ConditionalPanel extends PureComponent {
} }
} }
createEditor = (input, editor) => { setupAndAppendInlineEditor = (el, editor) => {
const { log, closeConditionalPanel } = this.props; const { log } = this.props;
const codeMirror = editor.CodeMirror.fromTextArea(input, {
mode: "javascript",
theme: "mozilla",
placeholder: L10N.getStr(
log
? "editor.conditionalPanel.logPoint.placeholder2"
: "editor.conditionalPanel.placeholder2"
),
cursorBlinkRate: prefs.cursorBlinkRate,
});
codeMirror.on("keydown", (cm, e) => { if (features.codemirrorNext) {
if (e.key === "Enter") { editor.appendToLocalElement(el);
e.codemirrorIgnore = true; editor.on("blur", e => this.onBlur(e));
}
});
codeMirror.on("blur", (cm, e) => { editor.setText(this.getDefaultValue());
if ( editor.focus();
// if there is no event editor.selectAll();
// or if the focus is the conditional panel } else {
// do not close the conditional panel const codeMirror = editor.CodeMirror.fromTextArea(el, {
!e || mode: "javascript",
(e?.relatedTarget && theme: "mozilla",
e.relatedTarget.closest(".conditional-breakpoint-panel")) placeholder: L10N.getStr(
) { log
return; ? "editor.conditionalPanel.logPoint.placeholder2"
} : "editor.conditionalPanel.placeholder2"
),
cursorBlinkRate: prefs.cursorBlinkRate,
});
closeConditionalPanel(); codeMirror.on("keydown", (cm, e) => {
}); if (e.key === "Enter") {
e.codemirrorIgnore = true;
}
});
const codeMirrorWrapper = codeMirror.getWrapperElement(); codeMirror.on("blur", (cm, e) => this.onBlur(e));
codeMirrorWrapper.addEventListener("keydown", e => { const codeMirrorWrapper = codeMirror.getWrapperElement();
codeMirror.save();
this.onKey(e);
});
this.input = input; codeMirrorWrapper.addEventListener("keydown", e => {
this.codeMirror = codeMirror; codeMirror.save();
codeMirror.focus(); this.onKey(e);
codeMirror.execCommand("selectAll"); });
this.input = el;
this.codeMirror = codeMirror;
codeMirror.focus();
codeMirror.execCommand("selectAll");
}
}; };
getDefaultValue() { getDefaultValue() {
const { breakpoint, log } = this.props; const { breakpoint, log } = this.props;
const options = breakpoint?.options || {}; const options = breakpoint?.options || {};
return log ? options.logValue : options.condition; const value = log ? options.logValue : options.condition;
return value || "";
} }
renderConditionalPanel(props, editor) { renderConditionalPanel(props, editor) {
@ -285,10 +358,15 @@ export class ConditionalPanel extends PureComponent {
}, },
"»" "»"
), ),
textarea({ features.codemirrorNext
defaultValue, ? div({
ref: input => this.createEditor(input, editor), className: "inline-codemirror-container",
}) ref: el => this.setupAndAppendInlineEditor(el, editor),
})
: textarea({
defaultValue,
ref: input => this.setupAndAppendInlineEditor(input, editor),
})
) )
); );

Просмотреть файл

@ -5,7 +5,13 @@
import SourceEditor from "devtools/client/shared/sourceeditor/editor"; import SourceEditor from "devtools/client/shared/sourceeditor/editor";
import { features, prefs } from "../prefs"; import { features, prefs } from "../prefs";
export function createEditor(useCm6 = false) { /**
* Create a SourceEditor
*
* @param {Object} config: SourceEditor config object
* @returns
*/
export function createEditor(config = { cm6: false }) {
const gutters = ["breakpoints", "hit-markers", "CodeMirror-linenumbers"]; const gutters = ["breakpoints", "hit-markers", "CodeMirror-linenumbers"];
if (features.codeFolding) { if (features.codeFolding) {
@ -14,7 +20,6 @@ export function createEditor(useCm6 = false) {
return new SourceEditor({ return new SourceEditor({
mode: SourceEditor.modes.js, mode: SourceEditor.modes.js,
cm6: useCm6,
foldGutter: features.codeFolding, foldGutter: features.codeFolding,
enableCodeFolding: features.codeFolding, enableCodeFolding: features.codeFolding,
readOnly: true, readOnly: true,
@ -37,6 +42,7 @@ export function createEditor(useCm6 = false) {
"Ctrl-G": false, "Ctrl-G": false,
}, },
cursorBlinkRate: prefs.cursorBlinkRate, cursorBlinkRate: prefs.cursorBlinkRate,
...config,
}); });
} }
@ -47,7 +53,7 @@ export function createEditor(useCm6 = false) {
* @returns {CodeMirror} * @returns {CodeMirror}
*/ */
export function createHeadlessEditor(useCm6) { export function createHeadlessEditor(useCm6) {
const editor = createEditor(useCm6); const editor = createEditor({ cm6: useCm6 });
editor.appendToLocalElement(document.createElement("div")); editor.appendToLocalElement(document.createElement("div"));
return editor; return editor;
} }

Просмотреть файл

@ -20,7 +20,7 @@ export function getEditor(useCm6) {
return editor; return editor;
} }
editor = createEditor(useCm6); editor = createEditor({ cm6: useCm6 });
return editor; return editor;
} }

Просмотреть файл

@ -647,8 +647,14 @@ class Editor extends EventEmitter {
const { const {
codemirror, codemirror,
codemirrorView: { EditorView, lineNumbers, drawSelection }, codemirrorView: {
codemirrorState: { EditorState, Compartment }, drawSelection,
EditorView,
keymap,
lineNumbers,
placeholder,
},
codemirrorState: { EditorState, Compartment, Prec },
codemirrorSearch: { highlightSelectionMatches }, codemirrorSearch: { highlightSelectionMatches },
codemirrorLanguage: { codemirrorLanguage: {
syntaxTreeAvailable, syntaxTreeAvailable,
@ -750,6 +756,14 @@ class Editor extends EventEmitter {
extensions.push(codemirrorLangJavascript.javascript()); extensions.push(codemirrorLangJavascript.javascript());
} }
if (this.config.placeholder) {
extensions.push(placeholder(this.config.placeholder));
}
if (this.config.keyMap) {
extensions.push(Prec.highest(keymap.of(this.config.keyMap)));
}
if (Services.prefs.prefHasUserValue(CARET_BLINK_TIME)) { if (Services.prefs.prefHasUserValue(CARET_BLINK_TIME)) {
// We need to multiply the preference value by 2 to match Firefox cursor rate // We need to multiply the preference value by 2 to match Firefox cursor rate
const cursorBlinkRate = Services.prefs.getIntPref(CARET_BLINK_TIME) * 2; const cursorBlinkRate = Services.prefs.getIntPref(CARET_BLINK_TIME) * 2;
@ -768,6 +782,11 @@ class Editor extends EventEmitter {
cm.isDocumentLoadComplete = false; cm.isDocumentLoadComplete = false;
this.#ownerDoc.sourceEditor = { editor: this, cm }; this.#ownerDoc.sourceEditor = { editor: this, cm };
editors.set(this, cm); editors.set(this, cm);
// For now, we only need to pipe the blur event
cm.contentDOM.addEventListener("blur", e => this.emit("blur", e), {
signal: this.#abortController?.signal,
});
} }
/** /**
@ -3284,6 +3303,29 @@ class Editor extends EventEmitter {
} }
return outputNode.innerHTML; return outputNode.innerHTML;
} }
/**
* Focus the CodeMirror editor
*/
focus() {
const cm = editors.get(this);
cm.focus();
}
/**
* Select the whole document
*/
selectAll() {
const cm = editors.get(this);
if (this.config.cm6) {
cm.dispatch({
selection: { anchor: 0, head: cm.state.doc.length },
userEvent: "select",
});
} else {
cm.execCommand("selectAll");
}
}
} }
// Since Editor is a thin layer over CodeMirror some methods // Since Editor is a thin layer over CodeMirror some methods