diff --git a/Public/css/highlight.css b/Public/css/highlight.css index 0040fac..6ac380a 100644 --- a/Public/css/highlight.css +++ b/Public/css/highlight.css @@ -84,13 +84,23 @@ .exp-special { color: #c0c; } -span.match { + +span.match-char { background: rgba(112, 176, 224, 0.5); color: #101112; border-right: solid 1px #ffffff; border-left: solid 1px #ffffff; margin-right: -2px; } +span.match-left { + border-left: solid 2px rgba(255, 0, 0, 0.2); + margin-left: -2px; +} +span.match-right { + border-right: solid 2px rgba(255, 0, 0, 0.2); + margin-right: -2px; +} + span.error { color: #d22; font-weight: 700; diff --git a/Public/js/views/test_highlighter.js b/Public/js/views/test_highlighter.js index 4617d4f..c332b04 100644 --- a/Public/js/views/test_highlighter.js +++ b/Public/js/views/test_highlighter.js @@ -7,8 +7,12 @@ import Utils from "../misc/utils"; export default class TestHighlighter extends EventDispatcher { constructor(editor) { super(); + this.editor = editor; this.activeMarks = []; + this.widgets = []; + + this.textHeight = editor.defaultTextHeight(); } draw(tokens) { @@ -20,13 +24,6 @@ export default class TestHighlighter extends EventDispatcher { const marks = this.activeMarks; for (const token of tokens) { - const className = "match"; - const location = Editor.calcRangePos( - this.editor, - token.location.start, - token.location.end - token.location.start - ); - const match = Utils.htmlSafe(token.value); let tooltip = `
match: ${match}
@@ -38,22 +35,101 @@ export default class TestHighlighter extends EventDispatcher { for (const [i, capture] of token.captures.entries()) { const value = Utils.htmlSafe(capture.value || ""); tooltip += `
-
group #${i + 1}: ${value}
+
group #${i + 1}: ${ + value === "" ? "empty string" : value + }
`; } } - marks.push( - doc.markText(location.startPos, location.endPos, { - className: className, - attributes: { - "data-tippy-content": tooltip, - }, - }) - ); + + if (token.location.start < token.location.end) { + const location = Editor.calcRangePos( + editor, + token.location.start, + token.location.end - token.location.start + ); + marks.push( + doc.markText(location.startPos, location.endPos, { + className: "match-char", + attributes: { + "data-tippy-content": tooltip, + }, + }) + ); + } else { + const location = Editor.calcRangePos(editor, token.location.start, 1); + + if ( + location.startPos.line === location.endPos.line && + location.startPos.ch === location.endPos.ch + ) { + this.addLeftAnchor(location, { "data-tippy-content": tooltip }); + } + if (location.startPos.line === location.endPos.line) { + if (location.startPos.ch < location.endPos.ch) { + marks.push( + doc.markText(location.startPos, location.endPos, { + className: "match-left", + attributes: { + "data-tippy-content": tooltip, + }, + }) + ); + } else { + // this.addRightAnchor(location, { "data-tippy-content": tooltip }); + } + } else { + if (location.startPos.ch === 0 && location.endPos.ch === 0) { + this.addLeftAnchor(location, { "data-tippy-content": tooltip }); + } else { + this.addRightAnchor(location, { "data-tippy-content": tooltip }); + } + } + } } }); } + addLeftAnchor(location, attributes = {}) { + const widget = document.createElement("span"); + widget.className = "match-left"; + widget.style.height = `${this.textHeight * 1.5}px`; + widget.style.width = "1px"; + widget.style.zIndex = "10"; + + for (const [key, value] of Object.entries(attributes)) { + widget.setAttribute(key, value); + } + + this.editor.addWidget(location.startPos, widget); + + const coords = this.editor.charCoords(location.startPos, "local"); + widget.style.left = `${coords.left}px`; + widget.style.top = `${coords.top + 2}px`; + + this.widgets.push(widget); + } + + addRightAnchor(location, attributes = {}) { + const widget = document.createElement("span"); + widget.className = "match-right"; + widget.style.height = `${this.textHeight * 1.5}px`; + widget.style.width = "1px"; + widget.style.zIndex = "10"; + + for (const [key, value] of Object.entries(attributes)) { + widget.setAttribute(key, value); + } + + this.editor.addWidget(location.endPos, widget); + + const coords = this.editor.charCoords(location.startPos, "local"); + widget.style.left = `${coords.left}px`; + widget.style.top = `${coords.top + 2}px`; + + this.widgets.push(widget); + } + clear() { this.editor.operation(() => { let marks = this.activeMarks; @@ -61,6 +137,11 @@ export default class TestHighlighter extends EventDispatcher { marks[i].clear(); } marks.length = 0; + + for (const widget of this.widgets) { + widget.parentNode.removeChild(widget); + } + this.widgets.length = 0; }); } }