diff --git a/CursorLayer.js b/CursorLayer.js
new file mode 100644
index 00000000..b7efcbb6
--- /dev/null
+++ b/CursorLayer.js
@@ -0,0 +1,58 @@
+function CursorLayer(parentEl)
+{
+ this.element = document.createElement("div");
+ this.element.className = "cursor-layer";
+ parentEl.appendChild(this.element);
+
+ this.cursor = document.createElement("div");
+ this.cursor.className = "cursor";
+
+ this.isVisible = false;
+}
+
+CursorLayer.prototype.setCursor = function(position)
+{
+ this.position = {
+ row: position.row,
+ column: position.column
+ };
+};
+
+CursorLayer.prototype.hideCursor = function()
+{
+ this.isVisible = false;
+ if (this.cursor.parentNode) {
+ this.cursor.parentNode.removeChild(this.cursor);
+ }
+};
+
+CursorLayer.prototype.showCursor = function()
+{
+ this.isVisible = true;
+ this.element.appendChild(this.cursor);
+};
+
+CursorLayer.prototype.getPixelPosition = function() {
+ return this.pixelPos || {left: 0, top:0};
+}
+
+CursorLayer.prototype.update = function(config)
+{
+ if (!this.position) return;
+
+ var cursorLeft = this.position.column * config.characterWidth;
+ var cursorTop = this.position.row * config.lineHeight;
+
+ this.pixelPos = {
+ left: cursorLeft,
+ top: cursorTop
+ };
+
+ this.cursor.style.left = cursorLeft + "px";
+ this.cursor.style.top = (cursorTop - (config.firstRow * config.lineHeight)) + "px";
+ this.cursor.style.height = config.lineHeight + "px";
+
+ if (this.isVisible) {
+ this.element.appendChild(this.cursor);
+ }
+};
\ No newline at end of file
diff --git a/Editor.js b/Editor.js
index 47274acf..7b827fa5 100644
--- a/Editor.js
+++ b/Editor.js
@@ -277,7 +277,7 @@ Editor.prototype =
};
capture(this.container, onMouseSelection, onMouseSelectionEnd);
- var timerId = setInterval(onSelectionInterval, 100);
+ var timerId = setInterval(onSelectionInterval, 20);
return preventDefault(e);
},
@@ -437,6 +437,8 @@ Editor.prototype =
setSelectionAnchor : function(row, column)
{
+ this.clearSelection();
+
this.selectionAnchor = {
row: Math.min(this.doc.getLength()-1, Math.max(0, row)),
column: Math.min(this.doc.getLine(this.cursor.row).length, Math.max(0, column))
diff --git a/MarkerLayer.js b/MarkerLayer.js
new file mode 100644
index 00000000..4bc21136
--- /dev/null
+++ b/MarkerLayer.js
@@ -0,0 +1,97 @@
+function MarkerLayer(parentEl)
+{
+ this.element = document.createElement("div");
+ this.element.className = "markers";
+ parentEl.appendChild(this.element);
+
+ this.markers = {};
+ this._markerId = 1;
+}
+
+MarkerLayer.prototype.addMarker = function(range, clazz)
+{
+ var id = this._markerId++;
+ this.markers[id] = {
+ range: range,
+ type: "line",
+ clazz: clazz
+ };
+
+ this.update();
+ return id;
+};
+
+MarkerLayer.prototype.removeMarker = function(markerId)
+{
+ var marker = this.markers[markerId];
+ if (marker) {
+ delete(this.markers[markerId]);
+ this.update();
+ }
+};
+
+MarkerLayer.prototype.update = function(config)
+{
+ var config = config || this.config;
+ if (!config) return;
+
+ this.config = config;
+
+ var html = [];
+ for (var key in this.markers)
+ {
+ var marker = this.markers[key];
+ var range = marker.range;
+
+ if (range.start.row !== range.end.row)
+ {
+ if (range.start.row >= config.firstRow && range.start.row <= config.lastRow)
+ {
+ html.push(
+ "
"
+ );
+ }
+
+ if (range.end.row >= config.firstRow && range.end.row <= config.lastRow)
+ {
+ html.push(
+ ""
+ );
+ };
+
+ for (var row=range.start.row+1; row < range.end.row; row++)
+ {
+ if (row >= config.firstRow && row <= config.lastRow)
+ {
+ html.push(
+ ""
+ );
+ }
+ };
+ }
+ else
+ {
+ if (range.start.row >= config.firstRow && range.start.row <= config.lastRow)
+ {
+ html.push(
+ ""
+ );
+ }
+ }
+ }
+ this.element.innerHTML = html.join("");
+};
\ No newline at end of file
diff --git a/TextDocument.js b/TextDocument.js
index a5591285..91b52c4b 100644
--- a/TextDocument.js
+++ b/TextDocument.js
@@ -123,11 +123,12 @@ TextDocument.prototype =
if (range.start.row == range.end.row) {
return this.lines[range.start.row].substring(range.start.column, range.end.column);
} else {
- return (
- this.lines[range.start.row].substring(range.start.column) +
- this.lines.slice(range.start.row+1, range.end.row+1) +
- this.lines[range.end.row].substring(0, range.end.column)
- );
+ var lines = [];
+ lines.push(this.lines[range.start.row].substring(range.start.column));
+ lines.push.apply(lines, this.lines.slice(range.start.row+1, range.end.row+1));
+ lines.push(this.lines[range.end.row].substring(0, range.end.column));
+
+ return lines.join("\n");
}
},
diff --git a/TextLayer.js b/TextLayer.js
new file mode 100644
index 00000000..5d7f65ee
--- /dev/null
+++ b/TextLayer.js
@@ -0,0 +1,78 @@
+function TextLayer(parentEl)
+{
+ this.element = document.createElement("div");
+ this.element.className = "canvas";
+ parentEl.appendChild(this.element);
+
+ this._measureSizes();
+}
+
+TextLayer.prototype.setDocument = function(doc) {
+ this.lines = doc.lines;
+ this.doc = doc;
+};
+
+TextLayer.prototype.getLineHeight = function() {
+ return this.lineHeight;
+};
+
+TextLayer.prototype.getCharacterWidth = function() {
+ return this.characterWidth;
+};
+
+TextLayer.prototype._measureSizes = function()
+{
+ var measureNode = document.createElement("div");
+ var style = measureNode.style;
+ style.width = style.height = "auto";
+ style.left = style.top = "-1000px";
+ style.visibility = "hidden";
+ style.position = "absolute";
+ style.overflow = "visible";
+
+ measureNode.innerHTML = "X
X";
+ this.element.appendChild(measureNode);
+
+ this.lineHeight = Math.round(measureNode.offsetHeight / 2);
+ this.characterWidth = measureNode.offsetWidth;
+
+ this.element.removeChild(measureNode);
+};
+
+TextLayer.prototype.update = function(config)
+{
+ var html = [];
+ for (var i=config.firstRow; i"
+ );
+ this.renderLine(html, i),
+ html.push("");
+ }
+
+ this.element.innerHTML = html.join("");
+};
+
+TextLayer.prototype.renderLine = function(stringBuilder, row)
+{
+ var tokens = this.doc.getLineTokens(row);
+ for (var i=0; i < tokens.length; i++)
+ {
+ var token = tokens[i];
+
+ var output = token.value.
+ replace(/&/g, "&").
+ replace(/", output, "");
+ } else {
+ stringBuilder.push(output);
+ }
+ };
+};
\ No newline at end of file
diff --git a/VirtualRenderer.js b/VirtualRenderer.js
index 3839ab50..00682073 100644
--- a/VirtualRenderer.js
+++ b/VirtualRenderer.js
@@ -3,75 +3,35 @@ function VirtualRenderer(containerId)
this.container = document.getElementById(containerId);
this.container.className += "editor";
- this.canvas = document.createElement("div");
- this.canvas.className = "canvas";
- this.container.appendChild(this.canvas);
+ var textLayer = this.textLayer = new TextLayer(this.container);
+ this.canvas = textLayer.element;
- this._measureSizes();
+ this.characterWidth = textLayer.getCharacterWidth();
+ this.lineHeight = textLayer.getLineHeight();
- this.composition = document.createElement("div");
- this.composition.className = "composition";
- this.composition.style.height = this.lineHeight + "px";
+ this.cursorLayer = new CursorLayer(this.container);
+ this.markerLayer = new MarkerLayer(this.container);
- this.cursor = document.createElement("div");
- this.cursor.className = "cursor";
- this.cursor.style.height = this.lineHeight + "px";
+ this.layers = [this.markerLayer, textLayer, this.cursorLayer];
- this.markers = {};
- this._markerId = 1;
-
this.scrollTop = 0;
- this.firstRow = 0;
this.cursorPos = {
row: 0,
column: 0
};
-
- this.layers = [];
- this.layers.push({
- element: this.canvas,
- update: this.updateLines
- });
-
- this.markerEl = document.createElement("div");
- this.markerEl.className = "markers";
- this.container.appendChild(this.markerEl);
-
- this.layers.push({
- element: this.markerEl,
- update: this.updateMarkers
- });
}
-VirtualRenderer.prototype.setDocument = function(doc) {
+VirtualRenderer.prototype.setDocument = function(doc)
+{
this.lines = doc.lines;
- this.doc = doc;
+ this.textLayer.setDocument(doc);
};
VirtualRenderer.prototype.getContainerElement = function() {
return this.container;
};
-VirtualRenderer.prototype._measureSizes = function()
-{
- var measureNode = document.createElement("div");
- var style = measureNode.style;
- style.width = style.height = "auto";
- style.left = style.top = "-1000px";
- style.visibility = "hidden";
- style.position = "absolute";
- style.overflow = "visible";
-
- measureNode.innerHTML = "X
X";
- this.canvas.appendChild(measureNode);
-
- this.lineHeight = Math.round(measureNode.offsetHeight / 2);
- this.characterWidth = measureNode.offsetWidth;
-
- this.canvas.removeChild(measureNode);
-};
-
VirtualRenderer.prototype.getLongestLineWidth = function(lines)
{
var longestLine = this.container.clientWidth;
@@ -90,9 +50,17 @@ VirtualRenderer.prototype.draw = function()
var longestLine = this.getLongestLineWidth(lines);
var lineCount = Math.ceil(minHeight / this.lineHeight);
- this.firstRow = firstRow = Math.round((this.scrollTop - offset) / this.lineHeight);
+ var firstRow = Math.round((this.scrollTop - offset) / this.lineHeight);
var lastRow = Math.min(lines.length, firstRow+lineCount);
+ var layerConfig = this.layerConfig = {
+ width: longestLine,
+ firstRow: firstRow,
+ lastRow: lastRow,
+ lineHeight: this.lineHeight,
+ characterWidth: this.characterWidth
+ };
+
for (var i=0; i < this.layers.length; i++)
{
var layer = this.layers[i];
@@ -102,171 +70,38 @@ VirtualRenderer.prototype.draw = function()
style.height = minHeight + "px";
style.width = longestLine + "px";
- layer.update.call(this, layer.element, firstRow, lastRow, longestLine);
+ layer.update(layerConfig);
};
-
- this.updateCursor(this.cursorPos);
}
-VirtualRenderer.prototype.updateLines = function(element, firstRow, lastRow, width)
-{
- var html = [];
- for (var i=firstRow; i"
- );
- this.renderLine(html, i),
- html.push("");
- }
-
- element.innerHTML = html.join("");
+VirtualRenderer.prototype.addMarker = function(range, clazz) {
+ return this.markerLayer.addMarker(range, clazz);
};
-VirtualRenderer.prototype.renderLine = function(stringBuilder, row)
-{
- var tokens = this.doc.getLineTokens(row);
- for (var i=0; i < tokens.length; i++)
- {
- var token = tokens[i];
-
- var output = token.value.
- replace(/&/g, "&").
- replace(/", output, "");
- } else {
- stringBuilder.push(output);
- }
- };
+VirtualRenderer.prototype.removeMarker = function(markerId) {
+ this.markerLayer.removeMarker(markerId);
};
-
-VirtualRenderer.prototype.updateMarkers = function(element, firstRow, lastRow, width)
+VirtualRenderer.prototype.updateCursor = function(position)
{
- var html = [];
- for (var key in this.markers)
- {
- var marker = this.markers[key];
- var range = marker.range;
-
- if (range.start.row !== range.end.row)
- {
- if (range.start.row >= firstRow && range.start.row <= lastRow)
- {
- html.push(
- ""
- );
- }
-
- if (range.end.row >= firstRow && range.end.row <= lastRow)
- {
- html.push(
- ""
- );
- };
-
- for (var row=range.start.row+1; row < range.end.row; row++)
- {
- if (row >= firstRow && row <= lastRow)
- {
- html.push(
- ""
- );
- }
- };
- }
- else
- {
- if (range.start.row >= firstRow && range.start.row <= lastRow)
- {
- html.push(
- ""
- );
- }
- }
- }
- element.innerHTML = html.join("");
+ this.cursorLayer.setCursor(position);
+ this.cursorLayer.update(this.layerConfig);
};
-VirtualRenderer.prototype.addMarker = function(range, clazz)
-{
- var id = this._markerId++;
- this.markers[id] = {
- range: range,
- type: "line",
- clazz: clazz
- };
-
- this.draw();
-
- return id;
+VirtualRenderer.prototype.hideCursor = function() {
+ this.cursorLayer.hideCursor();
};
-VirtualRenderer.prototype.removeMarker = function(markerId)
-{
- var marker = this.markers[markerId];
- if (marker) {
- delete(this.markers[markerId]);
- this.draw();
- }
-};
-
-VirtualRenderer.prototype.updateCursor = function(position)
-{
- this.cursorPos = {
- row: position.row,
- column: position.column
- }
-
- var left = this.cursorLeft = position.column * this.characterWidth;
- var top = this.cursorTop = position.row * this.lineHeight;
-
- this.cursor.style.left = left + "px";
- this.cursor.style.top = (top - (this.firstRow * this.lineHeight)) + "px";
-
- if (this.cursorVisible) {
- this.canvas.appendChild(this.cursor);
- }
-};
-
-VirtualRenderer.prototype.hideCursor = function()
-{
- this.cursorVisible = true;
- if (this.cursor.parentNode) {
- this.cursor.parentNode.removeChild(this.cursor);
- }
-};
-
-VirtualRenderer.prototype.showCursor = function()
-{
- this.cursorVisible = true;
- this.canvas.appendChild(this.cursor);
+VirtualRenderer.prototype.showCursor = function() {
+ this.cursorLayer.showCursor();
};
VirtualRenderer.prototype.scrollCursorIntoView = function()
{
- var left = this.cursorLeft;
- var top = this.cursorTop;
+ var pos = this.cursorLayer.getPixelPosition();
+
+ var left = pos.left
+ var top = pos.top;
if (this.getScrollTop() > top) {
this.scrollToY(top);
@@ -321,22 +156,11 @@ VirtualRenderer.prototype.visualizeBlur = function() {
this.container.className = "editor";
};
-VirtualRenderer.prototype.showComposition = function(position)
-{
- setText(this.composition, "");
-
- this.composition.style.left = (position.column * this.characterWidth+1) + "px";
- this.composition.style.top = (position.row * this.lineHeight+1) + "px";
-
- this.container.appendChild(this.composition);
+VirtualRenderer.prototype.showComposition = function(position) {
};
VirtualRenderer.prototype.setCompositionText = function(text) {
- setText(this.composition, text);
};
VirtualRenderer.prototype.hideComposition = function() {
- if (this.composition.parentNode) {
- this.container.removeChild(this.composition);
- }
};
\ No newline at end of file
diff --git a/editor.css b/editor.css
index ceebf793..e571e121 100644
--- a/editor.css
+++ b/editor.css
@@ -58,6 +58,11 @@
z-index: 1;
}
+.editor .cursor-layer {
+ position: absolute;
+ z-index: 3;
+}
+
.selection {
position: absolute;
background: rgba(77, 151, 255, 0.33);
diff --git a/editor.html b/editor.html
index dd7e4eed..f39218f8 100644
--- a/editor.html
+++ b/editor.html
@@ -21,6 +21,9 @@
+
+
+