From 6f98665d54b9c58333ff424d2e2327cde28df9af Mon Sep 17 00:00:00 2001 From: Hubert Boma Manilla Date: Sun, 19 May 2024 22:28:23 +0000 Subject: [PATCH] Bug 1891336 - [devtools] Highlight text during pause and for exceptions r=devtools-reviewers,nchevobbe Differential Revision: https://phabricator.services.mozilla.com/D207418 --- .../src/components/Editor/DebugLine.js | 9 +++- .../src/components/Editor/Exceptions.js | 11 +++++ devtools/client/shared/sourceeditor/editor.js | 45 +++++++++++++++++-- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/devtools/client/debugger/src/components/Editor/DebugLine.js b/devtools/client/debugger/src/components/Editor/DebugLine.js index 61caae69a522..9a3b1d569d34 100644 --- a/devtools/client/debugger/src/components/Editor/DebugLine.js +++ b/devtools/client/debugger/src/components/Editor/DebugLine.js @@ -68,11 +68,12 @@ export class DebugLine extends PureComponent { return; } - const { lineClass } = this.getTextClasses(why); + const { lineClass, markTextClass } = 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"); + editor.removePositionContentMarker("debug-position-marker"); } else { const isSourceWasm = isWasm(selectedSource.id); editor.setLineContentMarker({ @@ -88,6 +89,12 @@ export class DebugLine extends PureComponent { return editorLocation.line == lineNumber; }, }); + const editorLocation = toEditorPosition(location); + editor.setPositionContentMarker({ + id: "debug-position-marker", + positionClassName: markTextClass, + positions: [editorLocation], + }); } } else { startOperation(); diff --git a/devtools/client/debugger/src/components/Editor/Exceptions.js b/devtools/client/debugger/src/components/Editor/Exceptions.js index 217ec40be6b1..8d82b6016b73 100644 --- a/devtools/client/debugger/src/components/Editor/Exceptions.js +++ b/devtools/client/debugger/src/components/Editor/Exceptions.js @@ -39,6 +39,8 @@ class Exceptions extends Component { } if (!selectedSource || !editor || !exceptions.length) { + editor.removeLineContentMarker("line-exception-marker"); + editor.removePositionContentMarker("exception-position-marker"); return; } @@ -63,6 +65,15 @@ class Exceptions extends Component { return editorLocation.line == lineNumber; }, }); + + editor.setPositionContentMarker({ + id: "exception-position-marker", + positionClassName: "mark-text-exception", + positions: exceptions.map(e => ({ + line: e.lineNumber, + column: e.columnNumber - 1, + })), + }); } render() { diff --git a/devtools/client/shared/sourceeditor/editor.js b/devtools/client/shared/sourceeditor/editor.js index e47c0f51ffe6..0c2df99eca3e 100644 --- a/devtools/client/shared/sourceeditor/editor.js +++ b/devtools/client/shared/sourceeditor/editor.js @@ -866,15 +866,28 @@ class Editor extends EventEmitter { const { codemirrorView: { Decoration, ViewPlugin, WidgetType }, codemirrorState: { RangeSet }, + codemirrorLanguage: { syntaxTree }, } = this.#CodeMirror6; class NodeWidget extends WidgetType { - constructor(line, column, createElementNode) { + constructor(line, column, createElementNode, domNode) { super(); - this.toDOM = () => createElementNode(line, column); + this.toDOM = () => createElementNode(line, column, domNode); } } + function getIndentation(lineText) { + if (!lineText) { + return 0; + } + + const lineMatch = lineText.match(/^\s*/); + if (!lineMatch) { + return 0; + } + return lineMatch[0].length; + } + // Build and return the decoration set function buildDecorations(view) { const ranges = []; @@ -888,7 +901,10 @@ class Editor extends EventEmitter { position.line <= vEndLine.number ) { const line = view.state.doc.line(position.line); - const pos = line.from + position.column; + // Make sure to track any indentation at the beginning of the line + const column = Math.max(position.column, getIndentation(line.text)); + const pos = line.from + column; + if (marker.createPositionElementNode) { const nodeDecoration = Decoration.widget({ widget: new NodeWidget( @@ -902,6 +918,25 @@ class Editor extends EventEmitter { }); ranges.push({ from: pos, to: pos, value: nodeDecoration }); } + if (marker.positionClassName) { + const tokenAtPos = syntaxTree(view.state).resolve(pos, 1); + const tokenString = line.text.slice( + position.column, + tokenAtPos.to - line.from + ); + // ignore opening braces + if (tokenString === "{" || tokenString === "[") { + continue; + } + const classDecoration = Decoration.mark({ + class: marker.positionClassName, + }); + ranges.push({ + from: pos, + to: tokenAtPos.to, + value: classDecoration, + }); + } } } } @@ -957,9 +992,11 @@ class Editor extends EventEmitter { * @param {string} markerId - The unique identifier for this marker */ removePositionContentMarker(markerId) { + if (!this.#posContentMarkers.has(markerId)) { + return; + } const cm = editors.get(this); this.#posContentMarkers.delete(markerId); - cm.dispatch({ effects: this.#compartments.positionContentMarkersCompartment.reconfigure( this.#positionContentMarkersExtension(