Bug 1884651 - [devtools] Rendering debug lines when paused in code mirror 6 r=devtools-reviewers,nchevobbe

In this patch
- we begin to render debug lines when paused.
- New APIs are added to add and remove line content markers

Differential Revision: https://phabricator.services.mozilla.com/D204243
This commit is contained in:
Hubert Boma Manilla 2024-03-25 13:47:42 +00:00
parent f62619c3ac
commit 334badc332
4 changed files with 165 additions and 11 deletions

View File

@ -6,6 +6,7 @@ import { PureComponent } from "devtools/client/shared/vendor/react";
import PropTypes from "devtools/client/shared/vendor/react-prop-types";
import {
toEditorPosition,
fromEditorLine,
getDocument,
hasDocument,
startOperation,
@ -21,12 +22,16 @@ import {
getSourceTextContent,
getCurrentThread,
} from "../../selectors/index";
import { isWasm } from "../../utils/wasm";
import { features } from "../../utils/prefs";
export class DebugLine extends PureComponent {
debugExpression;
static get propTypes() {
return {
editor: PropTypes.object,
selectedSource: PropTypes.object,
location: PropTypes.object,
why: PropTypes.object,
};
@ -34,21 +39,62 @@ export class DebugLine extends PureComponent {
componentDidMount() {
const { why, location } = this.props;
if (features.codemirrorNext) {
return;
}
this.setDebugLine(why, location);
}
componentWillUnmount() {
const { why, location } = this.props;
if (features.codemirrorNext) {
return;
}
this.clearDebugLine(why, location);
}
componentDidUpdate(prevProps) {
const { why, location } = this.props;
const { why, location, editor, selectedSource } = this.props;
startOperation();
this.clearDebugLine(prevProps.why, prevProps.location);
this.setDebugLine(why, location);
endOperation();
if (!selectedSource) {
return;
}
if (
prevProps.location == this.props.location &&
prevProps.selectedSource?.id == selectedSource?.id
) {
return;
}
if (features.codemirrorNext) {
const { lineClass } = this.getTextClasses(why);
// Remove the debug line marker when no longer paused, or the selected source
// is no longer the source where the pause occured.
if (!location || location.source.id !== selectedSource.id) {
editor.removeLineContentMarker("debug-line-marker");
} else {
const isSourceWasm = isWasm(selectedSource.id);
editor.setLineContentMarker({
id: "debug-line-marker",
lineClassName: lineClass,
condition(line) {
const lineNumber = fromEditorLine(
selectedSource.id,
line,
isSourceWasm
);
const editorLocation = toEditorPosition(location);
return editorLocation.line == lineNumber;
},
});
}
} else {
startOperation();
this.clearDebugLine(prevProps.why, prevProps.location);
this.setDebugLine(why, location);
endOperation();
}
}
setDebugLine(why, location) {
@ -125,7 +171,10 @@ const mapStateToProps = state => {
return {};
}
const sourceTextContent = getSourceTextContent(state, location);
if (!isDocumentReady(location, sourceTextContent)) {
if (
!features.codemirrorNext &&
!isDocumentReady(location, sourceTextContent)
) {
return {};
}
return {

View File

@ -121,7 +121,9 @@ html[dir="rtl"] .editor-mount {
background-color: var(--debug-expression-error-background);
}
.new-debug-line > .CodeMirror-line {
.new-debug-line > .CodeMirror-line,
/* For CM6 */
.cm-editor .cm-line.new-debug-line {
background-color: transparent !important;
outline: var(--debug-line-border) solid 1px;
}
@ -132,7 +134,9 @@ html[dir="rtl"] .editor-mount {
display: none;
}
.new-debug-line-error > .CodeMirror-line {
.new-debug-line-error > .CodeMirror-line,
/* For CM6 */
.cm-editor .cm-line.new-debug-line-error {
background-color: var(--debug-expression-error-background) !important;
outline: var(--debug-line-error-border) solid 1px;
}

View File

@ -794,9 +794,14 @@ class Editor extends PureComponent {
const { editor } = this.state;
if (features.codemirrorNext) {
return React.createElement(Breakpoints, {
editor,
});
return React.createElement(
React.Fragment,
null,
React.createElement(Breakpoints, {
editor,
}),
React.createElement(DebugLine, { editor, selectedSource })
);
}
if (!selectedSource || !editor || !getDocument(selectedSource.id)) {

View File

@ -167,6 +167,7 @@ class Editor extends EventEmitter {
#prefObserver;
#win;
#lineGutterMarkers = new Map();
#lineContentMarkers = new Map();
#updateListener = null;
@ -625,6 +626,7 @@ class Editor extends EventEmitter {
const lineWrapCompartment = new Compartment();
const lineNumberCompartment = new Compartment();
const lineNumberMarkersCompartment = new Compartment();
const lineContentMarkerCompartment = new Compartment();
this.#compartments = {
tabSizeCompartment,
@ -632,6 +634,7 @@ class Editor extends EventEmitter {
lineWrapCompartment,
lineNumberCompartment,
lineNumberMarkersCompartment,
lineContentMarkerCompartment,
};
const indentStr = (this.config.indentWithTabs ? "\t" : " ").repeat(
@ -673,6 +676,7 @@ class Editor extends EventEmitter {
}
}),
lineNumberMarkersCompartment.of([]),
lineContentMarkerCompartment.of(this.#lineContentMarkersExtension([])),
// keep last so other extension take precedence
codemirror.minimalSetup,
];
@ -689,6 +693,98 @@ class Editor extends EventEmitter {
editors.set(this, cm);
}
/**
* This creates the extension used to manage the rendering of markers
* for in editor line content.
* @param {Array} markers - The current list of markers
* @returns {Array<ViewPlugin>} showLineContentDecorations - An extension which is an array containing the view
* which manages the rendering of the line content markers.
*/
#lineContentMarkersExtension(markers) {
const {
codemirrorView: { Decoration, ViewPlugin },
codemirrorState: { RangeSetBuilder },
} = this.#CodeMirror6;
// Build and return the decoration set
function buildDecorations(view) {
const builder = new RangeSetBuilder();
for (const { from, to } of view.visibleRanges) {
for (let pos = from; pos <= to; ) {
const line = view.state.doc.lineAt(pos);
for (const { lineClassName, condition } of markers) {
if (condition(line.number)) {
builder.add(
line.from,
line.from,
Decoration.line({ class: lineClassName })
);
}
}
pos = line.to + 1;
}
}
return builder.finish();
}
// The view which handles rendering and updating the
// markers decorations
const showLineContentDecorations = ViewPlugin.fromClass(
class {
decorations;
constructor(view) {
this.decorations = buildDecorations(view);
}
update(update) {
if (update.docChanged || update.viewportChanged) {
this.decorations = buildDecorations(update.view);
}
}
},
{ decorations: v => v.decorations }
);
return [showLineContentDecorations];
}
/**
* This adds a marker used to add classes to editor line based on a condition.
* @property {object} marker - The rule rendering a marker or class.
* @property {object} marker.id - The unique identifier for this marker
* @property {string} marker.lineClassName - The css class to add to the line
* @property {function} marker.condition - The condition that decides if the marker/class gets added or removed.
* The line is passed as an argument.
*/
setLineContentMarker(marker) {
const cm = editors.get(this);
this.#lineContentMarkers.set(marker.id, marker);
cm.dispatch({
effects: this.#compartments.lineContentMarkerCompartment.reconfigure(
this.#lineContentMarkersExtension(
Array.from(this.#lineContentMarkers.values())
)
),
});
}
/**
* This removes the marker which has the specified className
* @param {string} markerId - The unique identifier for this marker
*/
removeLineContentMarker(markerId) {
const cm = editors.get(this);
this.#lineContentMarkers.delete(markerId);
cm.dispatch({
effects: this.#compartments.lineContentMarkerCompartment.reconfigure(
this.#lineContentMarkersExtension(
Array.from(this.#lineContentMarkers.values())
)
),
});
}
/**
* Set event listeners for the line gutter
* @param {Object} domEventHandlers