Skip to content

Commit

Permalink
add soft wrap for ghost text
Browse files Browse the repository at this point in the history
  • Loading branch information
mkslanc committed Apr 26, 2024
1 parent 15f6be2 commit 41be890
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 9 deletions.
9 changes: 8 additions & 1 deletion src/css/editor-css.js
Expand Up @@ -656,7 +656,14 @@ module.exports = `
.ace_ghost_text {
opacity: 0.5;
font-style: italic;
white-space: pre;
}
.ace_ghost_text > div {
white-space: nowrap;
}
.ace_lineWidgetContainer.ace_ghost_text {
margin: 0px 4px
}
.ace_screenreader-only {
Expand Down
1 change: 1 addition & 0 deletions src/line_widgets.js
Expand Up @@ -388,6 +388,7 @@ class LineWidgets {

renderer.$cursorLayer.config = config;
for (var i = first; i <= last; i++) {
/**@type{LineWidget}*/
var w = lineWidgets[i];
if (!w || !w.el) continue;
if (w.hidden) {
Expand Down
54 changes: 47 additions & 7 deletions src/virtual_renderer.js
Expand Up @@ -1757,19 +1757,24 @@ class VirtualRenderer {
var insertPosition = position || { row: cursor.row, column: cursor.column };

this.removeGhostText();

var textLines = text.split("\n");
this.addToken(textLines[0], "ghost_text", insertPosition.row, insertPosition.column);

var textChunks = this.$calculateWrappedTextChunks(text, insertPosition);
this.addToken(textChunks[0], "ghost_text", insertPosition.row, insertPosition.column);

this.$ghostText = {
text: text,
position: {
row: insertPosition.row,
column: insertPosition. column
}
};
if (textLines.length > 1) {
if (textChunks.length > 1) {
var divs = textChunks.slice(1).map(el => {
return "<div>" + el + "</div>";
});

this.$ghostTextWidget = {
text: textLines.slice(1).join("\n"),
html: divs.join(""),
row: insertPosition.row,
column: insertPosition.column,
className: "ace_ghost_text"
Expand All @@ -1780,7 +1785,7 @@ class VirtualRenderer {
var pixelPosition = this.$cursorLayer.getPixelPosition(insertPosition, true);
var el = this.container;
var height = el.getBoundingClientRect().height;
var ghostTextHeight = textLines.length * this.lineHeight;
var ghostTextHeight = textChunks.length * this.lineHeight;
var fitsY = ghostTextHeight < (height - pixelPosition.top);

// If it fits, no action needed
Expand All @@ -1790,14 +1795,49 @@ class VirtualRenderer {
// if it cannot fully fit, scroll so that the row with the cursor
// is at the top of the screen.
if (ghostTextHeight < height) {
this.scrollBy(0, (textLines.length - 1) * this.lineHeight);
this.scrollBy(0, (textChunks.length - 1) * this.lineHeight);
} else {
this.scrollToRow(insertPosition.row);
}
}

}

/**
* Calculates and organizes text into wrapped chunks. Initially splits the text by newline characters,
* then further processes each line based on display tokens and session settings for tab size and wrapping limits.
*
* @param {string} text
* @param {Point} position
* @return {string[]}
*/
$calculateWrappedTextChunks(text, position) {
var availableWidth = this.$size.scrollerWidth - this.$padding * 2;
var limit = Math.floor(availableWidth / this.characterWidth) - 1;

var textLines = text.split(/\r?\n/);
var textChunks = [];
for (var i = 0; i < textLines.length; i++) {
var displayTokens = this.session.$getDisplayTokens(textLines[i], position.column);
var wrapSplits = this.session.$computeWrapSplits(displayTokens, limit, this.session.$tabSize);

if (wrapSplits.length > 0) {
var start = 0;
wrapSplits.push(textLines[i].length);

for (var j = 0; j < wrapSplits.length; j++) {
let textSlice = textLines[i].slice(start, wrapSplits[j]);
textChunks.push(textSlice);
start = wrapSplits[j];
}
}
else {
textChunks.push(textLines[i]);
}
}
return textChunks;
}

removeGhostText() {
if (!this.$ghostText) return;

Expand Down
21 changes: 20 additions & 1 deletion src/virtual_renderer_test.js
Expand Up @@ -338,7 +338,7 @@ module.exports = {
editor.renderer.$loop._flush();
assert.equal(editor.renderer.content.textContent, "abcdefGhost1");

assert.equal(editor.session.lineWidgets[0].el.textContent, "Ghost2\nGhost3");
assert.equal(editor.session.lineWidgets[0].el.innerHTML, "<div>Ghost2</div><div>Ghost3</div>");

editor.removeGhostText();

Expand All @@ -347,6 +347,25 @@ module.exports = {

assert.equal(editor.session.lineWidgets, null);
},
"test long multiline ghost text": function() {
editor.session.setValue("abcdef");
editor.renderer.$loop._flush();

editor.setGhostText("This is a long test text that is longer than 30 characters\n\nGhost3",
{row: 0, column: 6});

editor.renderer.$loop._flush();
assert.equal(editor.renderer.content.textContent, "abcdefThis is a long test text that is longer than ");

assert.equal(editor.session.lineWidgets[0].el.innerHTML, "<div>30 characters</div><div></div><div>Ghost3</div>");

editor.removeGhostText();

editor.renderer.$loop._flush();
assert.equal(editor.renderer.content.textContent, "abcdef");

assert.equal(editor.session.lineWidgets, null);
},
"test: brackets highlighting": function (done) {
var renderer = editor.renderer;
editor.session.setValue(
Expand Down

0 comments on commit 41be890

Please sign in to comment.