From f85c1b97589dd5ab02937f08cd5a38a79fd57c73 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 6 Apr 2010 11:07:33 +0200 Subject: [PATCH] Copy and paste and basic selection support --- DumbRenderer.js | 37 +++++++++++++++- Editor.js | 101 +++++++++++++++++++++++++++++++++++++++--- TextDocument.js | 15 ++++++- VirtualRenderer.js | 108 ++++++++++++++++++++++++++++++++++++++++----- cut_copy.html | 105 +++++++++++++++++++++++++++++++++++++++++++ editor.html | 14 +++++- 6 files changed, 358 insertions(+), 22 deletions(-) create mode 100644 cut_copy.html diff --git a/DumbRenderer.js b/DumbRenderer.js index 119637ff..0485dc2b 100644 --- a/DumbRenderer.js +++ b/DumbRenderer.js @@ -13,7 +13,10 @@ function DumbRenderer(containerId) this.cursor = document.createElement("div"); this.cursor.className = "cursor"; - this.cursor.style.height = this.lineHeight + "px"; + this.cursor.style.height = this.lineHeight + "px"; + + this.markers = {}; + this._markerId = 1; } DumbRenderer.prototype = @@ -167,6 +170,38 @@ DumbRenderer.prototype = this.container.className = ""; }, + addMarker : function(range, clazz) + { + var id = this._markerId++; + this.markers[id] = { + range: range, + type: "line", + clazz: clazz + }; + + this.draw(); + + return id; + }, + + removeMarker : function(markerId) + { + var marker = this.markers[markerId]; + if (marker) { + delete(this.markers[markerId]); + this.draw(); + } + }, + + updateMarker : function(markerId, range) + { + var marker = this.markers[markerId]; + if (marker) { + marker.range = range; + this.draw(); + } + }, + showComposition : function(position) { setText(this.composition, ""); diff --git a/Editor.js b/Editor.js index cfc33be8..e7727874 100644 --- a/Editor.js +++ b/Editor.js @@ -39,12 +39,26 @@ function TextInput(parentNode, host) { host.onCompositionEnd(); onTextInput(); } + + var onCopy = function() { + text.value = host.getCopyText(); + text.select(); + } + + var onCut = function() { + text.value = host.getCopyText(); + host.onCut(); + text.select(); + } addListener(text, "keypress", onTextInput, false); addListener(text, "textInput", onTextInput, false); - addListener(text, "paste", onTextInput, false); + addListener(text, "paste", onTextInput, false); addListener(text, "propertychange", onTextInput, false); + addListener(text, "copy", onCopy, false); + addListener(text, "cut", onCut, false); + addListener(text, "compositionstart", onCompositionStart, false); addListener(text, "compositionupdate", onCompositionUpdate, false); addListener(text, "compositionend", onCompositionEnd, false); @@ -84,6 +98,12 @@ function KeyBinding(element, host) addListener(element, "keydown", function(e) { var key = e.keyCode; + + // TODO + /* + if (!e.shiftKey) { + host.clearSelection(); + }*/ switch (key) { @@ -96,11 +116,21 @@ function KeyBinding(element, host) return stopEvent(e); case keys.LEFT: - host.moveLeft(); + if (e.metaKey) { + host.moveLineStart(); + } else { + host.moveLeft(); + } return stopEvent(e); case keys.RIGHT: - host.moveRight(); + if (e.metaKey) { + host.moveLineEnd(); + } else if (e.shiftKey) { + host.selectRight(); + } else { + host.moveRight(); + } return stopEvent(e); case keys.POS1: @@ -147,13 +177,17 @@ function Editor(doc, renderer) return preventDefault(e); }); - this.cursor = { - row: 0, - column: 0 - } this.doc = doc; renderer.setDocument(doc); + this.cursor = { + row: 0, + column: 0 + }; + + this.selectionRange = null; + this.selection = null; + this.draw(); } @@ -179,6 +213,25 @@ Editor.prototype = this.renderer.visualizeBlur(); }, + getCopyText : function() + { + if (this.selectionRange) { + return this.doc.getTextRange(this.selectionRange); + } else { + return ""; + } + }, + + onCut : function() + { + if (this.selectionRange) + { + this.cursor = this.doc.remove(this.selectionRange); + this.clearSelection(); + this.draw(); + } + }, + placeCursorToMouse : function(pageX, pageY) { var pos = this.renderer.screenToTextCoordinates(pageX, pageY); @@ -289,6 +342,40 @@ Editor.prototype = this.renderer.scrollCursorIntoView(); }, + clearSelection : function() + { + this.selectionRange = null; + if (this.selection) { + this.renderer.removeMarker(this.selection); + this.selection = null; + } + }, + + selectRight : function() + { + if (!this.selectionRange) { + this.selectionRange = { + start: { + row: this.cursor.row, + column: this.cursor.column + } + } + } + + this.moveRight(); + + this.selectionRange.end = { + row: this.cursor.row, + column: this.cursor.column + } + + if (this.selection) { + this.renderer.updateMarker(this.selection, this.selectionRange); + } else { + this.selection = this.renderer.addMarker(this.selectionRange, "selection"); + } + }, + moveBy : function(rows, chars) { this.moveTo(this.cursor.row+rows, this.cursor.column+chars); }, diff --git a/TextDocument.js b/TextDocument.js index 310f36ca..a5591285 100644 --- a/TextDocument.js +++ b/TextDocument.js @@ -117,7 +117,20 @@ TextDocument.prototype = }; } }, - + + getTextRange : function(range) + { + 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) + ); + } + }, + remove : function(range) { var firstRow = range.start.row; diff --git a/VirtualRenderer.js b/VirtualRenderer.js index 0b5ec321..fd5908bb 100644 --- a/VirtualRenderer.js +++ b/VirtualRenderer.js @@ -8,6 +8,21 @@ function VirtualRenderer(containerId) 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 + }); } inherits(VirtualRenderer, DumbRenderer); @@ -19,15 +34,27 @@ VirtualRenderer.prototype.draw = function() var minHeight = this.container.clientHeight + offset; var longestLine = this.getLongestLineWidth(lines); - - this.canvas.style.marginTop = (-offset) + "px"; - this.canvas.style.height = minHeight + "px"; - this.canvas.style.width = longestLine + "px"; - var lineCount = Math.ceil(minHeight / this.lineHeight); this.firstRow = firstRow = Math.round((this.scrollTop - offset) / this.lineHeight); var lastRow = Math.min(lines.length, firstRow+lineCount); - + + for (var i=0; i < this.layers.length; i++) + { + var layer = this.layers[i]; + + var style = layer.element.style; + style.marginTop = (-offset) + "px"; + style.height = minHeight + "px"; + style.width = longestLine + "px"; + + layer.update.call(this, layer.element, firstRow, lastRow, longestLine); + }; + + this.updateCursor(this.cursorPos); +} + +VirtualRenderer.prototype.updateLines = function(element, firstRow, lastRow, width) +{ var html = []; for (var i=firstRow; i" + "width:", width, "px'>" ); this.renderLine(html, i), html.push(""); } - this.canvas.innerHTML = html.join(""); - - this.updateCursor(this.cursorPos); -} + element.innerHTML = html.join(""); +}; VirtualRenderer.prototype.renderLine = function(stringBuilder, row) { @@ -66,6 +91,67 @@ VirtualRenderer.prototype.renderLine = function(stringBuilder, row) }; }; +VirtualRenderer.prototype.updateMarkers = function(element, firstRow, lastRow, width) +{ + 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(""); +}; + VirtualRenderer.prototype.updateCursor = function(position) { this.cursorPos = { diff --git a/cut_copy.html b/cut_copy.html new file mode 100644 index 00000000..3299f3ad --- /dev/null +++ b/cut_copy.html @@ -0,0 +1,105 @@ + + + + + + Text Events + + + + + + + +
+ +
+
+ + +
+ +
+ + + + + diff --git a/editor.html b/editor.html index 837af221..7e83e551 100644 --- a/editor.html +++ b/editor.html @@ -11,7 +11,7 @@ #virtual_container { position: absolute; - padding: 3px; + /*padding: 3px;*/ border: 1px solid black; overflow-x: auto; overflow-y: hidden; @@ -37,6 +37,7 @@ } .canvas { + z-index: 2; position: absolute; overflow: hidden; font-family: Monaco, "Courier New"; @@ -62,7 +63,7 @@ } .line.odd { - background: #FAFAFA; + /*background: #FAFAFA;*/ } .keyword { @@ -77,6 +78,15 @@ font-style: italic; color: rgb(0, 102, 255); } + + .markers { + z-index: 1; + } + + .selection { + position: absolute; + background: rgba(77, 151, 255, 0.33); + }