Skip to content

Commit 063ef9b

Browse files
authored
fix: move right of cursor tokens to end of ghost text. (#5616)
* fix: move tokens to the right of ghost text down
1 parent 26eda25 commit 063ef9b

File tree

5 files changed

+123
-8
lines changed

5 files changed

+123
-8
lines changed

src/autocomplete/inline_test.js

+64-2
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ module.exports = {
105105
inline.show(editor, completions[3], "f");
106106
editor.renderer.$loop._flush();
107107
assert.strictEqual(getAllLines(), textBase + "function foo() {");
108-
assert.strictEqual(editor.renderer.$ghostTextWidget.el.innerHTML, `<div> console.log('test');</div><div> }</div>`);
108+
assert.strictEqual(editor.renderer.$ghostTextWidget.el.innerHTML, `<div><span class="ace_ghost_text"> console.log('test');</span></div><div><span class="ace_ghost_text"> }</span><span></span></div>`);
109109
done();
110110
},
111111
"test: boundary tests": function(done) {
@@ -314,7 +314,69 @@ module.exports = {
314314
inline.show(editor, completions[8], "f");
315315
editor.renderer.$loop._flush();
316316
assert.strictEqual(getAllLines(), textBase + "foo suggestion with a");
317-
assert.strictEqual(editor.renderer.$ghostTextWidget.el.innerHTML, `<div> </div><div> </div><div>gap</div>`);
317+
assert.strictEqual(editor.renderer.$ghostTextWidget.el.innerHTML, `<div><span class="ace_ghost_text"> </span></div><div><span class="ace_ghost_text"> </span></div><div><span class="ace_ghost_text">gap</span><span></span></div>`);
318+
done();
319+
},
320+
"test: moves tokens to the right of cursor to the end of ghost text for multi line ghost text": function(done) {
321+
editor.execCommand("removetolinestarthard");
322+
editor.execCommand("insertstring", "f hi I should be hidden");
323+
editor.execCommand("gotolinestart");
324+
editor.execCommand("gotoright");
325+
editor.renderer.$loop._flush();
326+
assert.equal(editor.renderer.$ghostTextWidget, null);
327+
inline.show(editor, completions[8], "f");
328+
editor.renderer.$loop._flush();
329+
assert.strictEqual(getAllLines(), textBase.replaceAll(" ", "") + "foo suggestion with a hi I should be hidden");
330+
331+
// The string to the right of the cursor should be hidden tokens now.
332+
var tokens = editor.session.getTokens(2);
333+
assert.strictEqual(tokens[2].value, " hi I should be hidden");
334+
assert.strictEqual(tokens[2].type, "hidden_token");
335+
336+
// And should be added to the ghost text widget.
337+
assert.strictEqual(editor.renderer.$ghostTextWidget.el.textContent, " gap hi I should be hidden");
338+
339+
// Hide inline
340+
inline.hide();
341+
editor.renderer.$loop._flush();
342+
assert.equal(editor.renderer.$ghostTextWidget, null);
343+
344+
// Text to the right of the cursor should be tokenized normally again.
345+
var tokens = editor.session.getTokens(2);
346+
assert.strictEqual(tokens[0].value, "f hi I should be hidden");
347+
assert.strictEqual(tokens[0].type, "text");
348+
349+
done();
350+
},
351+
"test: moves tokens to the right of cursor to the end of ghost text for multi line ghost text when triggered inside token": function(done) {
352+
editor.execCommand("removetolinestarthard");
353+
editor.execCommand("insertstring", "fhi I should be hidden");
354+
editor.execCommand("gotolinestart");
355+
editor.execCommand("gotoright");
356+
editor.renderer.$loop._flush();
357+
assert.equal(editor.renderer.$ghostTextWidget, null);
358+
inline.show(editor, completions[8], "f");
359+
editor.renderer.$loop._flush();
360+
assert.strictEqual(getAllLines(), textBase.replaceAll(" ", "") + "foo suggestion with ahi I should be hidden");
361+
362+
// The string to the right of the cursor should be hidden tokens now.
363+
var tokens = editor.session.getTokens(2);
364+
assert.strictEqual(tokens[2].value, "hi I should be hidden");
365+
assert.strictEqual(tokens[2].type, "hidden_token");
366+
367+
// And should be added to the ghost text widget.
368+
assert.strictEqual(editor.renderer.$ghostTextWidget.el.textContent, " gaphi I should be hidden");
369+
370+
// Hide inline
371+
inline.hide();
372+
editor.renderer.$loop._flush();
373+
assert.equal(editor.renderer.$ghostTextWidget, null);
374+
375+
// Text to the right of the cursor should be tokenized normally again.
376+
var tokens = editor.session.getTokens(2);
377+
assert.strictEqual(tokens[0].value, "fhi I should be hidden");
378+
assert.strictEqual(tokens[0].type, "text");
379+
318380
done();
319381
},
320382
tearDown: function() {

src/css/editor-css.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@ module.exports = `
660660
font-style: italic;
661661
}
662662
663-
.ace_ghost_text > div {
663+
.ace_ghost_text_container > div {
664664
white-space: pre;
665665
}
666666
@@ -680,4 +680,8 @@ module.exports = `
680680
width:1px;
681681
height:1px;
682682
overflow:hidden;
683+
}
684+
685+
.ace_hidden_token {
686+
display: none;
683687
}`;

src/ext/inline_autocomplete_test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ module.exports = {
358358
typeAndChange("u", "n");
359359
editor.renderer.$loop._flush();
360360
assert.strictEqual(autocomplete.isOpen(), true);
361-
assert.equal(getAllLines(), `function foo() {\n<div> console.log('test');</div><div>}</div>`);
361+
assert.equal(getAllLines(), `function foo() {\n<div><span class="ace_ghost_text"> console.log('test');</span></div><div><span class="ace_ghost_text">}</span><span></span></div>`);
362362

363363
typeAndChange("d");
364364
editor.renderer.$loop._flush();

src/virtual_renderer.js

+51-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ var editorCss = require("./css/editor-css");
2323
var Decorator = require("./layer/decorators").Decorator;
2424

2525
var useragent = require("./lib/useragent");
26+
const isTextToken = require("./layer/text_util").isTextToken;
2627

2728
dom.importCssString(editorCss, "ace_editor.css", false);
2829

@@ -1779,8 +1780,14 @@ class VirtualRenderer {
17791780

17801781
var widgetDiv = dom.createElement("div");
17811782
if (textChunks.length > 1) {
1783+
// If there are tokens to the right of the cursor, hide those.
1784+
var hiddenTokens = this.hideTokensAfterPosition(insertPosition.row, insertPosition.column);
1785+
1786+
var lastLineDiv;
17821787
textChunks.slice(1).forEach(el => {
17831788
var chunkDiv = dom.createElement("div");
1789+
var chunkSpan = dom.createElement("span");
1790+
chunkSpan.className = "ace_ghost_text";
17841791

17851792
// If the line is wider than the viewport, wrap the line
17861793
if (el.wrapped) chunkDiv.className = "ghost_text_line_wrapped";
@@ -1789,15 +1796,28 @@ class VirtualRenderer {
17891796
// textcontent so that browsers render the empty line div.
17901797
if (el.text.length === 0) el.text = " ";
17911798

1792-
chunkDiv.appendChild(dom.createTextNode(el.text));
1799+
chunkSpan.appendChild(dom.createTextNode(el.text));
1800+
chunkDiv.appendChild(chunkSpan);
17931801
widgetDiv.appendChild(chunkDiv);
1802+
1803+
// Overwrite lastLineDiv every iteration so at the end it points to
1804+
// the last added element.
1805+
lastLineDiv = chunkDiv;
1806+
});
1807+
1808+
// Add the hidden tokens to the last line of the ghost text.
1809+
hiddenTokens.forEach(token => {
1810+
var element = dom.createElement("span");
1811+
if (!isTextToken(token.type)) element.className = "ace_" + token.type.replace(/\./g, " ace_");
1812+
element.appendChild(dom.createTextNode(token.value));
1813+
lastLineDiv.appendChild(element);
17941814
});
17951815

17961816
this.$ghostTextWidget = {
17971817
el: widgetDiv,
17981818
row: insertPosition.row,
17991819
column: insertPosition.column,
1800-
className: "ace_ghost_text"
1820+
className: "ace_ghost_text_container"
18011821
};
18021822
this.session.widgetManager.addLineWidget(this.$ghostTextWidget);
18031823

@@ -1902,6 +1922,35 @@ class VirtualRenderer {
19021922
this.updateLines(row, row);
19031923
}
19041924

1925+
// Hide all non-ghost-text tokens to the right of a given position.
1926+
hideTokensAfterPosition(row, column) {
1927+
var tokens = this.session.getTokens(row);
1928+
var l = 0;
1929+
var hasPassedCursor = false;
1930+
var hiddenTokens = [];
1931+
// Loop over all tokens and track at what position in the line they end.
1932+
for (var i = 0; i < tokens.length; i++) {
1933+
var token = tokens[i];
1934+
l += token.value.length;
1935+
1936+
if (token.type === "ghost_text") continue;
1937+
1938+
// If we've already passed the current cursor position, mark all of them as hidden.
1939+
if (hasPassedCursor) {
1940+
hiddenTokens.push({type: token.type, value: token.value});
1941+
token.type = "hidden_token";
1942+
continue;
1943+
}
1944+
// We call this method after we call addToken, so we are guaranteed a new token starts at the cursor position.
1945+
// Once we reached that point in the loop, flip the flag.
1946+
if (l === column) {
1947+
hasPassedCursor = true;
1948+
}
1949+
}
1950+
this.updateLines(row, row);
1951+
return hiddenTokens;
1952+
}
1953+
19051954
removeExtraToken(row, column) {
19061955
this.session.bgTokenizer.lines[row] = null;
19071956
this.updateLines(row, row);

src/virtual_renderer_test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ module.exports = {
376376
editor.renderer.$loop._flush();
377377
assert.equal(editor.renderer.content.textContent, "abcdefGhost1");
378378

379-
assert.equal(editor.session.lineWidgets[0].el.innerHTML, `<div>Ghost2</div><div>Ghost3</div>`);
379+
assert.equal(editor.session.lineWidgets[0].el.innerHTML, `<div><span class="ace_ghost_text">Ghost2</span></div><div><span class="ace_ghost_text">Ghost3</span><span></span></div>`);
380380

381381
editor.removeGhostText();
382382

@@ -395,7 +395,7 @@ module.exports = {
395395
editor.renderer.$loop._flush();
396396
assert.equal(editor.renderer.content.textContent, "abcdefThis is a long test text that is longer than ");
397397

398-
assert.equal(editor.session.lineWidgets[0].el.innerHTML, `<div class="ghost_text_line_wrapped">30 characters</div><div> </div><div>Ghost3</div>`);
398+
assert.equal(editor.session.lineWidgets[0].el.innerHTML, `<div class="ghost_text_line_wrapped"><span class="ace_ghost_text">30 characters</span></div><div><span class="ace_ghost_text"> </span></div><div><span class="ace_ghost_text">Ghost3</span><span></span></div>`);
399399

400400
editor.removeGhostText();
401401

0 commit comments

Comments
 (0)