diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /devtools/client/debugger/src/utils/editor/tokens.js | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/debugger/src/utils/editor/tokens.js')
-rw-r--r-- | devtools/client/debugger/src/utils/editor/tokens.js | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/utils/editor/tokens.js b/devtools/client/debugger/src/utils/editor/tokens.js new file mode 100644 index 0000000000..f8783c02fe --- /dev/null +++ b/devtools/client/debugger/src/utils/editor/tokens.js @@ -0,0 +1,178 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +function _isInvalidTarget(target) { + if (!target || !target.innerText) { + return true; + } + + const tokenText = target.innerText.trim(); + + // exclude syntax where the expression would be a syntax error + const invalidToken = + tokenText === "" || tokenText.match(/^[(){}\|&%,.;=<>\+-/\*\s](?=)/); + if (invalidToken) { + return true; + } + + // exclude tokens for which it does not make sense to show a preview: + // - literal + // - primitives + // - operators + // - tags + const INVALID_TARGET_CLASSES = [ + "cm-atom", + "cm-number", + "cm-operator", + "cm-string", + "cm-tag", + // also exclude editor element (defined in Editor component) + "editor-mount", + ]; + if ( + target.className === "" || + INVALID_TARGET_CLASSES.some(cls => target.classList.contains(cls)) + ) { + return true; + } + + // We need to exclude keywords, but since codeMirror tags "this" as a keyword, we need + // to check the tokenText as well. + // This seems to be the only case that we want to exclude (see devtools/client/shared/sourceeditor/codemirror/mode/javascript/javascript.js#24-41) + if (target.classList.contains("cm-keyword") && tokenText !== "this") { + return true; + } + + // exclude codemirror elements that are not tokens + if ( + // exclude inline preview + target.closest(".CodeMirror-widget") || + // exclude in-line "empty" space, as well as the gutter + target.matches(".CodeMirror-line, .CodeMirror-gutter-elt") || + target.getBoundingClientRect().top == 0 + ) { + return true; + } + + // exclude popup + if (target.closest(".popover")) { + return true; + } + + return false; +} + +function _dispatch(codeMirror, eventName, data) { + codeMirror.constructor.signal(codeMirror, eventName, data); +} + +function _invalidLeaveTarget(target) { + if (!target || target.closest(".popover")) { + return true; + } + + return false; +} + +/** + * Wraps the codemirror mouse events to generate token events + * @param {*} codeMirror + * @returns + */ +export function onMouseOver(codeMirror) { + let prevTokenPos = null; + + function onMouseLeave(event) { + if (_invalidLeaveTarget(event.relatedTarget)) { + addMouseLeave(event.target); + return; + } + + prevTokenPos = null; + _dispatch(codeMirror, "tokenleave", event); + } + + function addMouseLeave(target) { + target.addEventListener("mouseleave", onMouseLeave, { + capture: true, + once: true, + }); + } + + return enterEvent => { + const { target } = enterEvent; + + if (_isInvalidTarget(target)) { + return; + } + + const tokenPos = getTokenLocation(codeMirror, target); + + if ( + prevTokenPos?.line !== tokenPos?.line || + prevTokenPos?.column !== tokenPos?.column + ) { + addMouseLeave(target); + + _dispatch(codeMirror, "tokenenter", { + event: enterEvent, + target, + tokenPos, + }); + prevTokenPos = tokenPos; + } + }; +} + +/** + * Gets the end position of a token at a specific line/column + * + * @param {*} codeMirror + * @param {Number} line + * @param {Number} column + * @returns {Number} + */ +export function getTokenEnd(codeMirror, line, column) { + const token = codeMirror.getTokenAt({ + line, + ch: column + 1, + }); + const tokenString = token.string; + + return tokenString === "{" || tokenString === "[" ? null : token.end; +} + +/** + * Given the dom element related to the token, this gets its line and column. + * + * @param {*} codeMirror + * @param {*} tokenEl + * @returns {Object} An object of the form { line, column } + */ +export function getTokenLocation(codeMirror, tokenEl) { + // Get the quad (and not the bounding rect), as the span could wrap on multiple lines + // and the middle of the bounding rect may not be over the token: + // +───────────────────────+ + // │ myLongVariableNa│ + // │me + │ + // +───────────────────────+ + const { p1, p2, p3 } = tokenEl.getBoxQuads()[0]; + const left = p1.x + (p2.x - p1.x) / 2; + const top = p1.y + (p3.y - p1.y) / 2; + const { line, ch } = codeMirror.coordsChar( + { + left, + top, + }, + // Use the "window" context where the coordinates are relative to the top-left corner + // of the currently visible (scrolled) window. + // This enables codemirror also correctly handle wrappped lines in the editor. + "window" + ); + + return { + line: line + 1, + column: ch, + }; +} |