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 */
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() {
super();
this.cbPanel = null;
this.breakpointPanelEditor = null;
}
static get propTypes() {
@ -49,28 +50,68 @@ export class ConditionalPanel extends PureComponent {
};
}
removeBreakpointPanelEditor() {
if (this.breakpointPanelEditor) {
this.breakpointPanelEditor.destroy();
}
this.breakpointPanelEditor = null;
}
keepFocusOnInput() {
if (this.input) {
this.input.focus();
} else if (this.breakpointPanelEditor) {
this.breakpointPanelEditor.focus();
}
}
saveAndClose = () => {
if (this.input) {
this.setBreakpoint(this.input.value.trim());
/**
* Set the breakpoint/logpoint if expression isn't empty, and close the panel.
*
* @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();
};
/**
* Handle inline editor keydown event
*
* @param {Event} e: The keydown event
*/
onKey = e => {
if (e.key === "Enter" && !e.shiftKey) {
this.saveAndClose();
this.saveAndClose(this.input?.value);
} else if (e.key === "Escape") {
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) {
const { log, breakpoint } = this.props;
// If breakpoint is `pending`, props will not contain a breakpoint.
@ -105,17 +146,20 @@ export class ConditionalPanel extends PureComponent {
};
showConditionalPanel(prevProps) {
const { location, editor, breakpoint, selectedSource } = this.props;
const { location, log, editor, breakpoint, selectedSource } = this.props;
if (!selectedSource || !location) {
this.removeBreakpointPanelEditor();
return;
}
// When breakpoint is removed
if (prevProps?.breakpoint && !breakpoint) {
editor.removeLineContentMarker(markerTypes.CONDITIONAL_BP_MARKER);
this.removeBreakpointPanelEditor();
return;
}
if (selectedSource.id !== location.source.id) {
editor.removeLineContentMarker(markerTypes.CONDITIONAL_BP_MARKER);
this.removeBreakpointPanelEditor();
return;
}
const line = toEditorLine(location.source.id, location.line || 0);
@ -124,12 +168,42 @@ export class ConditionalPanel extends PureComponent {
lines: [{ line }],
renderAsBlock: true,
createLineElementNode: () => {
// Create a Codemirror 5 editor for the breakpoint panel
// TODO: Switch to use Codemirror 6 version Bug 1890205
const breakpointPanelEditor = createEditor();
breakpointPanelEditor.appendToLocalElement(
document.createElement("div")
);
// Create a Codemirror editor for the breakpoint panel
const onEnterKeyMapConfig = {
preventDefault: true,
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);
},
});
@ -168,6 +242,7 @@ export class ConditionalPanel extends PureComponent {
} else {
this.clearConditionalPanel();
}
this.removeBreakpointPanelEditor();
}
renderToWidget(props) {
@ -209,57 +284,55 @@ export class ConditionalPanel extends PureComponent {
}
}
createEditor = (input, editor) => {
const { log, closeConditionalPanel } = 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,
});
setupAndAppendInlineEditor = (el, editor) => {
const { log } = this.props;
codeMirror.on("keydown", (cm, e) => {
if (e.key === "Enter") {
e.codemirrorIgnore = true;
}
});
if (features.codemirrorNext) {
editor.appendToLocalElement(el);
editor.on("blur", e => this.onBlur(e));
codeMirror.on("blur", (cm, 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;
}
editor.setText(this.getDefaultValue());
editor.focus();
editor.selectAll();
} else {
const codeMirror = editor.CodeMirror.fromTextArea(el, {
mode: "javascript",
theme: "mozilla",
placeholder: L10N.getStr(
log
? "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 => {
codeMirror.save();
this.onKey(e);
});
const codeMirrorWrapper = codeMirror.getWrapperElement();
this.input = input;
this.codeMirror = codeMirror;
codeMirror.focus();
codeMirror.execCommand("selectAll");
codeMirrorWrapper.addEventListener("keydown", e => {
codeMirror.save();
this.onKey(e);
});
this.input = el;
this.codeMirror = codeMirror;
codeMirror.focus();
codeMirror.execCommand("selectAll");
}
};
getDefaultValue() {
const { breakpoint, log } = this.props;
const options = breakpoint?.options || {};
return log ? options.logValue : options.condition;
const value = log ? options.logValue : options.condition;
return value || "";
}
renderConditionalPanel(props, editor) {
@ -285,10 +358,15 @@ export class ConditionalPanel extends PureComponent {
},
"»"
),
textarea({
defaultValue,
ref: input => this.createEditor(input, editor),
})
features.codemirrorNext
? div({
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 { 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"];
if (features.codeFolding) {
@ -14,7 +20,6 @@ export function createEditor(useCm6 = false) {
return new SourceEditor({
mode: SourceEditor.modes.js,
cm6: useCm6,
foldGutter: features.codeFolding,
enableCodeFolding: features.codeFolding,
readOnly: true,
@ -37,6 +42,7 @@ export function createEditor(useCm6 = false) {
"Ctrl-G": false,
},
cursorBlinkRate: prefs.cursorBlinkRate,
...config,
});
}
@ -47,7 +53,7 @@ export function createEditor(useCm6 = false) {
* @returns {CodeMirror}
*/
export function createHeadlessEditor(useCm6) {
const editor = createEditor(useCm6);
const editor = createEditor({ cm6: useCm6 });
editor.appendToLocalElement(document.createElement("div"));
return editor;
}

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

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

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

@ -647,8 +647,14 @@ class Editor extends EventEmitter {
const {
codemirror,
codemirrorView: { EditorView, lineNumbers, drawSelection },
codemirrorState: { EditorState, Compartment },
codemirrorView: {
drawSelection,
EditorView,
keymap,
lineNumbers,
placeholder,
},
codemirrorState: { EditorState, Compartment, Prec },
codemirrorSearch: { highlightSelectionMatches },
codemirrorLanguage: {
syntaxTreeAvailable,
@ -750,6 +756,14 @@ class Editor extends EventEmitter {
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)) {
// We need to multiply the preference value by 2 to match Firefox cursor rate
const cursorBlinkRate = Services.prefs.getIntPref(CARET_BLINK_TIME) * 2;
@ -768,6 +782,11 @@ class Editor extends EventEmitter {
cm.isDocumentLoadComplete = false;
this.#ownerDoc.sourceEditor = { editor: 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;
}
/**
* 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