Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions ace-internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1417,9 +1417,16 @@ declare module "./src/editor" {
}

declare module "./src/edit_session" {
type TextMarker = import("./src/layer/text_markers").TextMarker;
type TextMarkers = typeof import("./src/layer/text_markers").editSessionTextMarkerMixin & {
$textMarkers: TextMarker[];
$textMarkerId: number;
$scheduleForRemove: Set<string>;
};

export interface EditSession extends Ace.EventEmitter<Ace.EditSessionEvents>,
Ace.OptionsProvider<Ace.EditSessionOptions>,
Ace.Folding, Ace.BracketMatch {
Ace.Folding, Ace.BracketMatch, TextMarkers {
doc: Ace.Document,
$highlightLineMarker?: {
start: Ace.Point,
Expand Down Expand Up @@ -1597,7 +1604,8 @@ declare module "./src/layer/gutter" {
}

declare module "./src/layer/text" {
export interface Text extends Ace.EventEmitter<Ace.TextEvents> {
type TextMarkersMixin = typeof import("./src/layer/text_markers").textMarkerMixin;
export interface Text extends Ace.EventEmitter<Ace.TextEvents>, TextMarkersMixin {
config: Ace.LayerConfig
}
}
Expand Down
2 changes: 1 addition & 1 deletion demo/kitchen-sink/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -711,4 +711,4 @@ function moveFocus() {
env.editor.cmdLine.focus();
else
env.editor.focus();
}
}
16 changes: 8 additions & 8 deletions src/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@
switch (scrollIntoView) {
case "center-animate":
scrollIntoView = "animate";
/* fall through */
/* fall through */
case "center":
this.renderer.scrollCursorIntoView(null, 0.5);
break;
Expand Down Expand Up @@ -467,7 +467,7 @@
*/
getFontSize() {
return this.getOption("fontSize") ||
dom.computedStyle(this.container).fontSize;
dom.computedStyle(this.container).fontSize;

Check warning on line 470 in src/editor.js

View check run for this annotation

Codecov / codecov/patch

src/editor.js#L470

Added line #L470 was not covered by tests
}

/**
Expand Down Expand Up @@ -974,13 +974,13 @@
if (transform.selection.length == 2) { // Transform relative to the current column
this.selection.setSelectionRange(
new Range(cursor.row, start + transform.selection[0],
cursor.row, start + transform.selection[1]));
cursor.row, start + transform.selection[1]));
} else { // Transform relative to the current row.
this.selection.setSelectionRange(
new Range(cursor.row + transform.selection[0],
transform.selection[1],
cursor.row + transform.selection[2],
transform.selection[3]));
transform.selection[1],
cursor.row + transform.selection[2],
transform.selection[3]));
}
}
if (this.$enableAutoIndent) {
Expand Down Expand Up @@ -1883,7 +1883,7 @@
* Copies all the selected lines up one row.
*
**/
copyLinesUp() {
copyLinesUp() {
this.$moveLines(-1, true);
}

Expand Down Expand Up @@ -2081,7 +2081,7 @@
* Shifts the document to wherever "page down" is, as well as moving the cursor position.
**/
gotoPageDown() {
this.$moveByPage(1, false);
this.$moveByPage(1, false);

Check warning on line 2084 in src/editor.js

View check run for this annotation

Codecov / codecov/patch

src/editor.js#L2084

Added line #L2084 was not covered by tests
}

/**
Expand Down
8 changes: 6 additions & 2 deletions src/layer/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,9 @@ class Text {

if (tab) {
var tabSize = self.session.getScreenTabSize(screenColumn + m.index);
valueFragment.appendChild(self.$tabStrings[tabSize].cloneNode(true));
var text = self.$tabStrings[tabSize].cloneNode(true);
text["charCount"] = 1;
valueFragment.appendChild(text);
screenColumn += tabSize - 1;
} else if (simpleSpace) {
if (self.showSpaces) {
Expand Down Expand Up @@ -609,7 +611,9 @@ class Text {
lineEl = this.$createLineElement();
parent.appendChild(lineEl);

lineEl.appendChild(this.dom.createTextNode(lang.stringRepeat("\xa0", splits.indent), this.element));
var text = this.dom.createTextNode(lang.stringRepeat("\xa0", splits.indent), this.element);
text["charCount"] = 0; // not to take into account when we are counting columns
lineEl.appendChild(text);

split ++;
screenColumn = 0;
Expand Down
279 changes: 279 additions & 0 deletions src/layer/text_markers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
const Text = require("./text").Text;

/**
* @typedef TextMarker
* @property {import("../../ace-internal").Ace.IRange} range
* @property {number} id
* @property {string} className
*/

const textMarkerMixin = {
/**
* @param {string} className
* @this {Text}
*/
$removeClass(className) {
if (!this.element) return;
const selectedElements = this.element.querySelectorAll('.' + className);
for (let i = 0; i < selectedElements.length; i++) {
selectedElements[i].classList.remove(className);

Check warning on line 19 in src/layer/text_markers.js

View check run for this annotation

Codecov / codecov/patch

src/layer/text_markers.js#L19

Added line #L19 was not covered by tests
}
},
/**
* @this {Text}
*/
$applyTextMarkers() {
if (this.session.$scheduleForRemove) {
this.session.$scheduleForRemove.forEach(className => {
this.$removeClass(className);
});

this.session.$scheduleForRemove = new Set();
}

const textMarkers = this.session.getTextMarkers();

if (textMarkers.length === 0) {
return;
}

const classNameGroups = new Set();
textMarkers.forEach(marker => {
classNameGroups.add(marker.className);
});

classNameGroups.forEach(className => {
this.$removeClass(className);
});

textMarkers.forEach((marker) => {
for (let row = marker.range.start.row; row <= marker.range.end.row; row++) {
const cell = this.$lines.cells.find((el) => el.row === row);

if (cell) {
this.$modifyDomForMarkers(cell.element, row, marker);
}
}
});
},
/**
* @param {HTMLElement} lineElement
* @param {number} row
* @param {TextMarker} marker
* @this {Text}
*/
$modifyDomForMarkers(lineElement, row, marker) {
const lineLength = this.session.getLine(row).length;
let startCol = row > marker.range.start.row ? 0 : marker.range.start.column;
let endCol = row < marker.range.end.row ? lineLength : marker.range.end.column;

var lineElements = [];
if (lineElement.classList.contains('ace_line_group')) {
lineElements = Array.from(lineElement.childNodes);

Check warning on line 72 in src/layer/text_markers.js

View check run for this annotation

Codecov / codecov/patch

src/layer/text_markers.js#L72

Added line #L72 was not covered by tests
}
else {
lineElements = [lineElement];
}

var currentColumn = 0;
lineElements.forEach((lineElement) => {
const childNodes = Array.from(lineElement.childNodes);
for (let i = 0; i < childNodes.length; i++) {
let subChildNodes = [childNodes[i]];
let parentNode = lineElement;
if (childNodes[i].childNodes && childNodes[i].childNodes.length > 0) {
subChildNodes = Array.from(childNodes[i].childNodes);
parentNode = childNodes[i];
}
for (let j = 0; j < subChildNodes.length; j++) {
const node = subChildNodes[j];
const nodeText = node.textContent || '';
const contentLength = node["charCount"] || node.parentNode["charCount"] || nodeText.length;
const nodeStart = currentColumn;
const nodeEnd = currentColumn + contentLength;

if (node["charCount"] === 0 || contentLength === 0) {
continue;
}

if (nodeStart < endCol && nodeEnd > startCol) {
if (node.nodeType === 3) { //text node
const beforeSelection = Math.max(0, startCol - nodeStart);
const afterSelection = Math.max(0, nodeEnd - endCol);
const selectionLength = contentLength - beforeSelection - afterSelection;

if (beforeSelection > 0 || afterSelection > 0) {
const fragment = this.dom.createFragment(this.element);

if (beforeSelection > 0) {
fragment.appendChild(
this.dom.createTextNode(nodeText.substring(0, beforeSelection), this.element));
}

if (selectionLength > 0) {
const selectedSpan = this.dom.createElement('span');
selectedSpan.classList.add(marker.className);
selectedSpan.textContent = nodeText.substring(
beforeSelection,
beforeSelection + selectionLength
);
fragment.appendChild(selectedSpan);
}

if (afterSelection > 0) {
fragment.appendChild(
this.dom.createTextNode(
nodeText.substring(beforeSelection + selectionLength),
this.element
));
}

parentNode.replaceChild(fragment, node);
}
else {
const selectedSpan = this.dom.createElement('span');
selectedSpan.classList.add(marker.className);
selectedSpan.textContent = nodeText;
selectedSpan["charCount"] = node["charCount"];
parentNode.replaceChild(selectedSpan, node);
}
}
else if (node.nodeType === 1) { //element node
if (nodeStart >= startCol && nodeEnd <= endCol) {
// @ts-ignore
node.classList.add(marker.className);
}
else {
const beforeSelection = Math.max(0, startCol - nodeStart);
const afterSelection = Math.max(0, nodeEnd - endCol);
const selectionLength = contentLength - beforeSelection - afterSelection;

if (beforeSelection > 0 || afterSelection > 0) {
// @ts-ignore
const nodeClasses = node.className;
const fragment = this.dom.createFragment(this.element);

if (beforeSelection > 0) {
const beforeSpan = this.dom.createElement('span');
beforeSpan.className = nodeClasses;
beforeSpan.textContent = nodeText.substring(0, beforeSelection);
fragment.appendChild(beforeSpan);
}

if (selectionLength > 0) {
const selectedSpan = this.dom.createElement('span');
selectedSpan.className = nodeClasses + ' ' + marker.className;
selectedSpan.textContent = nodeText.substring(
beforeSelection,
beforeSelection + selectionLength
);
fragment.appendChild(selectedSpan);
}

if (afterSelection > 0) {
const afterSpan = this.dom.createElement('span');
afterSpan.className = nodeClasses;
afterSpan.textContent = nodeText.substring(beforeSelection + selectionLength);
fragment.appendChild(afterSpan);
}

parentNode.replaceChild(fragment, node);
}
}
}
}
currentColumn = nodeEnd;
}
}
});
}
};
Object.assign(Text.prototype, textMarkerMixin);

var EditSession = require("../edit_session").EditSession;
const editSessionTextMarkerMixin = {
/**
* Adds a text marker to the current edit session.
*
* @param {import("../../ace-internal").Ace.IRange} range - The range to mark in the document
* @param {string} className - The CSS class name to apply to the marked text
* @returns {number} The unique identifier for the added text marker
*
* @this {EditSession}
*/
addTextMarker(range, className) {
/**@type{number}*/
this.$textMarkerId = this.$textMarkerId || 0;
this.$textMarkerId++;
var marker = {
range: range,
id: this.$textMarkerId,
className: className
};
if (!this.$textMarkers) {
this.$textMarkers = [];
}
this.$textMarkers[marker.id] = marker;
return marker.id;
},
/**
* Removes a text marker from the current edit session.
*
* @param {number} markerId - The unique identifier of the text marker to remove
*
* @this {EditSession}
*/
removeTextMarker(markerId) {
if (!this.$textMarkers) {
return;

Check warning on line 228 in src/layer/text_markers.js

View check run for this annotation

Codecov / codecov/patch

src/layer/text_markers.js#L228

Added line #L228 was not covered by tests
}

const marker = this.$textMarkers[markerId];
if (!marker) {
return;

Check warning on line 233 in src/layer/text_markers.js

View check run for this annotation

Codecov / codecov/patch

src/layer/text_markers.js#L233

Added line #L233 was not covered by tests
}
if (!this.$scheduleForRemove) {
this.$scheduleForRemove = new Set();
}
this.$scheduleForRemove.add(marker.className);
delete this.$textMarkers[markerId];
},
/**
* Retrieves the text markers associated with the current edit session.
*
* @returns {TextMarker[]} An array of text markers, or an empty array if no markers exist
*
* @this {EditSession}
*/
getTextMarkers() {
return this.$textMarkers || [];
}
};
Object.assign(EditSession.prototype, editSessionTextMarkerMixin);


const onAfterRender = (e, renderer) => {
renderer.$textLayer.$applyTextMarkers();
};

const Editor = require("../editor").Editor;
require("../config").defineOptions(Editor.prototype, "editor", {
enableTextMarkers: {
/**
* @param {boolean} val
* @this {Editor}
*/
set: function (val) {
if (val) {
this.renderer.on("afterRender", onAfterRender);
}
else {
this.renderer.off("afterRender", onAfterRender);

Check warning on line 271 in src/layer/text_markers.js

View check run for this annotation

Codecov / codecov/patch

src/layer/text_markers.js#L271

Added line #L271 was not covered by tests
}
},
value: true
}
});

exports.textMarkerMixin = textMarkerMixin;
exports.editSessionTextMarkerMixin = editSessionTextMarkerMixin;
Loading