From 7e462ec372892733c3220df734c4d0a3acb34a49 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 2 Apr 2010 15:39:45 +0200 Subject: [PATCH 001/392] initial version --- Editor.mm | 35 +++ editor.html | 521 ++++++++++++++++++++++++++++++++++++++++++ key_event_logger.html | 100 ++++++++ 3 files changed, 656 insertions(+) create mode 100644 Editor.mm create mode 100644 editor.html create mode 100644 key_event_logger.html diff --git a/Editor.mm b/Editor.mm new file mode 100644 index 00000000..a3b14fa0 --- /dev/null +++ b/Editor.mm @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/editor.html b/editor.html new file mode 100644 index 00000000..8323a61f --- /dev/null +++ b/editor.html @@ -0,0 +1,521 @@ + + + + + + Editor + + + + + + + +
+
+
+ + + + + \ No newline at end of file diff --git a/key_event_logger.html b/key_event_logger.html new file mode 100644 index 00000000..0b27d407 --- /dev/null +++ b/key_event_logger.html @@ -0,0 +1,100 @@ + + + + + + Text Events + + + + + + + +
+ +
+
+ + +
+ +
+ + + + + From bf3266a83ad734d4efc4d8481e756fb790ffb2e5 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 2 Apr 2010 16:22:04 +0200 Subject: [PATCH 002/392] extract rendering code --- editor.html | 302 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 181 insertions(+), 121 deletions(-) diff --git a/editor.html b/editor.html index 8323a61f..bd0a97b2 100644 --- a/editor.html +++ b/editor.html @@ -11,25 +11,25 @@ #container { position: absolute; + border: 1px solid black; width: 600px; + height: 400px; } - #canvas { + #container.focus { + border: 1px solid #327fbd;; + } + + .canvas { position: absolute; - border: 1px solid black; - margin: 4px; - width: 590px; + width: 600px; height: 400px; overflow-x: auto; overflow-y: auto; font-family: Courier New; white-space: nowrap; } - - #canvas.focus { - border: 1px solid #327fbd;; - } - + .composition { position: absolute; text-decoration: underline; @@ -55,7 +55,6 @@
-
From fc57e3e64be5813c364e5b551b1a398c71b07b7f Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 2 Apr 2010 18:28:14 +0200 Subject: [PATCH 003/392] Implement virtual renderer --- editor.html | 262 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 224 insertions(+), 38 deletions(-) diff --git a/editor.html b/editor.html index bd0a97b2..cb7616c8 100644 --- a/editor.html +++ b/editor.html @@ -9,9 +9,24 @@ + + + + + @@ -75,709 +94,11 @@ diff --git a/lib.js b/lib.js new file mode 100644 index 00000000..d839e70e --- /dev/null +++ b/lib.js @@ -0,0 +1,48 @@ +function addListener(elem, type, callback) { + if (elem.addEventListener) { + return elem.addEventListener(type, callback, false); + } + if (elem.attachEvent) { + elem.attachEvent("on" + type, function() { + callback(window.event); + }); + } +} + +function setText(elem, text) { + if (elem.innerText !== undefined) { + elem.innerText = text; + } + if (elem.textContent !== undefined) { + elem.textContent = text; + } +} + +function stopEvent(e) { + stopPropagation(e); + preventDefault(e); + return false; +} + +function stopPropagation(e) { + if (e.stopPropagation) + e.stopPropagation(); + else + e.cancelBubble = true; +} + +function preventDefault (e) +{ + if (e.preventDefault) + e.preventDefault(); + else + e.returnValue = false; +} + +inherits = function (ctor, superCtor) { + var tempCtor = function(){}; + tempCtor.prototype = superCtor.prototype; + ctor.super_ = superCtor.prototype; + ctor.prototype = new tempCtor(); + ctor.prototype.constructor = ctor; +}; \ No newline at end of file From f85c1b97589dd5ab02937f08cd5a38a79fd57c73 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 6 Apr 2010 11:07:33 +0200 Subject: [PATCH 006/392] 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); + } From c19476b82450b42bc5c6f25eda9e67671ad60a8a Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 6 Apr 2010 14:54:54 +0200 Subject: [PATCH 007/392] minor fixes --- Editor.js | 1 + VirtualRenderer.js | 2 ++ editor.html | 9 ++++----- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Editor.js b/Editor.js index e7727874..9162f191 100644 --- a/Editor.js +++ b/Editor.js @@ -168,6 +168,7 @@ function Editor(doc, renderer) addListener(container, "mousedown", function(e) { textInput.focus(); self.placeCursorToMouse(e.pageX, e.pageY); + self.renderer.scrollCursorIntoView(); return preventDefault(e); }); diff --git a/VirtualRenderer.js b/VirtualRenderer.js index fd5908bb..412a969b 100644 --- a/VirtualRenderer.js +++ b/VirtualRenderer.js @@ -1,6 +1,7 @@ function VirtualRenderer(containerId) { DumbRenderer.call(this, containerId); + this.scrollTop = 0; this.firstRow = 0; @@ -24,6 +25,7 @@ function VirtualRenderer(containerId) update: this.updateMarkers }); } + inherits(VirtualRenderer, DumbRenderer); VirtualRenderer.prototype.draw = function() diff --git a/editor.html b/editor.html index 7e83e551..0f8b8d4c 100644 --- a/editor.html +++ b/editor.html @@ -44,7 +44,8 @@ font-size: 14px; white-space: nowrap; -webkit-box-sizing: border-box; - box-sizing: border-box; + box-sizing: border-box; + cursor: text; } .composition { @@ -105,10 +106,8 @@ From 83911a628adf4462042d5aa40c355b77860039be Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 6 Apr 2010 16:31:01 +0200 Subject: [PATCH 008/392] remove dumb renderer --- DumbRenderer.js | 224 --------------------------------------------- VirtualRenderer.js | 135 ++++++++++++++++++++++++++- editor.html | 7 +- 3 files changed, 134 insertions(+), 232 deletions(-) delete mode 100644 DumbRenderer.js diff --git a/DumbRenderer.js b/DumbRenderer.js deleted file mode 100644 index 0485dc2b..00000000 --- a/DumbRenderer.js +++ /dev/null @@ -1,224 +0,0 @@ -function DumbRenderer(containerId) -{ - this.container = document.getElementById(containerId); - this.canvas = document.createElement("div"); - this.canvas.className = "canvas"; - this.container.appendChild(this.canvas); - - this._measureSizes(); - - this.composition = document.createElement("div"); - this.composition.className = "composition"; - this.composition.style.height = this.lineHeight + "px"; - - this.cursor = document.createElement("div"); - this.cursor.className = "cursor"; - this.cursor.style.height = this.lineHeight + "px"; - - this.markers = {}; - this._markerId = 1; -} - -DumbRenderer.prototype = -{ - setDocument : function(doc) { - this.lines = doc.lines; - this.doc = doc; - }, - - getContainerElement : function() { - return this.container; - }, - - _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); - }, - - getLongestLineWidth : function(lines) - { - var longestLine = this.container.clientWidth; - for (var i=0; i < lines.length; i++) { - longestLine = Math.max(longestLine, (lines[i].length * this.characterWidth)); - } - return longestLine; - }, - - draw : function() - { - var lines = this.lines; - var longestLine = this.getLongestLineWidth(lines); - - var html = []; - for (var i=0; i < lines.length; i++) - { - html.push( - "
", - lines[i]. - replace(/&/g, "&"). - replace(/" - ); - }; - this.canvas.innerHTML = html.join(""); - - this.canvas.appendChild(this.cursor); - }, - - updateCursor : function(position) - { - 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 + "px"; - - if (this.cursorVisible) { - this.canvas.appendChild(this.cursor); - } - }, - - hideCursor : function() - { - this.cursorVisible = true; - if (this.cursor.parentNode) { - this.cursor.parentNode.removeChild(this.cursor); - } - }, - - showCursor : function() - { - this.cursorVisible = true; - this.canvas.appendChild(this.cursor); - }, - - getScrollTop : function() { - return this.container.scrollTop; - }, - - scrollToY : function(scrollTop) { - return this.container.scrollTop = scrollTop; - }, - - scrollCursorIntoView : function() - { - var left = this.cursorLeft; - var top = this.cursorTop; - - if (this.container.scrollLeft > left) { - this.container.scrollLeft = left; - } - - if (this.container.scrollLeft + this.container.clientWidth < left + this.characterWidth) { - this.container.scrollLeft = left + this.characterWidth - this.container.clientWidth; - } - - if (this.container.scrollTop > top) { - this.container.scrollTop = top; - } - - if (this.container.scrollTop + this.container.clientHeight < top + this.lineHeight) { - this.container.scrollTop = top + this.lineHeight - this.container.clientHeight; - } - }, - - screenToTextCoordinates : function(pageX, pageY) - { - var canvasPos = this.container.getBoundingClientRect(); - - if (pageY < canvasPos.top || pageY > canvasPos.bottom) { - row = null; - } else { - var row = Math.floor((pageY + this.container.scrollTop - canvasPos.top) / this.lineHeight); - } - - if (pageX < canvasPos.left || pageX > canvasPos.right) { - col = null; - } else { - var col = Math.floor((pageX + this.container.scrollLeft - canvasPos.left) / this.characterWidth); - } - - return { - row: row, - column: col - } - }, - - visualizeFocus : function() { - this.container.className = "focus"; - }, - - visualizeBlur : function() { - 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, ""); - - 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); - }, - - setCompositionText : function(text) { - setText(this.composition, text); - }, - - hideComposition : function() { - if (this.composition.parentNode) { - this.container.removeChild(this.composition); - } - } -} \ No newline at end of file diff --git a/VirtualRenderer.js b/VirtualRenderer.js index 412a969b..cfcdf631 100644 --- a/VirtualRenderer.js +++ b/VirtualRenderer.js @@ -1,10 +1,27 @@ function VirtualRenderer(containerId) { - DumbRenderer.call(this, containerId); + this.container = document.getElementById(containerId); + this.canvas = document.createElement("div"); + this.canvas.className = "canvas"; + this.container.appendChild(this.canvas); + + this._measureSizes(); + + this.composition = document.createElement("div"); + this.composition.className = "composition"; + this.composition.style.height = this.lineHeight + "px"; + + this.cursor = document.createElement("div"); + this.cursor.className = "cursor"; + this.cursor.style.height = this.lineHeight + "px"; + + this.markers = {}; + this._markerId = 1; + this.scrollTop = 0; this.firstRow = 0; - + this.cursorPos = { row: 0, column: 0 @@ -23,10 +40,45 @@ function VirtualRenderer(containerId) this.layers.push({ element: this.markerEl, update: this.updateMarkers - }); + }); } -inherits(VirtualRenderer, DumbRenderer); +VirtualRenderer.prototype.setDocument = function(doc) { + this.lines = doc.lines; + this.doc = 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; + for (var i=0; i < lines.length; i++) { + longestLine = Math.max(longestLine, (lines[i].length * this.characterWidth)); + } + return longestLine; +}; VirtualRenderer.prototype.draw = function() { @@ -93,6 +145,7 @@ VirtualRenderer.prototype.renderLine = function(stringBuilder, row) }; }; + VirtualRenderer.prototype.updateMarkers = function(element, firstRow, lastRow, width) { var html = []; @@ -154,6 +207,38 @@ VirtualRenderer.prototype.updateMarkers = function(element, firstRow, lastRow, w element.innerHTML = html.join(""); }; +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.removeMarker = function(markerId) +{ + var marker = this.markers[markerId]; + if (marker) { + delete(this.markers[markerId]); + this.draw(); + } +}; + +VirtualRenderer.prototype.updateMarker = function(markerId, range) +{ + var marker = this.markers[markerId]; + if (marker) { + marker.range = range; + this.draw(); + } +}; + VirtualRenderer.prototype.updateCursor = function(position) { this.cursorPos = { @@ -172,6 +257,20 @@ VirtualRenderer.prototype.updateCursor = function(position) } }; +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.scrollCursorIntoView = function() { var left = this.cursorLeft; @@ -229,4 +328,32 @@ VirtualRenderer.prototype.screenToTextCoordinates = function(pageX, pageY) row: row, column: col } +}; + +VirtualRenderer.prototype.visualizeFocus = function() { + this.container.className = "focus"; +}; + +VirtualRenderer.prototype.visualizeBlur = function() { + this.container.className = ""; +}; + +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.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.html b/editor.html index 0f8b8d4c..53f3032a 100644 --- a/editor.html +++ b/editor.html @@ -11,7 +11,6 @@ #virtual_container { position: absolute; - /*padding: 3px;*/ border: 1px solid black; overflow-x: auto; overflow-y: hidden; @@ -41,7 +40,7 @@ position: absolute; overflow: hidden; font-family: Monaco, "Courier New"; - font-size: 14px; + font-size: 12px; white-space: nowrap; -webkit-box-sizing: border-box; box-sizing: border-box; @@ -81,6 +80,7 @@ } .markers { + position: absolute; z-index: 1; } @@ -88,12 +88,12 @@ position: absolute; background: rgba(77, 151, 255, 0.33); } + - @@ -107,7 +107,6 @@ From 21a739a961212c1e233dfdc3a07f1e233491733a Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 6 Apr 2010 16:37:29 +0200 Subject: [PATCH 009/392] editor is now full screen --- Editor.js | 4 ++++ editor.html | 31 +++++++++++-------------------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/Editor.js b/Editor.js index 9162f191..d7f196c2 100644 --- a/Editor.js +++ b/Editor.js @@ -200,6 +200,10 @@ Editor.prototype = this.renderer.updateCursor(this.cursor); }, + resize : function() { + this.renderer.draw(); + }, + updateCursor : function() { this.renderer.updateCursor(this.cursor); }, diff --git a/editor.html b/editor.html index 53f3032a..99110776 100644 --- a/editor.html +++ b/editor.html @@ -9,26 +9,15 @@ + From 08f14e321f39ed6d66a9608f0553a640a4a0a37e Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 6 Apr 2010 17:51:52 +0200 Subject: [PATCH 011/392] some helper methods --- lib.js | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/lib.js b/lib.js index d839e70e..70b83c54 100644 --- a/lib.js +++ b/lib.js @@ -45,4 +45,31 @@ inherits = function (ctor, superCtor) { ctor.super_ = superCtor.prototype; ctor.prototype = new tempCtor(); ctor.prototype.constructor = ctor; -}; \ No newline at end of file +}; + +getInnerWidth = function(element) +{ + return ( + parseInt(computedStyle(element, "paddingLeft")) + + parseInt(computedStyle(element, "paddingRight")) + + element.clientWidth + ); +}; + +getInnerHeight = function(element) +{ + return ( + parseInt(computedStyle(element, "paddingTop")) + + parseInt(computedStyle(element, "paddingBottom")) + + element.clientHeight + ); +}; + +computedStyle = function(element, style) +{ + if (window.getComputedStyle) { + return (window.getComputedStyle(element, null))[style]; + } else { + return element.currentStyle[style]; + } +} \ No newline at end of file From 7c845509d74c30334a9a31a5703f825f70ff39a5 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 7 Apr 2010 09:21:32 +0200 Subject: [PATCH 012/392] make selections work --- Editor.js | 215 ++++++++++++++++++++++++++++++++++----------- VirtualRenderer.js | 2 +- lib.js | 6 ++ 3 files changed, 169 insertions(+), 54 deletions(-) diff --git a/Editor.js b/Editor.js index d7f196c2..a26bbab7 100644 --- a/Editor.js +++ b/Editor.js @@ -90,7 +90,8 @@ var keys = { END: 35, DELETE: 46, BACKSPACE: 8, - TAB: 9 + TAB: 9, + A: 65 } function KeyBinding(element, host) @@ -98,54 +99,88 @@ function KeyBinding(element, host) addListener(element, "keydown", function(e) { var key = e.keyCode; - - // TODO - /* - if (!e.shiftKey) { - host.clearSelection(); - }*/ switch (key) - { + { + case keys.A: + if (e.metaKey) + { + host.selectAll(); + return stopEvent(e); + } + break; + case keys.UP: - host.moveUp(); + if (e.shiftKey) { + host.selectUp(); + } else { + host.clearSelection(); + host.moveUp(); + } return stopEvent(e); case keys.DOWN: - host.moveDown(); + if (e.shiftKey) { + host.selectDown(); + } else { + host.clearSelection(); + host.moveDown(); + } return stopEvent(e); case keys.LEFT: - if (e.metaKey) { + if (e.metaKey && e.shiftKey) { + host.selectLineStart(); + } else if (e.metaKey) { + host.clearSelection(); host.moveLineStart(); + } else if (e.shiftKey) { + host.selectLeft(); } else { + host.clearSelection(); host.moveLeft(); } return stopEvent(e); case keys.RIGHT: - if (e.metaKey) { + if (e.metaKey && e.shiftKey) { + host.selectLineEnd(); + } else if (e.metaKey) { + host.clearSelection(); host.moveLineEnd(); } else if (e.shiftKey) { host.selectRight(); - } else { + } else { + host.clearSelection(); host.moveRight(); } return stopEvent(e); case keys.POS1: - host.moveLineStart(); + if (e.shiftKey) { + host.selectLineStart(); + } else { + host.clearSelection(); + host.moveLineStart(); + } return stopEvent(e); case keys.END: - host.moveLineEnd(); + if (e.shiftKey) { + host.selectLineEnd(); + } else { + host.clearSelection(); + host.moveLineEnd(); + } return stopEvent(e); case keys.DELETE: + host.clearSelection(); host.removeRight(); return stopEvent(e); case keys.BACKSPACE: + host.clearSelection(); host.removeLeft(); return stopEvent(e); @@ -164,19 +199,8 @@ function Editor(doc, renderer) var textInput = new TextInput(container, this); new KeyBinding(container, this); - var self = this; - addListener(container, "mousedown", function(e) { - textInput.focus(); - self.placeCursorToMouse(e.pageX, e.pageY); - self.renderer.scrollCursorIntoView(); - return preventDefault(e); - }); - - addListener(container, "mousewheel", function(e) { - var delta = e.wheelDeltaY; - self.renderer.scrollToY(self.renderer.getScrollTop() - (delta/10)); - return preventDefault(e); - }); + addListener(container, "mousedown", bind(this.onMouseDown, this)); + addListener(container, "mousewheel", bind(this.onMouseWheel, this)); this.doc = doc; renderer.setDocument(doc); @@ -186,7 +210,8 @@ function Editor(doc, renderer) column: 0 }; - this.selectionRange = null; + this.selectionAnchor = null; + this.selectionLead = null; this.selection = null; this.draw(); @@ -218,10 +243,29 @@ Editor.prototype = this.renderer.visualizeBlur(); }, + onMouseDown : function(e) + { + this.textInput.focus(); + + var pos = this.renderer.screenToTextCoordinates(pageX, pageY); + this.moveTo(pos.row, pos.column); + this.setSelectionAnchor(pos.row, pos.column); + + this.renderer.scrollCursorIntoView(); + return preventDefault(e); + }, + + onMouseWheel : function(e) + { + var delta = e.wheelDeltaY; + this.renderer.scrollToY(this.renderer.getScrollTop() - (delta/10)); + return preventDefault(e); + }, + getCopyText : function() { - if (this.selectionRange) { - return this.doc.getTextRange(this.selectionRange); + if (this.hasSelection()) { + return this.doc.getTextRange(this.getSelectionRange()); } else { return ""; } @@ -229,29 +273,24 @@ Editor.prototype = onCut : function() { - if (this.selectionRange) + if (this.hasSelection()) { - this.cursor = this.doc.remove(this.selectionRange); + this.cursor = this.doc.remove(this.getSelectionRange()); this.clearSelection(); this.draw(); } }, - placeCursorToMouse : function(pageX, pageY) - { - var pos = this.renderer.screenToTextCoordinates(pageX, pageY); - this.moveTo(pos.row, pos.column); - }, - onTextInput: function(text) - { + { this.cursor = this.doc.insert(this.cursor, text); + this.clearSelection(); this.draw(); this.renderer.scrollCursorIntoView(); }, removeRight : function() - { + { var rangeEnd = { row: this.cursor.row, column: this.cursor.column + 1 @@ -347,40 +386,110 @@ Editor.prototype = this.renderer.scrollCursorIntoView(); }, + hasSelection : function() { + return !!this.selectionLead; + }, + + setSelectionAnchor : function(row, column) + { + this.selectionAnchor = { + row: row, + column: column + }; + + this.selectionLead = null; + }, + + getSelectionRange : function() + { + var anchor = this.selectionAnchor; + var lead = this.selectionLead; + + if (!anchor) { + return null; + } else { + if (anchor.row > lead.row || (anchor.row == lead.row && anchor.column > lead.column)) { + return { + start: lead, + end: anchor + } + } else { + return { + start: anchor, + end: lead + } + } + } + }, + clearSelection : function() { - this.selectionRange = null; + this.selectionLead = null; + this.selectionAnchor = null; + if (this.selection) { this.renderer.removeMarker(this.selection); this.selection = null; } }, - selectRight : function() + selectAll : function() { - if (!this.selectionRange) { - this.selectionRange = { - start: { - row: this.cursor.row, - column: this.cursor.column - } + var lastRow = this.doc.getLength()-1; + this.setSelectionAnchor(lastRow, this.doc.getLine(lastRow).length); + + this._moveSelection(function() { + this.moveTo(0, 0); + }); + }, + + _moveSelection : function(mover) + { + if (!this.selectionAnchor) { + this.selectionAnchor = { + row: this.cursor.row, + column: this.cursor.column } } - this.moveRight(); + mover.call(this); - this.selectionRange.end = { + this.selectionLead = { row: this.cursor.row, column: this.cursor.column } if (this.selection) { - this.renderer.updateMarker(this.selection, this.selectionRange); + this.renderer.updateMarker(this.selection, this.getSelectionRange()); } else { - this.selection = this.renderer.addMarker(this.selectionRange, "selection"); + this.selection = this.renderer.addMarker(this.getSelectionRange(), "selection"); } }, + selectUp : function() { + this._moveSelection(this.moveUp); + }, + + selectDown : function() { + this._moveSelection(this.moveDown); + }, + + selectRight : function() { + this._moveSelection(this.moveRight); + }, + + selectLeft : function() { + this._moveSelection(this.moveLeft); + }, + + selectLineStart : function() { + this._moveSelection(this.moveLineStart); + }, + + selectLineEnd : function() { + this._moveSelection(this.moveLineEnd); + }, + moveBy : function(rows, chars) { this.moveTo(this.cursor.row+rows, this.cursor.column+chars); }, diff --git a/VirtualRenderer.js b/VirtualRenderer.js index 74311db3..33e4a367 100644 --- a/VirtualRenderer.js +++ b/VirtualRenderer.js @@ -41,7 +41,7 @@ function VirtualRenderer(containerId) this.layers.push({ element: this.markerEl, update: this.updateMarkers - }); + }); } VirtualRenderer.prototype.setDocument = function(doc) { diff --git a/lib.js b/lib.js index 70b83c54..0f973453 100644 --- a/lib.js +++ b/lib.js @@ -72,4 +72,10 @@ computedStyle = function(element, style) } else { return element.currentStyle[style]; } +} + +bind = function(fcn, context) { + return function() { + return fcn.apply(context, arguments); + } } \ No newline at end of file From 0926133e95e114d6bdf7280b5ce5b9a0733691c9 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 7 Apr 2010 09:23:59 +0200 Subject: [PATCH 013/392] move experiments into a separate folder --- experiments/capture.html | 67 +++++++++++++++++++ cut_copy.html => experiments/cut_copy.html | 0 .../key_event_logger.html | 0 3 files changed, 67 insertions(+) create mode 100644 experiments/capture.html rename cut_copy.html => experiments/cut_copy.html (100%) rename key_event_logger.html => experiments/key_event_logger.html (100%) diff --git a/experiments/capture.html b/experiments/capture.html new file mode 100644 index 00000000..eed0f2f7 --- /dev/null +++ b/experiments/capture.html @@ -0,0 +1,67 @@ + + + + + + + + + + + + +
+ +
+ + + + diff --git a/cut_copy.html b/experiments/cut_copy.html similarity index 100% rename from cut_copy.html rename to experiments/cut_copy.html diff --git a/key_event_logger.html b/experiments/key_event_logger.html similarity index 100% rename from key_event_logger.html rename to experiments/key_event_logger.html From f1d25fb7b2d13ea87ce9075b1415f29089b08bbd Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 7 Apr 2010 10:08:44 +0200 Subject: [PATCH 014/392] add support for drag selection --- Editor.js | 111 +++++++++++++++++++++++++++++++-------------- VirtualRenderer.js | 13 +----- lib.js | 23 ++++++++++ 3 files changed, 103 insertions(+), 44 deletions(-) diff --git a/Editor.js b/Editor.js index a26bbab7..14e2c744 100644 --- a/Editor.js +++ b/Editor.js @@ -174,14 +174,14 @@ function KeyBinding(element, host) } return stopEvent(e); - case keys.DELETE: - host.clearSelection(); + case keys.DELETE: host.removeRight(); + host.clearSelection(); return stopEvent(e); - case keys.BACKSPACE: - host.clearSelection(); + case keys.BACKSPACE: host.removeLeft(); + host.clearSelection(); return stopEvent(e); case keys.TAB: @@ -196,7 +196,7 @@ function Editor(doc, renderer) var container = renderer.getContainerElement(); this.renderer = renderer; - var textInput = new TextInput(container, this); + this.textInput = new TextInput(container, this); new KeyBinding(container, this); addListener(container, "mousedown", bind(this.onMouseDown, this)); @@ -247,14 +247,41 @@ Editor.prototype = { this.textInput.focus(); - var pos = this.renderer.screenToTextCoordinates(pageX, pageY); + var pos = this.renderer.screenToTextCoordinates(e.pageX, e.pageY); this.moveTo(pos.row, pos.column); this.setSelectionAnchor(pos.row, pos.column); - this.renderer.scrollCursorIntoView(); + + var _self = this; + var mousePageX, mousePageY; + + var onMouseSelection = function(e) { + mousePageX = e.pageX; + mousePageY = e.pageY; + }; + + var onMouseSelectionEnd = function() { + clearInterval(timerId); + }; + + var onSelectionInterval = function() + { + if (mousePageX === undefined || mousePageY === undefined) return; + + selectionLead = _self.renderer.screenToTextCoordinates(mousePageX, mousePageY); + + _self._moveSelection(function() { + _self.moveTo(selectionLead.row, selectionLead.column); + }); + _self.renderer.scrollCursorIntoView(); + }; + + capture(this.container, onMouseSelection, onMouseSelectionEnd); + var timerId = setInterval(onSelectionInterval, 100); + return preventDefault(e); }, - + onMouseWheel : function(e) { var delta = e.wheelDeltaY; @@ -282,24 +309,35 @@ Editor.prototype = }, onTextInput: function(text) - { - this.cursor = this.doc.insert(this.cursor, text); - this.clearSelection(); + { + if (this.hasSelection()) + { + this.cursor = this.doc.remove(this.getSelectionRange()); + this.clearSelection(); + } + this.cursor = this.doc.insert(this.cursor, text); this.draw(); this.renderer.scrollCursorIntoView(); }, removeRight : function() - { - var rangeEnd = { - row: this.cursor.row, - column: this.cursor.column + 1 + { + if (this.hasSelection()) + { + this.cursor = this.doc.remove(this.getSelectionRange()); } - if (rangeEnd.column > this.doc.getLine(this.cursor.row).length) { - rangeEnd.row += 1; - rangeEnd.column = 0; + else + { + var rangeEnd = { + row: this.cursor.row, + column: this.cursor.column + 1 + } + if (rangeEnd.column > this.doc.getLine(this.cursor.row).length) { + rangeEnd.row += 1; + rangeEnd.column = 0; + } + this.doc.remove({start: this.cursor, end: renageEnd}); } - this.doc.remove({start: this.cursor, end: renageEnd}); this.draw(); this.renderer.scrollCursorIntoView(); @@ -307,20 +345,27 @@ Editor.prototype = removeLeft : function() { - if (this.cursor.row == 0 && this.cursor.column == 0) { - return; - } - - var rangeStart = { - row: this.cursor.row, - column: this.cursor.column + -1 - } - if (rangeStart.column < 0) + if (this.hasSelection()) { - rangeStart.row -= 1; - rangeStart.column = this.doc.getLine(this.cursor.row-1).length; + this.cursor = this.doc.remove(this.getSelectionRange()); + } + else + { + if (this.cursor.row == 0 && this.cursor.column == 0) { + return; + } + + var rangeStart = { + row: this.cursor.row, + column: this.cursor.column + -1 + } + if (rangeStart.column < 0) + { + rangeStart.row -= 1; + rangeStart.column = this.doc.getLine(this.cursor.row-1).length; + } + this.cursor = this.doc.remove({start: rangeStart, end: this.cursor}); } - this.cursor = this.doc.remove({start: rangeStart, end: this.cursor}); this.draw(); this.renderer.scrollCursorIntoView(); @@ -393,8 +438,8 @@ Editor.prototype = setSelectionAnchor : function(row, column) { this.selectionAnchor = { - row: row, - column: column + 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)) }; this.selectionLead = null; diff --git a/VirtualRenderer.js b/VirtualRenderer.js index 33e4a367..0bd1efa7 100644 --- a/VirtualRenderer.js +++ b/VirtualRenderer.js @@ -313,17 +313,8 @@ VirtualRenderer.prototype.screenToTextCoordinates = function(pageX, pageY) { var canvasPos = this.container.getBoundingClientRect(); - if (pageX < canvasPos.left || pageX > canvasPos.right) { - col = null; - } else { - var col = Math.floor((pageX + this.container.scrollLeft - canvasPos.left) / this.characterWidth); - } - - if (pageY < canvasPos.top || pageY > canvasPos.bottom) { - row = null; - } else { - var row = Math.floor((pageY + this.scrollTop - canvasPos.top) / this.lineHeight); - } + var col = Math.floor((pageX + this.container.scrollLeft - canvasPos.left) / this.characterWidth); + var row = Math.floor((pageY + this.scrollTop - canvasPos.top) / this.lineHeight); return { row: row, diff --git a/lib.js b/lib.js index 0f973453..981c559b 100644 --- a/lib.js +++ b/lib.js @@ -78,4 +78,27 @@ bind = function(fcn, context) { return function() { return fcn.apply(context, arguments); } +} + +capture = function(el, eventHandler, releaseCaptureHandler) +{ + function onMouseMove(e) + { + eventHandler(e); + e.stopPropagation(); + } + + function onMouseUp(e) + { + eventHandler && eventHandler(e); + releaseCaptureHandler && releaseCaptureHandler(); + + document.removeEventListener("mousemove", onMouseMove, true); + document.removeEventListener("mouseup", onMouseUp, true); + + e.stopPropagation(); + } + + document.addEventListener("mousemove", onMouseMove, true); + document.addEventListener("mouseup", onMouseUp, true); } \ No newline at end of file From b127e92c01f7cde5e2f3ae8571b8171e89795974 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 7 Apr 2010 10:48:33 +0200 Subject: [PATCH 015/392] remove updateMarker method --- Editor.js | 5 ++--- VirtualRenderer.js | 9 --------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/Editor.js b/Editor.js index 14e2c744..47274acf 100644 --- a/Editor.js +++ b/Editor.js @@ -505,10 +505,9 @@ Editor.prototype = } if (this.selection) { - this.renderer.updateMarker(this.selection, this.getSelectionRange()); - } else { - this.selection = this.renderer.addMarker(this.getSelectionRange(), "selection"); + this.renderer.removeMarker(this.selection); } + this.selection = this.renderer.addMarker(this.getSelectionRange(), "selection"); }, selectUp : function() { diff --git a/VirtualRenderer.js b/VirtualRenderer.js index 0bd1efa7..3839ab50 100644 --- a/VirtualRenderer.js +++ b/VirtualRenderer.js @@ -231,15 +231,6 @@ VirtualRenderer.prototype.removeMarker = function(markerId) } }; -VirtualRenderer.prototype.updateMarker = function(markerId, range) -{ - var marker = this.markers[markerId]; - if (marker) { - marker.range = range; - this.draw(); - } -}; - VirtualRenderer.prototype.updateCursor = function(position) { this.cursorPos = { From a23ef8e775856e2ae03b27d34a02eaa4ec615d6b Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 7 Apr 2010 12:19:44 +0200 Subject: [PATCH 016/392] split layers into separate files --- CursorLayer.js | 58 +++++++++++ Editor.js | 4 +- MarkerLayer.js | 97 ++++++++++++++++++ TextDocument.js | 11 +- TextLayer.js | 78 ++++++++++++++ VirtualRenderer.js | 248 +++++++-------------------------------------- editor.css | 5 + editor.html | 3 + 8 files changed, 286 insertions(+), 218 deletions(-) create mode 100644 CursorLayer.js create mode 100644 MarkerLayer.js create mode 100644 TextLayer.js 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 @@ + + + From b79abd8de8b1359d7248abfc6f146f44dc4c0833 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 7 Apr 2010 14:36:27 +0200 Subject: [PATCH 017/392] blinking cursor --- CursorLayer.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CursorLayer.js b/CursorLayer.js index b7efcbb6..28a16148 100644 --- a/CursorLayer.js +++ b/CursorLayer.js @@ -24,12 +24,23 @@ CursorLayer.prototype.hideCursor = function() if (this.cursor.parentNode) { this.cursor.parentNode.removeChild(this.cursor); } + clearInterval(this.blinkId); }; CursorLayer.prototype.showCursor = function() { this.isVisible = true; this.element.appendChild(this.cursor); + + var cursor = this.cursor; + cursor.style.visibility = "visible"; + + this.blinkId = setInterval(function() { + cursor.style.visibility = "hidden"; + setTimeout(function() { + cursor.style.visibility = "visible"; + }, 400); + }, 1000); }; CursorLayer.prototype.getPixelPosition = function() { From 77cabfd9c8a4ae1efd7b09bff16e5effd4a7a183 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 7 Apr 2010 15:06:33 +0200 Subject: [PATCH 018/392] externalize the TextInput class --- Editor.js | 83 ---------------------------------------------------- TextInput.js | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++ editor.html | 1 + 3 files changed, 83 insertions(+), 83 deletions(-) create mode 100644 TextInput.js diff --git a/Editor.js b/Editor.js index 7b827fa5..959d9773 100644 --- a/Editor.js +++ b/Editor.js @@ -1,86 +1,3 @@ -function TextInput(parentNode, host) { - - var text = document.createElement("textarea"); - var style = text.style; - style.position = "absolute"; - style.left = "-10000px"; - style.top = "-10000px"; - parentNode.appendChild(text); - - var inCompostion = false; - - var onTextInput = function(e) { - setTimeout(function() { - if (!inCompostion) { - if (text.value) host.onTextInput(text.value); - text.value = ""; - } - }, 0) - } - - var onCompositionStart = function(e) - { - inCompostion = true; - - if (text.value) host.onTextInput(text.value); - text.value = ""; - - host.onCompositionStart(); - setTimeout(onCompositionUpdate, 0); - } - - var onCompositionUpdate = function() { - host.onCompositionUpdate(text.value); - } - - var onCompositionEnd = function() - { - inCompostion = false; - 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, "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); - - addListener(text, "blur", function() { - host.onBlur(); - }, false); - - addListener(text, "focus", function() { - host.onFocus(); - }, false); - - - this.focus = function() { - text.focus(); - } - - this.blur = function() { - this.blur(); - } -}; - var keys = { UP: 38, RIGHT: 39, diff --git a/TextInput.js b/TextInput.js new file mode 100644 index 00000000..c4ffb03b --- /dev/null +++ b/TextInput.js @@ -0,0 +1,82 @@ +function TextInput(parentNode, host) { + + var text = document.createElement("textarea"); + var style = text.style; + style.position = "absolute"; + style.left = "-10000px"; + style.top = "-10000px"; + parentNode.appendChild(text); + + var inCompostion = false; + + var onTextInput = function(e) { + setTimeout(function() { + if (!inCompostion) { + if (text.value) host.onTextInput(text.value); + text.value = ""; + } + }, 0) + } + + var onCompositionStart = function(e) + { + inCompostion = true; + + if (text.value) host.onTextInput(text.value); + text.value = ""; + + host.onCompositionStart(); + setTimeout(onCompositionUpdate, 0); + } + + var onCompositionUpdate = function() { + host.onCompositionUpdate(text.value); + } + + var onCompositionEnd = function() + { + inCompostion = false; + 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, "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); + + addListener(text, "blur", function() { + host.onBlur(); + }, false); + + addListener(text, "focus", function() { + host.onFocus(); + }, false); + + + this.focus = function() { + text.focus(); + } + + this.blur = function() { + this.blur(); + } +}; \ No newline at end of file diff --git a/editor.html b/editor.html index f39218f8..7c6ab42a 100644 --- a/editor.html +++ b/editor.html @@ -24,6 +24,7 @@ + From 6e236468a88f9fe78cbdcfc92b60523d9814a45f Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 7 Apr 2010 15:06:45 +0200 Subject: [PATCH 019/392] minor tweaks --- Editor.js | 2 +- editor.css | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Editor.js b/Editor.js index 959d9773..5b5e306f 100644 --- a/Editor.js +++ b/Editor.js @@ -194,7 +194,7 @@ Editor.prototype = }; capture(this.container, onMouseSelection, onMouseSelectionEnd); - var timerId = setInterval(onSelectionInterval, 20); + var timerId = setInterval(onSelectionInterval, 20 ); return preventDefault(e); }, diff --git a/editor.css b/editor.css index e571e121..5ba7b355 100644 --- a/editor.css +++ b/editor.css @@ -1,7 +1,7 @@ .editor { position: absolute; border: 1px solid black; - overflow-x: auto; + overflow-x: scroll; overflow-y: hidden; } @@ -28,7 +28,7 @@ .cursor { position: absolute; - width: 1px; + width: 2px; background: black; } From f8642b9339574b94d9316d85130b7b150ceadf40 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 7 Apr 2010 16:21:19 +0200 Subject: [PATCH 020/392] refactor CSS --- CursorLayer.js | 2 +- MarkerLayer.js | 2 +- TextLayer.js | 2 +- editor.css | 76 +++++++++++++++++++++++++++++++------------------- 4 files changed, 50 insertions(+), 32 deletions(-) diff --git a/CursorLayer.js b/CursorLayer.js index 28a16148..0475f54e 100644 --- a/CursorLayer.js +++ b/CursorLayer.js @@ -1,7 +1,7 @@ function CursorLayer(parentEl) { this.element = document.createElement("div"); - this.element.className = "cursor-layer"; + this.element.className = "layer cursor-layer"; parentEl.appendChild(this.element); this.cursor = document.createElement("div"); diff --git a/MarkerLayer.js b/MarkerLayer.js index 4bc21136..c399d4c7 100644 --- a/MarkerLayer.js +++ b/MarkerLayer.js @@ -1,7 +1,7 @@ function MarkerLayer(parentEl) { this.element = document.createElement("div"); - this.element.className = "markers"; + this.element.className = "layer marker-layer"; parentEl.appendChild(this.element); this.markers = {}; diff --git a/TextLayer.js b/TextLayer.js index 5d7f65ee..deafc2ee 100644 --- a/TextLayer.js +++ b/TextLayer.js @@ -1,7 +1,7 @@ function TextLayer(parentEl) { this.element = document.createElement("div"); - this.element.className = "canvas"; + this.element.className = "layer text-layer"; parentEl.appendChild(this.element); this._measureSizes(); diff --git a/editor.css b/editor.css index 5ba7b355..7a435d19 100644 --- a/editor.css +++ b/editor.css @@ -1,29 +1,61 @@ .editor { position: absolute; border: 1px solid black; - overflow-x: scroll; - overflow-y: hidden; + overflow: hidden; } .editor.focus { border: 1px solid #327fbd;; } -.editor .canvas { - z-index: 2; +.scroller { position: absolute; - overflow: hidden; + left: 50px; + right: 0; + bottom: 0; + top: 0; + overflow-x: scroll; + overflow-y: hidden; +} + +.gutter { + position: absolute; + width: 50px; + top: 0px; + bottom: 0px; + overflow-x: scroll; + overflow-y: hidden; + + background: rgb(227, 227, 227); + border-right: 1px solid rgb(159, 159, 159); + color: rgb(136, 136, 136); font-family: Monaco, "Courier New"; - font-size: 12px; + font-size: 12px; + + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.layer { + position: absolute; + overflow: hidden; white-space: nowrap; -webkit-box-sizing: border-box; box-sizing: border-box; - cursor: text; } - -.composition { - position: absolute; - text-decoration: underline; + +.gutter-layer { + right: 0; + text-align: right; + padding-right: 10px; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.text-layer { + font-family: Monaco, "Courier New"; + font-size: 12px; + cursor: text; } .cursor { @@ -36,34 +68,20 @@ white-space: nowrap; } -.line.odd { - /*background: #FAFAFA;*/ -} - -.keyword { +.line .keyword { color: blue; } -.string { +.line .string { color: rgb(3, 106, 7); } -.comment { +.line .comment { font-style: italic; color: rgb(0, 102, 255); } -.editor .markers { - position: absolute; - z-index: 1; -} - -.editor .cursor-layer { - position: absolute; - z-index: 3; -} - -.selection { +.marker-layer .selection { position: absolute; background: rgba(77, 151, 255, 0.33); } \ No newline at end of file From 9315c374e6cbbf5b3e89d5a70ba835eb48467cd6 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 7 Apr 2010 16:27:02 +0200 Subject: [PATCH 021/392] optimize longest line calculation --- TextDocument.js | 52 +++++++++++++++++++++++++++++++++------------- VirtualRenderer.js | 18 +++++++--------- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/TextDocument.js b/TextDocument.js index 91b52c4b..96bf48f7 100644 --- a/TextDocument.js +++ b/TextDocument.js @@ -1,5 +1,7 @@ -function TextDocument(text) { +function TextDocument(text) +{ this.lines = this._split(text); + this.modified = true; } TextDocument.prototype = @@ -8,6 +10,22 @@ TextDocument.prototype = return text.split(/[\n\r]/) }, + getWidth : function() + { + if (this.modified) + { + this.modified = false; + + var lines = this.lines; + var longestLine = 0; + for (var i=0; i < lines.length; i++) { + longestLine = Math.max(longestLine, lines[i].length); + } + this.width = longestLine; + } + return this.width; + }, + getLine : function(row) { return this.lines[row] || ""; }, @@ -72,8 +90,24 @@ TextDocument.prototype = return this.lines.length; }, + getTextRange : function(range) + { + if (range.start.row == range.end.row) { + return this.lines[range.start.row].substring(range.start.column, range.end.column); + } else { + 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"); + } + }, + insert : function(position, text) { + this.modified = true; + var newLines = this._split(text); if (text == "\n") @@ -117,23 +151,11 @@ 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 { - 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"); - } - }, remove : function(range) { + this.modified = true; + var firstRow = range.start.row; var lastRow = range.end.row; diff --git a/VirtualRenderer.js b/VirtualRenderer.js index 00682073..fb6d57a1 100644 --- a/VirtualRenderer.js +++ b/VirtualRenderer.js @@ -25,6 +25,7 @@ function VirtualRenderer(containerId) VirtualRenderer.prototype.setDocument = function(doc) { this.lines = doc.lines; + this.doc = doc; this.textLayer.setDocument(doc); }; @@ -32,23 +33,18 @@ VirtualRenderer.prototype.getContainerElement = function() { return this.container; }; -VirtualRenderer.prototype.getLongestLineWidth = function(lines) -{ - var longestLine = this.container.clientWidth; - for (var i=0; i < lines.length; i++) { - longestLine = Math.max(longestLine, (lines[i].length * this.characterWidth)); - } - return longestLine; -}; - VirtualRenderer.prototype.draw = function() { var lines = this.lines; var offset = this.scrollTop % this.lineHeight; - var minHeight = this.container.clientHeight + offset; + var minHeight = this.scroller.clientHeight + offset; + + var longestLine = Math.max( + this.scroller.clientWidth, + this.doc.getWidth() * this.characterWidth + ); - var longestLine = this.getLongestLineWidth(lines); var lineCount = Math.ceil(minHeight / this.lineHeight); var firstRow = Math.round((this.scrollTop - offset) / this.lineHeight); var lastRow = Math.min(lines.length, firstRow+lineCount); From 99b81b0b04cf8ebf7c3e3b286263914a5d019c27 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 7 Apr 2010 16:27:17 +0200 Subject: [PATCH 022/392] gutter support --- GutterLayer.js | 22 ++++++++++++++++++++++ VirtualRenderer.js | 38 ++++++++++++++++++++++++++------------ editor.html | 1 + lib.js | 16 ++++++++++++++++ 4 files changed, 65 insertions(+), 12 deletions(-) create mode 100644 GutterLayer.js diff --git a/GutterLayer.js b/GutterLayer.js new file mode 100644 index 00000000..cddac987 --- /dev/null +++ b/GutterLayer.js @@ -0,0 +1,22 @@ +function GutterLayer(parentEl) +{ + this.element = document.createElement("div"); + this.element.className = "layer gutter-layer"; + parentEl.appendChild(this.element); +} + +GutterLayer.prototype.update = function(config) +{ + var html = []; + for (var i=config.firstRow; i", + i, + "" + ); + html.push(""); + } + + this.element.innerHTML = html.join(""); +}; \ No newline at end of file diff --git a/VirtualRenderer.js b/VirtualRenderer.js index fb6d57a1..f2baa828 100644 --- a/VirtualRenderer.js +++ b/VirtualRenderer.js @@ -3,14 +3,24 @@ function VirtualRenderer(containerId) this.container = document.getElementById(containerId); this.container.className += "editor"; - var textLayer = this.textLayer = new TextLayer(this.container); + this.scroller = document.createElement("div"); + this.scroller.className = "scroller"; + this.container.appendChild(this.scroller); + + this.gutter = document.createElement("div"); + this.gutter.className = "gutter"; + this.container.appendChild(this.gutter); + + this.gutterLayer = new GutterLayer(this.gutter); + this.markerLayer = new MarkerLayer(this.scroller); + + var textLayer = this.textLayer = new TextLayer(this.scroller); this.canvas = textLayer.element; this.characterWidth = textLayer.getCharacterWidth(); this.lineHeight = textLayer.getLineHeight(); - this.cursorLayer = new CursorLayer(this.container); - this.markerLayer = new MarkerLayer(this.container); + this.cursorLayer = new CursorLayer(this.scroller); this.layers = [this.markerLayer, textLayer, this.cursorLayer]; @@ -68,6 +78,10 @@ VirtualRenderer.prototype.draw = function() layer.update(layerConfig); }; + + this.gutterLayer.element.style.marginTop = (-offset) + "px"; + this.gutterLayer.element.style.height = minHeight + "px"; + this.gutterLayer.update(layerConfig); } VirtualRenderer.prototype.addMarker = function(range, clazz) { @@ -103,16 +117,16 @@ VirtualRenderer.prototype.scrollCursorIntoView = function() this.scrollToY(top); } - if (this.getScrollTop() + this.container.clientHeight < top + this.lineHeight) { - this.scrollToY(top + this.lineHeight - this.container.clientHeight); + if (this.getScrollTop() + this.scroller.clientHeight < top + this.lineHeight) { + this.scrollToY(top + this.lineHeight - this.scroller.clientHeight); } - if (this.container.scrollLeft > left) { - this.container.scrollLeft = left; + if (this.scroller.scrollLeft > left) { + this.scroller.scrollLeft = left; } - if (this.container.scrollLeft + this.container.clientWidth < left + this.characterWidth) { - this.container.scrollLeft = left + this.characterWidth - this.container.clientWidth; + if (this.scroller.scrollLeft + this.scroller.clientWidth < left + this.characterWidth) { + this.scroller.scrollLeft = left + this.characterWidth - this.scroller.clientWidth; } }, @@ -122,7 +136,7 @@ VirtualRenderer.prototype.getScrollTop = function() { VirtualRenderer.prototype.scrollToY = function(scrollTop) { - var maxHeight = this.lines.length * this.lineHeight - this.container.offsetHeight; + var maxHeight = this.lines.length * this.lineHeight - this.scroller.offsetHeight; var scrollTop = Math.max(0, Math.min(maxHeight, scrollTop)); if (this.scrollTop !== scrollTop) { @@ -133,9 +147,9 @@ VirtualRenderer.prototype.scrollToY = function(scrollTop) VirtualRenderer.prototype.screenToTextCoordinates = function(pageX, pageY) { - var canvasPos = this.container.getBoundingClientRect(); + var canvasPos = this.scroller.getBoundingClientRect(); - var col = Math.floor((pageX + this.container.scrollLeft - canvasPos.left) / this.characterWidth); + var col = Math.floor((pageX + this.scroller.scrollLeft - canvasPos.left) / this.characterWidth); var row = Math.floor((pageY + this.scrollTop - canvasPos.top) / this.lineHeight); return { diff --git a/editor.html b/editor.html index 7c6ab42a..bff1a2a8 100644 --- a/editor.html +++ b/editor.html @@ -22,6 +22,7 @@ + diff --git a/lib.js b/lib.js index 981c559b..5db700ff 100644 --- a/lib.js +++ b/lib.js @@ -74,6 +74,22 @@ computedStyle = function(element, style) } } +scrollbarHeight = function() { + var el = document.createElement("div"); + var style = el.style; + + style.position = "absolute"; + style.left = "-10000px"; + style.overflow = "scroll"; + style.height = "100px"; + + document.body.appendChild(el); + var height = el.offsetHeight - el.clientHeight; + document.body.removeChild(el); + + return height; +} + bind = function(fcn, context) { return function() { return fcn.apply(context, arguments); From cc5cd114e604ca4f99850b13a89e94977043cc36 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 7 Apr 2010 17:40:42 +0200 Subject: [PATCH 023/392] Fix FF 3.6 issue, where mono spaced characters can have fixed sub pixel widths. --- CursorLayer.js | 4 ++-- MarkerLayer.js | 10 +++++----- TextLayer.js | 9 ++++++--- VirtualRenderer.js | 6 +++--- editor.css | 2 +- editor.html | 1 - 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/CursorLayer.js b/CursorLayer.js index 0475f54e..41d88cda 100644 --- a/CursorLayer.js +++ b/CursorLayer.js @@ -50,8 +50,8 @@ CursorLayer.prototype.getPixelPosition = function() { CursorLayer.prototype.update = function(config) { if (!this.position) return; - - var cursorLeft = this.position.column * config.characterWidth; + + var cursorLeft = Math.round(this.position.column * config.characterWidth); var cursorTop = this.position.row * config.lineHeight; this.pixelPos = { diff --git a/MarkerLayer.js b/MarkerLayer.js index c399d4c7..45db2b72 100644 --- a/MarkerLayer.js +++ b/MarkerLayer.js @@ -50,9 +50,9 @@ MarkerLayer.prototype.update = function(config) html.push( "
" + "left:", Math.round(range.start.column * config.characterWidth), "px;'>" ); } @@ -62,7 +62,7 @@ MarkerLayer.prototype.update = function(config) "
" + "width:", Math.round(range.end.column * config.characterWidth), "px;'>" ); }; @@ -86,9 +86,9 @@ MarkerLayer.prototype.update = function(config) html.push( "
" + "left:", Math.round(range.start.column * config.characterWidth), "px;'>" ); } } diff --git a/TextLayer.js b/TextLayer.js index deafc2ee..cc67b914 100644 --- a/TextLayer.js +++ b/TextLayer.js @@ -30,11 +30,14 @@ TextLayer.prototype._measureSizes = function() style.position = "absolute"; style.overflow = "visible"; - measureNode.innerHTML = "X
X"; + measureNode.innerHTML = new Array(1000).join("Xy"); this.element.appendChild(measureNode); - this.lineHeight = Math.round(measureNode.offsetHeight / 2); - this.characterWidth = measureNode.offsetWidth; + // in FF 3.6 monospace fonts can have a fixed sub pixel width. + // that's why we have to measure many characters + // Note: characterWidth can be a float! + this.lineHeight = measureNode.offsetHeight; + this.characterWidth = measureNode.offsetWidth / 2000; this.element.removeChild(measureNode); }; diff --git a/VirtualRenderer.js b/VirtualRenderer.js index f2baa828..dab66ed7 100644 --- a/VirtualRenderer.js +++ b/VirtualRenderer.js @@ -52,7 +52,7 @@ VirtualRenderer.prototype.draw = function() var longestLine = Math.max( this.scroller.clientWidth, - this.doc.getWidth() * this.characterWidth + Math.round(this.doc.getWidth() * this.characterWidth) ); var lineCount = Math.ceil(minHeight / this.lineHeight); @@ -126,7 +126,7 @@ VirtualRenderer.prototype.scrollCursorIntoView = function() } if (this.scroller.scrollLeft + this.scroller.clientWidth < left + this.characterWidth) { - this.scroller.scrollLeft = left + this.characterWidth - this.scroller.clientWidth; + this.scroller.scrollLeft = Math.round(left + this.characterWidth - this.scroller.clientWidth); } }, @@ -136,7 +136,7 @@ VirtualRenderer.prototype.getScrollTop = function() { VirtualRenderer.prototype.scrollToY = function(scrollTop) { - var maxHeight = this.lines.length * this.lineHeight - this.scroller.offsetHeight; + var maxHeight = this.lines.length * this.lineHeight - this.scroller.clientHeight; var scrollTop = Math.max(0, Math.min(maxHeight, scrollTop)); if (this.scrollTop !== scrollTop) { diff --git a/editor.css b/editor.css index 7a435d19..e4142ba0 100644 --- a/editor.css +++ b/editor.css @@ -53,7 +53,7 @@ } .text-layer { - font-family: Monaco, "Courier New"; + font-family: Monaco, "Courier New", monospace; font-size: 12px; cursor: text; } diff --git a/editor.html b/editor.html index bff1a2a8..e984a58c 100644 --- a/editor.html +++ b/editor.html @@ -39,7 +39,6 @@ var editor = new Editor(new TextDocument("Juhu Kinners"), new VirtualRenderer("container")); window.onresize = function() { - console.log("resize"); editor.resize(); } From 7cce9d8858fe45e46d96b7a61d919c6d353f25a0 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 7 Apr 2010 17:49:16 +0200 Subject: [PATCH 024/392] editor fills whole page --- editor.css | 4 ++-- editor.html | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/editor.css b/editor.css index e4142ba0..f101ee35 100644 --- a/editor.css +++ b/editor.css @@ -1,11 +1,11 @@ .editor { position: absolute; - border: 1px solid black; + border: 2px solid rgb(159, 159, 159); overflow: hidden; } .editor.focus { - border: 1px solid #327fbd;; + border: 2px solid #327fbd;; } .scroller { diff --git a/editor.html b/editor.html index e984a58c..58ea2c92 100644 --- a/editor.html +++ b/editor.html @@ -10,10 +10,10 @@ From f0bf1dd88c7ba9014b87b4416cf5ffbb7f7ba108 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 7 Apr 2010 18:32:40 +0200 Subject: [PATCH 025/392] add change event support to the text document --- Editor.js | 8 ++++++-- TextDocument.js | 54 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/Editor.js b/Editor.js index 5b5e306f..228280ce 100644 --- a/Editor.js +++ b/Editor.js @@ -120,6 +120,9 @@ function Editor(doc, renderer) addListener(container, "mousewheel", bind(this.onMouseWheel, this)); this.doc = doc; + doc.addChangeListener(function(startRow, endRow) { + console.log(startRow, endRow); + }); renderer.setDocument(doc); this.cursor = { @@ -229,10 +232,11 @@ Editor.prototype = { if (this.hasSelection()) { - this.cursor = this.doc.remove(this.getSelectionRange()); + this.cursor = this.doc.replace(this.getSelectionRange(), text); this.clearSelection(); + } else { + this.cursor = this.doc.insert(this.cursor, text); } - this.cursor = this.doc.insert(this.cursor, text); this.draw(); this.renderer.scrollCursorIntoView(); }, diff --git a/TextDocument.js b/TextDocument.js index 96bf48f7..ff869a5a 100644 --- a/TextDocument.js +++ b/TextDocument.js @@ -2,6 +2,8 @@ function TextDocument(text) { this.lines = this._split(text); this.modified = true; + + this.listeners = []; } TextDocument.prototype = @@ -10,6 +12,21 @@ TextDocument.prototype = return text.split(/[\n\r]/) }, + addChangeListener : function(listener) { + this.listeners.push(listener); + }, + + fireChangeEvent : function(firstRow, lastRow) + { + if (lastRow === undefined) { + lastRow = this.lines.length-1; + } + + for (var i=0; i < this.listeners.length; i++) { + this.listeners[i](firstRow, lastRow); + }; + }, + getWidth : function() { if (this.modified) @@ -104,7 +121,17 @@ TextDocument.prototype = } }, - insert : function(position, text) + insert : function(position, text) + { + var end = this._insert(position, text); + this.fireChangeEvent( + position.row, + position.row == end.row ? position.row : undefined + ); + return end; + }, + + _insert : function(position, text) { this.modified = true; @@ -153,6 +180,17 @@ TextDocument.prototype = }, remove : function(range) + { + var end = this._remove(range); + + this.fireChangeEvent( + range.start.row, + range.end.row == range.start.row ? range.start.row : undefined + ); + return end; + }, + + _remove : function(range) { this.modified = true; @@ -170,11 +208,19 @@ TextDocument.prototype = replace : function(range, text) { - this.remove(range); + this._remove(range); if (text) { - return this.insert(range.start, text); + var end = this._insert(range.start, text); } else { - return range.start; + end = range.start; } + + var lastRemoved = range.end.column == 0 ? range.end.column-1 : range.end.column; + this.fireChangeEvent( + range.start.row, + lastRemoved == end.row ? lastRemoved : undefined + ); + + return end; } } \ No newline at end of file From 9ee77b62054766caf38450080d825ad583ecfb08 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 8 Apr 2010 13:14:32 +0200 Subject: [PATCH 026/392] triple click selects the whole line --- Editor.js | 21 +++++++++++++++++++-- experiments/triple_click.html | 28 ++++++++++++++++++++++++++++ lib.js | 35 +++++++++++++++++++++++++++++++++-- 3 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 experiments/triple_click.html diff --git a/Editor.js b/Editor.js index 228280ce..fe08dc5c 100644 --- a/Editor.js +++ b/Editor.js @@ -118,6 +118,7 @@ function Editor(doc, renderer) addListener(container, "mousedown", bind(this.onMouseDown, this)); addListener(container, "mousewheel", bind(this.onMouseWheel, this)); + addTripleClickListener(container, bind(this.selectCurrentLine, this)); this.doc = doc; doc.addChangeListener(function(startRow, endRow) { @@ -457,14 +458,30 @@ Editor.prototype = this._moveSelection(this.moveLineEnd); }, + selectCurrentLine : function() + { + this.setSelectionAnchor(this.cursor.row, 0); + this._moveSelection(function() { + this.moveTo(this.cursor.row+1, 0); + }); + }, + moveBy : function(rows, chars) { this.moveTo(this.cursor.row+rows, this.cursor.column+chars); }, moveTo : function(row, column) { - this.cursor.row = Math.min(this.doc.getLength()-1, Math.max(0, row)); - this.cursor.column = Math.min(this.doc.getLine(this.cursor.row).length, Math.max(0, column)); + if (row >= this.doc.getLength()) { + this.cursor.row = this.doc.getLength()-1; + this.cursor.column = this.doc.getLine(this.cursor.row).length; + } else if (row < 0) { + this.cursor.row = 0; + this.cursor.column = 0; + } else { + this.cursor.row = row; + this.cursor.column = Math.min(this.doc.getLine(this.cursor.row).length, Math.max(0, column)); + } this.updateCursor(); } } \ No newline at end of file diff --git a/experiments/triple_click.html b/experiments/triple_click.html new file mode 100644 index 00000000..bdc3caa7 --- /dev/null +++ b/experiments/triple_click.html @@ -0,0 +1,28 @@ + + + + + + triple_click + + + + + +
+ Juhu Kinners +
+ + + + + + diff --git a/lib.js b/lib.js index 5db700ff..2a2301be 100644 --- a/lib.js +++ b/lib.js @@ -3,9 +3,20 @@ function addListener(elem, type, callback) { return elem.addEventListener(type, callback, false); } if (elem.attachEvent) { - elem.attachEvent("on" + type, function() { + var wrapper = function() { callback(window.event); - }); + } + callback.$$wrapper = wrapper; + elem.attachEvent("on" + type, wrapper); + } +} + +function removeListener(elem, type, callback) { + if (elem.removeEventListener) { + return elem.removeEventListener(type, callback, false); + } + if (elem.detachEvent) { + elem.detachEvent("on" + type, callback.$$wrapper || callback); } } @@ -117,4 +128,24 @@ capture = function(el, eventHandler, releaseCaptureHandler) document.addEventListener("mousemove", onMouseMove, true); document.addEventListener("mouseup", onMouseUp, true); +} + +function addTripleClickListener(el, callback) +{ + addListener(el, "dblclick", function() + { + var listener = function(e) + { + clearTimeout(timeoutId); + remove(); + callback(e); + } + + var remove = function() { + removeListener(el, "click", listener); + }; + + addListener(el, "click", listener); + var timeoutId = setTimeout(remove, 300); + }); } \ No newline at end of file From f74b72b0fbbeada2a5b0571438dd1e6e138447c8 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 8 Apr 2010 13:15:21 +0200 Subject: [PATCH 027/392] double click selects word at cursor --- Editor.js | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Editor.js b/Editor.js index fe08dc5c..e57bea45 100644 --- a/Editor.js +++ b/Editor.js @@ -117,6 +117,7 @@ function Editor(doc, renderer) new KeyBinding(container, this); addListener(container, "mousedown", bind(this.onMouseDown, this)); + addListener(container, "dblclick", bind(this.onMouseDoubleClick, this)); addListener(container, "mousewheel", bind(this.onMouseWheel, this)); addTripleClickListener(container, bind(this.selectCurrentLine, this)); @@ -202,7 +203,46 @@ Editor.prototype = return preventDefault(e); }, + + onMouseDoubleClick : function(e) + { + var line = this.doc.getLine(this.cursor.row); + var column = this.cursor.column; + var tokenRe = /[a-zA-Z0-9_]+/g; + var nonTokenRe = /[^a-zA-Z0-9_]+/g; + + var inToken = false; + if (column > 0) { + inToken = !!line.charAt(column-1).match(tokenRe); + } + + if (!inToken) { + inToken = !!line.charAt(column).match(tokenRe); + } + + var re = inToken ? tokenRe : nonTokenRe; + + var start = column; + if (start > 0) + { + do { + start--; + } while (start >= 0 && line.charAt(start).match(re)) + start++; + } + + var end = column; + while (end < line.length && line.charAt(end).match(re)) { + end++; + } + + this.setSelectionAnchor(this.cursor.row, start); + this._moveSelection(function() { + this.moveTo(this.cursor.row, end); + }); + }, + onMouseWheel : function(e) { var delta = e.wheelDeltaY; From 8135c9a612d239716c87c10a64a1034263d8a78d Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 8 Apr 2010 13:16:03 +0200 Subject: [PATCH 028/392] only update text layer if the document changes --- Editor.js | 60 ++++++++++++++++++++++------------------------ TextDocument.js | 4 ---- TextLayer.js | 17 +++++++++++++ VirtualRenderer.js | 20 ++++++++++++++++ 4 files changed, 65 insertions(+), 36 deletions(-) diff --git a/Editor.js b/Editor.js index e57bea45..5c1be564 100644 --- a/Editor.js +++ b/Editor.js @@ -122,41 +122,33 @@ function Editor(doc, renderer) addTripleClickListener(container, bind(this.selectCurrentLine, this)); this.doc = doc; - doc.addChangeListener(function(startRow, endRow) { - console.log(startRow, endRow); - }); + doc.addChangeListener(bind(this.onDocumentChange, this)); renderer.setDocument(doc); this.cursor = { row: 0, column: 0 - }; - - this.selectionAnchor = null; - this.selectionLead = null; - this.selection = null; - - this.draw(); -} + }; + + this.selectionAnchor = null; + this.selectionLead = null; + this.selection = null; -Editor.prototype = -{ - draw : function() - { this.renderer.draw(); - this.renderer.updateCursor(this.cursor); - }, - - resize : function() { - this.renderer.draw(); - }, - - updateCursor : function() { - this.renderer.updateCursor(this.cursor); - }, - - onFocus : function() { - this.renderer.showCursor(); + } + + Editor.prototype = + { + resize : function() { + this.renderer.draw(); + }, + + updateCursor : function() { + this.renderer.updateCursor(this.cursor); + }, + + onFocus : function() { + this.renderer.showCursor(); this.renderer.visualizeFocus(); }, @@ -165,6 +157,10 @@ Editor.prototype = this.renderer.visualizeBlur(); }, + onDocumentChange : function(startRow, endRow) { + this.renderer.updateLines(startRow, endRow); + }, + onMouseDown : function(e) { this.textInput.focus(); @@ -265,7 +261,7 @@ Editor.prototype = { this.cursor = this.doc.remove(this.getSelectionRange()); this.clearSelection(); - this.draw(); + this.renderer.updateCursor(this.cursor); } }, @@ -278,7 +274,7 @@ Editor.prototype = } else { this.cursor = this.doc.insert(this.cursor, text); } - this.draw(); + this.renderer.updateCursor(this.cursor); this.renderer.scrollCursorIntoView(); }, @@ -287,6 +283,7 @@ Editor.prototype = if (this.hasSelection()) { this.cursor = this.doc.remove(this.getSelectionRange()); + this.renderer.updateCursor(this.cursor); } else { @@ -301,7 +298,6 @@ Editor.prototype = this.doc.remove({start: this.cursor, end: renageEnd}); } - this.draw(); this.renderer.scrollCursorIntoView(); }, @@ -329,7 +325,7 @@ Editor.prototype = this.cursor = this.doc.remove({start: rangeStart, end: this.cursor}); } - this.draw(); + this.renderer.updateCursor(this.cursor); this.renderer.scrollCursorIntoView(); }, diff --git a/TextDocument.js b/TextDocument.js index ff869a5a..46164f1e 100644 --- a/TextDocument.js +++ b/TextDocument.js @@ -18,10 +18,6 @@ TextDocument.prototype = fireChangeEvent : function(firstRow, lastRow) { - if (lastRow === undefined) { - lastRow = this.lines.length-1; - } - for (var i=0; i < this.listeners.length; i++) { this.listeners[i](firstRow, lastRow); }; diff --git a/TextLayer.js b/TextLayer.js index cc67b914..918b20cd 100644 --- a/TextLayer.js +++ b/TextLayer.js @@ -42,6 +42,23 @@ TextLayer.prototype._measureSizes = function() this.element.removeChild(measureNode); }; +TextLayer.prototype.updateLines = function(layerConfig, firstRow, lastRow) +{ + var first = Math.max(firstRow, layerConfig.firstRow); + var last = Math.min(lastRow, layerConfig.lastRow); + + var lineElements = this.element.childNodes; + + for (var i=first; i <= last; i++) + { + var html = []; + this.renderLine(html, i); + + var lineElement = lineElements[i-layerConfig.firstRow]; + lineElement.innerHTML = html.join(""); + }; +}; + TextLayer.prototype.update = function(config) { var html = []; diff --git a/VirtualRenderer.js b/VirtualRenderer.js index dab66ed7..594f7d5e 100644 --- a/VirtualRenderer.js +++ b/VirtualRenderer.js @@ -43,6 +43,26 @@ VirtualRenderer.prototype.getContainerElement = function() { return this.container; }; +VirtualRenderer.prototype.updateLines = function(firstRow, lastRow) +{ + var layerConfig = this.layerConfig; + + // if the first row is below the viewport -> ignore it + if (firstRow > layerConfig.lastRow+1) { + return; + } + + // if the last row is unknow -> redraw everything + if (lastRow === undefined) + { + this.draw() + return; + } + + // else update only the changed rows + this.textLayer.updateLines(layerConfig, firstRow, lastRow); +}; + VirtualRenderer.prototype.draw = function() { var lines = this.lines; From de732e19063c2dbcedd866da9e4603c8d31a31da Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 8 Apr 2010 13:28:04 +0200 Subject: [PATCH 029/392] update triple click support --- lib.js | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/lib.js b/lib.js index 2a2301be..744e89aa 100644 --- a/lib.js +++ b/lib.js @@ -130,22 +130,28 @@ capture = function(el, eventHandler, releaseCaptureHandler) document.addEventListener("mouseup", onMouseUp, true); } +function autoRemoveListener(el, type, callback, timeout) +{ + var listener = function(e) + { + clearTimeout(timeoutId); + remove(); + callback(e); + } + + var remove = function() { + removeListener(el, type, listener); + }; + + addListener(el, type, listener); + var timeoutId = setTimeout(remove, timeout); +} + function addTripleClickListener(el, callback) { - addListener(el, "dblclick", function() - { - var listener = function(e) - { - clearTimeout(timeoutId); - remove(); - callback(e); - } - - var remove = function() { - removeListener(el, "click", listener); - }; - - addListener(el, "click", listener); - var timeoutId = setTimeout(remove, 300); + addListener(el, "mousedown", function() { + autoRemoveListener(el, "mousedown", function() { + autoRemoveListener(el, "mousedown", callback, 300); + }, 300); }); } \ No newline at end of file From dd3bf54396b5a02e1c633828216af3b477b57c58 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 8 Apr 2010 14:47:52 +0200 Subject: [PATCH 030/392] fix text copy bug --- TextDocument.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TextDocument.js b/TextDocument.js index 46164f1e..8d557899 100644 --- a/TextDocument.js +++ b/TextDocument.js @@ -110,7 +110,7 @@ TextDocument.prototype = } else { 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.apply(lines, this.lines.slice(range.start.row+1, range.end.row)); lines.push(this.lines[range.end.row].substring(0, range.end.column)); return lines.join("\n"); From 60822b385fbd5d9ec0007ae1d039e00ab5f39c61 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 8 Apr 2010 15:01:33 +0200 Subject: [PATCH 031/392] Separate navigation from cursor movement --- Editor.js | 173 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 108 insertions(+), 65 deletions(-) diff --git a/Editor.js b/Editor.js index 5c1be564..9e723f32 100644 --- a/Editor.js +++ b/Editor.js @@ -31,8 +31,7 @@ function KeyBinding(element, host) if (e.shiftKey) { host.selectUp(); } else { - host.clearSelection(); - host.moveUp(); + host.navigateUp(); } return stopEvent(e); @@ -40,8 +39,7 @@ function KeyBinding(element, host) if (e.shiftKey) { host.selectDown(); } else { - host.clearSelection(); - host.moveDown(); + host.navigateDown(); } return stopEvent(e); @@ -49,13 +47,11 @@ function KeyBinding(element, host) if (e.metaKey && e.shiftKey) { host.selectLineStart(); } else if (e.metaKey) { - host.clearSelection(); - host.moveLineStart(); + host.navigateLineStart(); } else if (e.shiftKey) { host.selectLeft(); } else { - host.clearSelection(); - host.moveLeft(); + host.navigateLeft(); } return stopEvent(e); @@ -63,13 +59,11 @@ function KeyBinding(element, host) if (e.metaKey && e.shiftKey) { host.selectLineEnd(); } else if (e.metaKey) { - host.clearSelection(); - host.moveLineEnd(); + host.navigateLineEnd(); } else if (e.shiftKey) { host.selectRight(); } else { - host.clearSelection(); - host.moveRight(); + host.navigateRight(); } return stopEvent(e); @@ -77,8 +71,7 @@ function KeyBinding(element, host) if (e.shiftKey) { host.selectLineStart(); } else { - host.clearSelection(); - host.moveLineStart(); + host.navigateLineStart(); } return stopEvent(e); @@ -86,19 +79,16 @@ function KeyBinding(element, host) if (e.shiftKey) { host.selectLineEnd(); } else { - host.clearSelection(); - host.moveLineEnd(); + host.navigateLineEnd(); } return stopEvent(e); case keys.DELETE: host.removeRight(); - host.clearSelection(); return stopEvent(e); case keys.BACKSPACE: host.removeLeft(); - host.clearSelection(); return stopEvent(e); case keys.TAB: @@ -166,7 +156,7 @@ function Editor(doc, renderer) this.textInput.focus(); var pos = this.renderer.screenToTextCoordinates(e.pageX, e.pageY); - this.moveTo(pos.row, pos.column); + this.moveCursorTo(pos.row, pos.column); this.setSelectionAnchor(pos.row, pos.column); this.renderer.scrollCursorIntoView(); @@ -189,7 +179,7 @@ function Editor(doc, renderer) selectionLead = _self.renderer.screenToTextCoordinates(mousePageX, mousePageY); _self._moveSelection(function() { - _self.moveTo(selectionLead.row, selectionLead.column); + _self.moveCursorTo(selectionLead.row, selectionLead.column); }); _self.renderer.scrollCursorIntoView(); }; @@ -235,7 +225,7 @@ function Editor(doc, renderer) this.setSelectionAnchor(this.cursor.row, start); this._moveSelection(function() { - this.moveTo(this.cursor.row, end); + this.moveCursorTo(this.cursor.row, end); }); }, @@ -284,6 +274,7 @@ function Editor(doc, renderer) { this.cursor = this.doc.remove(this.getSelectionRange()); this.renderer.updateCursor(this.cursor); + this.clearSelection(); } else { @@ -306,6 +297,7 @@ function Editor(doc, renderer) if (this.hasSelection()) { this.cursor = this.doc.remove(this.getSelectionRange()); + this.clearSelection(); } else { @@ -344,51 +336,120 @@ function Editor(doc, renderer) this.removeLeft(); }, - moveUp : function() { - this.moveBy(-1, 0); + navigateUp : function() + { + this.clearSelection(); + this.moveCursorUp(); this.renderer.scrollCursorIntoView(); }, - - moveDown : function() { - this.moveBy(1, 0); + + navigateDown : function() { + this.clearSelection(); + this.moveCursorDown(); + this.renderer.scrollCursorIntoView(); + }, + + navigateLeft : function() + { + if (this.hasSelection()) { + var selectionStart = this.getSelectionRange().start; + this.moveCursorTo(selectionStart.row, selectionStart.column); + } else { + this.moveCursorLeft(); + } + this.clearSelection(); + + this.renderer.scrollCursorIntoView(); + }, + + navigateRight : function() + { + if (this.hasSelection()) { + var selectionEnd = this.getSelectionRange().end; + this.moveCursorTo(selectionEnd.row, selectionEnd.column); + } else { + this.moveCursorRight(); + } + this.clearSelection(); + + this.renderer.scrollCursorIntoView(); + }, + + navigateLineStart : function() + { + this.clearSelection(); + this.moveCursorLineStart(); this.renderer.scrollCursorIntoView(); }, + + navigateLineEnd : function() + { + this.clearSelection(); + this.moveCursorLineEnd(); + this.renderer.scrollCursorIntoView(); + }, + + moveCursorUp : function() { + this.moveCursorBy(-1, 0); + }, - moveLeft : function() + moveCursorDown : function() { + this.moveCursorBy(1, 0); + }, + + moveCursorLeft : function() { if (this.cursor.column == 0) { if (this.cursor.row > 0) { - this.moveTo(this.cursor.row-1, this.doc.getLine(this.cursor.row-1).length); + this.moveCursorTo(this.cursor.row-1, this.doc.getLine(this.cursor.row-1).length); } } else { - this.moveBy(0, -1); + this.moveCursorBy(0, -1); } - this.renderer.scrollCursorIntoView(); }, - moveRight : function() + moveCursorRight : function() { if (this.cursor.column == this.doc.getLine(this.cursor.row).length) { if (this.cursor.row < this.doc.getLength()-1) { - this.moveTo(this.cursor.row+1, 0); + this.moveCursorTo(this.cursor.row+1, 0); } } else { - this.moveBy(0, 1); + this.moveCursorBy(0, 1); } this.renderer.scrollCursorIntoView(); }, - moveLineStart : function() + moveCursorLineStart : function() { - this.moveTo(this.cursor.row, 0); + this.moveCursorTo(this.cursor.row, 0); this.renderer.scrollCursorIntoView(); }, - moveLineEnd : function() { - this.moveTo(this.cursor.row, this.doc.getLine(this.cursor.row).length); + moveCursorLineEnd : function() { + this.moveCursorTo(this.cursor.row, this.doc.getLine(this.cursor.row).length); this.renderer.scrollCursorIntoView(); }, + moveCursorBy : function(rows, chars) { + this.moveCursorTo(this.cursor.row+rows, this.cursor.column+chars); + }, + + moveCursorTo : function(row, column) + { + if (row >= this.doc.getLength()) { + this.cursor.row = this.doc.getLength()-1; + this.cursor.column = this.doc.getLine(this.cursor.row).length; + } else if (row < 0) { + this.cursor.row = 0; + this.cursor.column = 0; + } else { + this.cursor.row = row; + this.cursor.column = Math.min(this.doc.getLine(this.cursor.row).length, Math.max(0, column)); + } + this.updateCursor(); + }, + hasSelection : function() { return !!this.selectionLead; }, @@ -444,7 +505,7 @@ function Editor(doc, renderer) this.setSelectionAnchor(lastRow, this.doc.getLine(lastRow).length); this._moveSelection(function() { - this.moveTo(0, 0); + this.moveCursorTo(0, 0); }); }, @@ -468,56 +529,38 @@ function Editor(doc, renderer) this.renderer.removeMarker(this.selection); } this.selection = this.renderer.addMarker(this.getSelectionRange(), "selection"); + this.renderer.scrollCursorIntoView(); }, selectUp : function() { - this._moveSelection(this.moveUp); + this._moveSelection(this.moveCursorUp); }, selectDown : function() { - this._moveSelection(this.moveDown); + this._moveSelection(this.moveCursorDown); }, selectRight : function() { - this._moveSelection(this.moveRight); + this._moveSelection(this.moveCursorRight); }, selectLeft : function() { - this._moveSelection(this.moveLeft); + this._moveSelection(this.moveCursorLeft); }, selectLineStart : function() { - this._moveSelection(this.moveLineStart); + this._moveSelection(this.moveCursorLineStart); }, selectLineEnd : function() { - this._moveSelection(this.moveLineEnd); + this._moveSelection(this.moveCursorLineEnd); }, selectCurrentLine : function() { this.setSelectionAnchor(this.cursor.row, 0); this._moveSelection(function() { - this.moveTo(this.cursor.row+1, 0); + this.moveCursorTo(this.cursor.row+1, 0); }); - }, - - moveBy : function(rows, chars) { - this.moveTo(this.cursor.row+rows, this.cursor.column+chars); - }, - - moveTo : function(row, column) - { - if (row >= this.doc.getLength()) { - this.cursor.row = this.doc.getLength()-1; - this.cursor.column = this.doc.getLine(this.cursor.row).length; - } else if (row < 0) { - this.cursor.row = 0; - this.cursor.column = 0; - } else { - this.cursor.row = row; - this.cursor.column = Math.min(this.doc.getLine(this.cursor.row).length, Math.max(0, column)); - } - this.updateCursor(); - } + } } \ No newline at end of file From 77f941515eeb9812f8ee69925f0630dd7314e3fa Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 8 Apr 2010 15:43:05 +0200 Subject: [PATCH 032/392] fix FF mouse wheel support --- Editor.js | 6 +++--- lib.js | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Editor.js b/Editor.js index 9e723f32..9de1afa5 100644 --- a/Editor.js +++ b/Editor.js @@ -108,7 +108,7 @@ function Editor(doc, renderer) addListener(container, "mousedown", bind(this.onMouseDown, this)); addListener(container, "dblclick", bind(this.onMouseDoubleClick, this)); - addListener(container, "mousewheel", bind(this.onMouseWheel, this)); + addMouseWheelListener(container, bind(this.onMouseWheel, this)); addTripleClickListener(container, bind(this.selectCurrentLine, this)); this.doc = doc; @@ -231,8 +231,8 @@ function Editor(doc, renderer) onMouseWheel : function(e) { - var delta = e.wheelDeltaY; - this.renderer.scrollToY(this.renderer.getScrollTop() - (delta/10)); + var delta = e.wheel; + this.renderer.scrollToY(this.renderer.getScrollTop() - (delta * 15)); return preventDefault(e); }, diff --git a/lib.js b/lib.js index 744e89aa..1f31694a 100644 --- a/lib.js +++ b/lib.js @@ -130,6 +130,16 @@ capture = function(el, eventHandler, releaseCaptureHandler) document.addEventListener("mouseup", onMouseUp, true); } +function addMouseWheelListener(el, callback) +{ + var listener = function(e) { + e.wheel = (e.wheelDelta) ? e.wheelDelta / 120 : -(e.detail || 0) / 3; + callback(e); + } + addListener(el, "DOMMouseScroll", listener); + addListener(el, "mousewheel", listener); +}; + function autoRemoveListener(el, type, callback, timeout) { var listener = function(e) From 971b96c132ed54d10601d4c531a2478b80a66621 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 8 Apr 2010 16:06:45 +0200 Subject: [PATCH 033/392] namespace methods from lib --- Editor.js | 38 ++++++++++---------- TextInput.js | 22 ++++++------ experiments/triple_click.html | 2 +- lib.js | 66 +++++++++++++++++++---------------- 4 files changed, 67 insertions(+), 61 deletions(-) diff --git a/Editor.js b/Editor.js index 9de1afa5..3ffa7444 100644 --- a/Editor.js +++ b/Editor.js @@ -13,7 +13,7 @@ var keys = { function KeyBinding(element, host) { - addListener(element, "keydown", function(e) + lib.addListener(element, "keydown", function(e) { var key = e.keyCode; @@ -23,7 +23,7 @@ function KeyBinding(element, host) if (e.metaKey) { host.selectAll(); - return stopEvent(e); + return lib.stopEvent(e); } break; @@ -33,7 +33,7 @@ function KeyBinding(element, host) } else { host.navigateUp(); } - return stopEvent(e); + return lib.stopEvent(e); case keys.DOWN: if (e.shiftKey) { @@ -41,7 +41,7 @@ function KeyBinding(element, host) } else { host.navigateDown(); } - return stopEvent(e); + return lib.stopEvent(e); case keys.LEFT: if (e.metaKey && e.shiftKey) { @@ -53,7 +53,7 @@ function KeyBinding(element, host) } else { host.navigateLeft(); } - return stopEvent(e); + return lib.stopEvent(e); case keys.RIGHT: if (e.metaKey && e.shiftKey) { @@ -65,7 +65,7 @@ function KeyBinding(element, host) } else { host.navigateRight(); } - return stopEvent(e); + return lib.stopEvent(e); case keys.POS1: if (e.shiftKey) { @@ -73,7 +73,7 @@ function KeyBinding(element, host) } else { host.navigateLineStart(); } - return stopEvent(e); + return lib.stopEvent(e); case keys.END: if (e.shiftKey) { @@ -81,19 +81,19 @@ function KeyBinding(element, host) } else { host.navigateLineEnd(); } - return stopEvent(e); + return lib.stopEvent(e); case keys.DELETE: host.removeRight(); - return stopEvent(e); + return lib.stopEvent(e); case keys.BACKSPACE: host.removeLeft(); - return stopEvent(e); + return lib.stopEvent(e); case keys.TAB: host.onTextInput(" "); - return stopEvent(e); + return lib.stopEvent(e); } }); }; @@ -106,13 +106,13 @@ function Editor(doc, renderer) this.textInput = new TextInput(container, this); new KeyBinding(container, this); - addListener(container, "mousedown", bind(this.onMouseDown, this)); - addListener(container, "dblclick", bind(this.onMouseDoubleClick, this)); - addMouseWheelListener(container, bind(this.onMouseWheel, this)); - addTripleClickListener(container, bind(this.selectCurrentLine, this)); + lib.addListener(container, "mousedown", lib.bind(this.onMouseDown, this)); + lib.addListener(container, "dblclick", lib.bind(this.onMouseDoubleClick, this)); + lib.addMouseWheelListener(container, lib.bind(this.onMouseWheel, this)); + lib.addTripleClickListener(container, lib.bind(this.selectCurrentLine, this)); this.doc = doc; - doc.addChangeListener(bind(this.onDocumentChange, this)); + doc.addChangeListener(lib.bind(this.onDocumentChange, this)); renderer.setDocument(doc); this.cursor = { @@ -184,10 +184,10 @@ function Editor(doc, renderer) _self.renderer.scrollCursorIntoView(); }; - capture(this.container, onMouseSelection, onMouseSelectionEnd); + lib.capture(this.container, onMouseSelection, onMouseSelectionEnd); var timerId = setInterval(onSelectionInterval, 20 ); - return preventDefault(e); + return lib.preventDefault(e); }, onMouseDoubleClick : function(e) @@ -233,7 +233,7 @@ function Editor(doc, renderer) { var delta = e.wheel; this.renderer.scrollToY(this.renderer.getScrollTop() - (delta * 15)); - return preventDefault(e); + return lib.preventDefault(e); }, getCopyText : function() diff --git a/TextInput.js b/TextInput.js index c4ffb03b..73893536 100644 --- a/TextInput.js +++ b/TextInput.js @@ -51,23 +51,23 @@ function TextInput(parentNode, host) { text.select(); } - addListener(text, "keypress", onTextInput, false); - addListener(text, "textInput", onTextInput, false); - addListener(text, "paste", onTextInput, false); - addListener(text, "propertychange", onTextInput, false); + lib.addListener(text, "keypress", onTextInput, false); + lib.addListener(text, "textInput", onTextInput, false); + lib.addListener(text, "paste", onTextInput, false); + lib.addListener(text, "propertychange", onTextInput, false); - addListener(text, "copy", onCopy, false); - addListener(text, "cut", onCut, false); + lib.addListener(text, "copy", onCopy, false); + lib.addListener(text, "cut", onCut, false); - addListener(text, "compositionstart", onCompositionStart, false); - addListener(text, "compositionupdate", onCompositionUpdate, false); - addListener(text, "compositionend", onCompositionEnd, false); + lib.addListener(text, "compositionstart", onCompositionStart, false); + lib.addListener(text, "compositionupdate", onCompositionUpdate, false); + lib.addListener(text, "compositionend", onCompositionEnd, false); - addListener(text, "blur", function() { + lib.addListener(text, "blur", function() { host.onBlur(); }, false); - addListener(text, "focus", function() { + lib.addListener(text, "focus", function() { host.onFocus(); }, false); diff --git a/experiments/triple_click.html b/experiments/triple_click.html index bdc3caa7..3fa905b9 100644 --- a/experiments/triple_click.html +++ b/experiments/triple_click.html @@ -18,7 +18,7 @@ - + + diff --git a/experiments/tokenizer.html b/experiments/tokenizer.html new file mode 100644 index 00000000..adcac8f2 --- /dev/null +++ b/experiments/tokenizer.html @@ -0,0 +1,39 @@ + + + + + + BackgroundTokenizer + + + + + + + + + + + + From 2e70bf46fa837105654fcd12a293671142826411 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 8 Apr 2010 18:47:00 +0200 Subject: [PATCH 035/392] html5 worker test --- experiments/worker.html | 33 +++++++++++++++++++++++++++++++++ experiments/worker.js | 3 +++ 2 files changed, 36 insertions(+) create mode 100644 experiments/worker.html create mode 100644 experiments/worker.js diff --git a/experiments/worker.html b/experiments/worker.html new file mode 100644 index 00000000..f3c75bde --- /dev/null +++ b/experiments/worker.html @@ -0,0 +1,33 @@ + + + + + + worker + + + + + + + + + + diff --git a/experiments/worker.js b/experiments/worker.js new file mode 100644 index 00000000..312a6f98 --- /dev/null +++ b/experiments/worker.js @@ -0,0 +1,3 @@ +onmessage = function(e) { + onmessage = new Function("e", e.data); +} \ No newline at end of file From fac85b2b2766d1dc0f1844fdb5d84f708047e9a5 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 9 Apr 2010 11:51:44 +0200 Subject: [PATCH 036/392] Multi line tokenizer --- BackgroundTokenizer.js | 135 +++++++++++++++++++++++++++++++++---- Editor.js | 1 - editor.css | 4 ++ experiments/tokenizer.html | 1 + 4 files changed, 126 insertions(+), 15 deletions(-) diff --git a/BackgroundTokenizer.js b/BackgroundTokenizer.js index 91d89aec..8b3d73b3 100644 --- a/BackgroundTokenizer.js +++ b/BackgroundTokenizer.js @@ -71,7 +71,11 @@ BackgroundTokenizer.prototype.getTokens = function(row) if (this.lines[row]) { return this.lines[row].tokens; } else { - return getLineTokens(this.textLines[row] || "", "start").tokens; + var state = "start"; + if (row > 0 && this.lines[row-1]) { + state = this.lines[row-1].state; + } + return getLineTokens(this.textLines[row] || "", state).tokens; } }; @@ -101,14 +105,98 @@ var keywords = { "with" : 1 }; -getLineTokens = function(line, state) +getLineTokens = function(line, startState) { - var tokens = []; + var rules = { + start : + [ + { + token: "comment", + regex: "\\/\\/.*$" + }, + { + token: "comment", // multi line comment in one line + regex: "\\/\\*.*?\\*\\/" + }, + { + token: "comment", // multi line comment in several lines + regex: "\\/\\*.*$", + next: "comment" + }, + { + token: "string", // single line + regex: '["][^"]*["]' + }, + { + token: "string", // single line + regex: "['][^']*[']" + }, + { + token: "number", // hex + regex: "0[xX][0-9a-fA-F]+\\b" + }, + { + token: "number", // float + regex: "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b" + }, + { + token: function(value) + { + if (keywords[value]) { + return "keyword"; + } else { + return "identifier" + } + }, + regex: "[a-zA-Z_][a-zA-Z0-9_]*\\b" + }, + { + token: function(value) { + //return parens[value]; + return "text"; + }, + regex: "[\\[\\]\\(\\)\\{\\}]" + }, + { + token: "text", + regex: "\\s+" + } + ], + "comment": + [ + { + token: "comment", // closing comment + regex: ".*?\\*\\/", + next: "start" + }, + { + token: "comment", // comment spanning whole line + regex: ".+" + } + ] + }; - var re = /(?:(\s+)|("[^"]*")|('[^']*')|([\[\]\(\)\{\}])|([a-zA-Z_][a-zA-Z0-9_]*)|(\/\/.*)|(.))/g - re.lastIndex = 0; + var regExps = {}; + for (var key in rules) + { + var state = rules[key]; + var ruleRegExps = []; + + for (var i=0; i < state.length; i++) { + ruleRegExps.push(state[i].regex); + }; + + regExps[key] = new RegExp("(?:(" + ruleRegExps.join(")|(") + ")|(.))", "g"); + } - var match; + + var currentState = startState; + var state = rules[currentState]; + var re = regExps[currentState]; + re.lastIndex = 0; + + var match, tokens = []; + while (match = re.exec(line)) { var token = { @@ -116,19 +204,38 @@ getLineTokens = function(line, state) value: match[0] } - if (match[2] || match[3]) { - token.type = "string"; - } else if (match[5] && keywords[match[5]]) { - token.type = "keyword"; - } else if (match[6]) { - token.type = "comment"; - } + //console.log(match); + + for (var i=0; i < state.length; i++) + { + if (match[i+1]) + { + if (typeof state[i].token == "function") { + token.type = state[i].token(match[0]); + } else { + token.type = state[i].token; + } + + if (state[i].next && state[i].next !== currentState) + { + currentState = state[i].next; + var state = rules[currentState]; + var lastIndex = re.lastIndex; + + var re = regExps[currentState]; + re.lastIndex = lastIndex; + } + break; + } + }; tokens.push(token); }; + //console.log(tokens, currentState) + return { tokens: tokens, - state: "start" + state: currentState } }; \ No newline at end of file diff --git a/Editor.js b/Editor.js index 32e97451..fe8f7ff9 100644 --- a/Editor.js +++ b/Editor.js @@ -158,7 +158,6 @@ function Editor(doc, renderer) }, onTokenizerUpdate : function(startRow, endRow) { - console.log("token update", startRow, endRow); this.renderer.updateLines(startRow, endRow); }, diff --git a/editor.css b/editor.css index f101ee35..0b6f789f 100644 --- a/editor.css +++ b/editor.css @@ -81,6 +81,10 @@ color: rgb(0, 102, 255); } +.line .number { + color: rgb(0, 0, 205); +} + .marker-layer .selection { position: absolute; background: rgba(77, 151, 255, 0.33); diff --git a/experiments/tokenizer.html b/experiments/tokenizer.html index adcac8f2..c2f40f49 100644 --- a/experiments/tokenizer.html +++ b/experiments/tokenizer.html @@ -22,6 +22,7 @@ button.onclick = function() { var onComplete = function() { console.log("complete"); + console.log(tokenizer.lines); }; var onUpdate = function(firstLine, lastLine) { From 24dd04d1babe99e9141ff7e8b075b8d496979379 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 9 Apr 2010 12:32:34 +0200 Subject: [PATCH 037/392] support multi line strings --- BackgroundTokenizer.js | 54 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/BackgroundTokenizer.js b/BackgroundTokenizer.js index 8b3d73b3..ead891c4 100644 --- a/BackgroundTokenizer.js +++ b/BackgroundTokenizer.js @@ -107,6 +107,9 @@ var keywords = { getLineTokens = function(line, startState) { + // regexp must not have capturing parentheses + // regexps are ordered -> the first match is used + var rules = { start : [ @@ -117,20 +120,30 @@ getLineTokens = function(line, startState) { token: "comment", // multi line comment in one line regex: "\\/\\*.*?\\*\\/" - }, + }, { - token: "comment", // multi line comment in several lines + token: "comment", // multi line comment start regex: "\\/\\*.*$", next: "comment" }, { token: "string", // single line - regex: '["][^"]*["]' + regex: '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' }, + { + token: "string", // multi line string start + regex: '["].*\\\\$', + next: "qqstring" + }, { token: "string", // single line - regex: "['][^']*[']" + regex: "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']" }, + { + token: "string", // multi line string start + regex: "['].*\\\\$", + next: "qstring" + }, { token: "number", // hex regex: "0[xX][0-9a-fA-F]+\\b" @@ -173,7 +186,31 @@ getLineTokens = function(line, startState) token: "comment", // comment spanning whole line regex: ".+" } - ] + ], + "qqstring": + [ + { + token: "string", + regex: '(?:(?:\\\\.)|(?:[^"\\\\]))*?"', + next: "start" + }, + { + token: "string", + regex: '.+' + } + ], + "qstring": + [ + { + token: "string", + regex: "(?:(?:\\\\.)|(?:[^'\\\\]))*?'", + next: "start" + }, + { + token: "string", + regex: '.+' + } + ] }; var regExps = {}; @@ -197,6 +234,8 @@ getLineTokens = function(line, startState) var match, tokens = []; + var lastIndex = 0; + while (match = re.exec(line)) { var token = { @@ -204,6 +243,11 @@ getLineTokens = function(line, startState) value: match[0] } + if (re.lastIndex == lastIndex) { + throw new Error("tokenizer error") + } + lastIndex = re.lastIndex; + //console.log(match); for (var i=0; i < state.length; i++) From 07dc92addbea5691bb91dd2c5d39180c794835f0 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 9 Apr 2010 14:10:31 +0200 Subject: [PATCH 038/392] externalize tokenizer and add XML support --- BackgroundTokenizer.js | 214 +---------------------------------------- Editor.js | 6 +- JavaScript.js | 137 ++++++++++++++++++++++++++ Tokenizer.js | 77 +++++++++++++++ XML.js | 78 +++++++++++++++ editor.css | 4 + editor.html | 5 +- 7 files changed, 310 insertions(+), 211 deletions(-) create mode 100644 JavaScript.js create mode 100644 Tokenizer.js create mode 100644 XML.js diff --git a/BackgroundTokenizer.js b/BackgroundTokenizer.js index ead891c4..fe190a30 100644 --- a/BackgroundTokenizer.js +++ b/BackgroundTokenizer.js @@ -1,9 +1,10 @@ -function BackgroundTokenizer(onUpdate, onComplete) +function BackgroundTokenizer(tokenizer, onUpdate, onComplete) { this.running = false; this.textLines = []; this.lines = []; this.currentLine = 0; + this.tokenizer = tokenizer; this.onUpdate = onUpdate || function(firstLine, lastLine) {}; this.onComplete = onComplete || function() {}; @@ -24,7 +25,7 @@ function BackgroundTokenizer(onUpdate, onComplete) var line = textLines[self.currentLine]; var state = self.currentLine == 0 ? "start" : self.lines[self.currentLine-1].state; - self.lines[self.currentLine] = getLineTokens(line, state); + self.lines[self.currentLine] = self.tokenizer.getLineTokens(line, state); if ((new Date()-workerStart) > 80) { @@ -58,7 +59,7 @@ BackgroundTokenizer.prototype.start = function(startRow) if (!this.running) { this.running = true; - setTimeout(this._worker); + setTimeout(this._worker, 50); } }; @@ -75,211 +76,6 @@ BackgroundTokenizer.prototype.getTokens = function(row) if (row > 0 && this.lines[row-1]) { state = this.lines[row-1].state; } - return getLineTokens(this.textLines[row] || "", state).tokens; - } -}; - -var keywords = { - "break" : 1, - "case" : 1, - "catch" : 1, - "continue" : 1, - "default" : 1, - "delete" : 1, - "do" : 1, - "else" : 1, - "finally" : 1, - "for" : 1, - "function" : 1, - "if" : 1, - "in" : 1, - "instanceof" : 1, - "new" : 1, - "return" : 1, - "switch" : 1, - "throw" : 1, - "try" : 1, - "typeof" : 1, - "var" : 1, - "while" : 1, - "with" : 1 -}; - -getLineTokens = function(line, startState) -{ - // regexp must not have capturing parentheses - // regexps are ordered -> the first match is used - - var rules = { - start : - [ - { - token: "comment", - regex: "\\/\\/.*$" - }, - { - token: "comment", // multi line comment in one line - regex: "\\/\\*.*?\\*\\/" - }, - { - token: "comment", // multi line comment start - regex: "\\/\\*.*$", - next: "comment" - }, - { - token: "string", // single line - regex: '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' - }, - { - token: "string", // multi line string start - regex: '["].*\\\\$', - next: "qqstring" - }, - { - token: "string", // single line - regex: "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']" - }, - { - token: "string", // multi line string start - regex: "['].*\\\\$", - next: "qstring" - }, - { - token: "number", // hex - regex: "0[xX][0-9a-fA-F]+\\b" - }, - { - token: "number", // float - regex: "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b" - }, - { - token: function(value) - { - if (keywords[value]) { - return "keyword"; - } else { - return "identifier" - } - }, - regex: "[a-zA-Z_][a-zA-Z0-9_]*\\b" - }, - { - token: function(value) { - //return parens[value]; - return "text"; - }, - regex: "[\\[\\]\\(\\)\\{\\}]" - }, - { - token: "text", - regex: "\\s+" - } - ], - "comment": - [ - { - token: "comment", // closing comment - regex: ".*?\\*\\/", - next: "start" - }, - { - token: "comment", // comment spanning whole line - regex: ".+" - } - ], - "qqstring": - [ - { - token: "string", - regex: '(?:(?:\\\\.)|(?:[^"\\\\]))*?"', - next: "start" - }, - { - token: "string", - regex: '.+' - } - ], - "qstring": - [ - { - token: "string", - regex: "(?:(?:\\\\.)|(?:[^'\\\\]))*?'", - next: "start" - }, - { - token: "string", - regex: '.+' - } - ] - }; - - var regExps = {}; - for (var key in rules) - { - var state = rules[key]; - var ruleRegExps = []; - - for (var i=0; i < state.length; i++) { - ruleRegExps.push(state[i].regex); - }; - - regExps[key] = new RegExp("(?:(" + ruleRegExps.join(")|(") + ")|(.))", "g"); - } - - - var currentState = startState; - var state = rules[currentState]; - var re = regExps[currentState]; - re.lastIndex = 0; - - var match, tokens = []; - - var lastIndex = 0; - - while (match = re.exec(line)) - { - var token = { - type: "text", - value: match[0] - } - - if (re.lastIndex == lastIndex) { - throw new Error("tokenizer error") - } - lastIndex = re.lastIndex; - - //console.log(match); - - for (var i=0; i < state.length; i++) - { - if (match[i+1]) - { - if (typeof state[i].token == "function") { - token.type = state[i].token(match[0]); - } else { - token.type = state[i].token; - } - - if (state[i].next && state[i].next !== currentState) - { - currentState = state[i].next; - var state = rules[currentState]; - var lastIndex = re.lastIndex; - - var re = regExps[currentState]; - re.lastIndex = lastIndex; - } - break; - } - }; - - tokens.push(token); - }; - - //console.log(tokens, currentState) - - return { - tokens: tokens, - state: currentState + return this.tokenizer.getLineTokens(this.textLines[row] || "", state).tokens; } }; \ No newline at end of file diff --git a/Editor.js b/Editor.js index fe8f7ff9..a77136bd 100644 --- a/Editor.js +++ b/Editor.js @@ -115,7 +115,11 @@ function Editor(doc, renderer) doc.addChangeListener(lib.bind(this.onDocumentChange, this)); renderer.setDocument(doc); - this.tokenizer = new BackgroundTokenizer(lib.bind(this.onTokenizerUpdate, this)); + this.tokenizer = new BackgroundTokenizer( + new Tokenizer(XML.RULES), + lib.bind(this.onTokenizerUpdate, this) + ); + this.tokenizer.setLines(doc.lines); renderer.setTokenizer(this.tokenizer); diff --git a/JavaScript.js b/JavaScript.js new file mode 100644 index 00000000..4816b7b1 --- /dev/null +++ b/JavaScript.js @@ -0,0 +1,137 @@ +(function() { + +window.JavaScript = {}; + +var keywords = { + "break" : 1, + "case" : 1, + "catch" : 1, + "continue" : 1, + "default" : 1, + "delete" : 1, + "do" : 1, + "else" : 1, + "finally" : 1, + "for" : 1, + "function" : 1, + "if" : 1, + "in" : 1, + "instanceof" : 1, + "new" : 1, + "return" : 1, + "switch" : 1, + "throw" : 1, + "try" : 1, + "typeof" : 1, + "var" : 1, + "while" : 1, + "with" : 1 +}; + +// regexp must not have capturing parentheses +// regexps are ordered -> the first match is used + +JavaScript.RULES = { + start : + [ + { + token: "comment", + regex: "\\/\\/.*$" + }, + { + token: "comment", // multi line comment in one line + regex: "\\/\\*.*?\\*\\/" + }, + { + token: "comment", // multi line comment start + regex: "\\/\\*.*$", + next: "comment" + }, + { + token: "string", // single line + regex: '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' + }, + { + token: "string", // multi line string start + regex: '["].*\\\\$', + next: "qqstring" + }, + { + token: "string", // single line + regex: "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']" + }, + { + token: "string", // multi line string start + regex: "['].*\\\\$", + next: "qstring" + }, + { + token: "number", // hex + regex: "0[xX][0-9a-fA-F]+\\b" + }, + { + token: "number", // float + regex: "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b" + }, + { + token: function(value) + { + if (keywords[value]) { + return "keyword"; + } else { + return "identifier" + } + }, + regex: "[a-zA-Z_][a-zA-Z0-9_]*\\b" + }, + { + token: function(value) { + //return parens[value]; + return "text"; + }, + regex: "[\\[\\]\\(\\)\\{\\}]" + }, + { + token: "text", + regex: "\\s+" + } + ], + "comment": + [ + { + token: "comment", // closing comment + regex: ".*?\\*\\/", + next: "start" + }, + { + token: "comment", // comment spanning whole line + regex: ".+" + } + ], + "qqstring": + [ + { + token: "string", + regex: '(?:(?:\\\\.)|(?:[^"\\\\]))*?"', + next: "start" + }, + { + token: "string", + regex: '.+' + } + ], + "qstring": + [ + { + token: "string", + regex: "(?:(?:\\\\.)|(?:[^'\\\\]))*?'", + next: "start" + }, + { + token: "string", + regex: '.+' + } + ] +}; + +})(); \ No newline at end of file diff --git a/Tokenizer.js b/Tokenizer.js new file mode 100644 index 00000000..f6cfe194 --- /dev/null +++ b/Tokenizer.js @@ -0,0 +1,77 @@ + +function Tokenizer(rules) +{ + this.rules = rules; + + this.regExps = {}; + for (var key in this.rules) + { + var state = this.rules[key]; + var ruleRegExps = []; + + for (var i=0; i < state.length; i++) { + ruleRegExps.push(state[i].regex); + }; + + this.regExps[key] = new RegExp("(?:(" + ruleRegExps.join(")|(") + ")|(.))", "g"); + } +}; + +Tokenizer.prototype.getLineTokens = function(line, startState) +{ + var currentState = startState; + var state = this.rules[currentState]; + var re = this.regExps[currentState]; + re.lastIndex = 0; + + var match, tokens = []; + + var lastIndex = 0; + + while (match = re.exec(line)) + { + var token = { + type: "text", + value: match[0] + } + + if (re.lastIndex == lastIndex) { + throw new Error("tokenizer error") + } + lastIndex = re.lastIndex; + + //console.log(match); + + for (var i=0; i < state.length; i++) + { + if (match[i+1]) + { + if (typeof state[i].token == "function") { + token.type = state[i].token(match[0]); + } else { + token.type = state[i].token; + } + + if (state[i].next && state[i].next !== currentState) + { + currentState = state[i].next; + var state = this.rules[currentState]; + var lastIndex = re.lastIndex; + + var re = this.regExps[currentState]; + re.lastIndex = lastIndex; + } + break; + } + }; + + tokens.push(token); + }; + + //console.log(tokens, currentState) + + return { + tokens: tokens, + state: currentState + } +}; \ No newline at end of file diff --git a/XML.js b/XML.js new file mode 100644 index 00000000..cd6b2b02 --- /dev/null +++ b/XML.js @@ -0,0 +1,78 @@ +(function() { + +window.XML = {}; + +// regexp must not have capturing parentheses +// regexps are ordered -> the first match is used + +XML.RULES = { + start : + [ + { + token: "text", + regex: "<\\!\\[CDATA\\[", + next: "cdata" + }, + { + token: "xml_pe", + regex: "<\\?.*?\\?>" + }, + { + token: "text", // opening tag + regex: "<", + next: "tag" + }, + { + token: "text", + regex: "\\s+" + }, + { + token: "text", + regex: ".+" + } + ], + + tag: + [ + { + token: "text", + regex: ">", + next: "start" + }, + { + token: "keyword", + regex: "[-_a-zA-Z0-9:]+" + }, + { + token: "text", + regex: "\\s+" + }, + { + token: "string", + regex: '".*?"' + }, + { + token: "string", + regex: "'.*?'" + } + ], + + cdata: + [ + { + token: "text", + regex: "\\]\\]>", + next: "start" + }, + { + token: "string", + regex: "\\s+" + }, + { + token: "text", + regex: ".+" + } + ] +}; + +})(); \ No newline at end of file diff --git a/editor.css b/editor.css index 0b6f789f..2cf1701a 100644 --- a/editor.css +++ b/editor.css @@ -85,6 +85,10 @@ color: rgb(0, 0, 205); } +.line .xml_pe { + color: rgb(104, 104, 91); +} + .marker-layer .selection { position: absolute; background: rgba(77, 151, 255, 0.33); diff --git a/editor.html b/editor.html index 87c93655..dcd024fd 100644 --- a/editor.html +++ b/editor.html @@ -21,7 +21,10 @@ - + + + + From a1dd52ebc87b350bca258deb3f457145df13d61a Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 9 Apr 2010 14:33:25 +0200 Subject: [PATCH 039/392] support XML comments --- XML.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/XML.js b/XML.js index cd6b2b02..1db6c3d8 100644 --- a/XML.js +++ b/XML.js @@ -17,6 +17,11 @@ XML.RULES = { token: "xml_pe", regex: "<\\?.*?\\?>" }, + { + token: "comment", + regex: "<\\!--", + next: "comment" + }, { token: "text", // opening tag regex: "<", @@ -65,13 +70,26 @@ XML.RULES = { next: "start" }, { - token: "string", + token: "text", regex: "\\s+" }, { token: "text", regex: ".+" } + ], + + comment: + [ + { + token: "comment", + regex: ".*?-->", + next: "start" + }, + { + token: "comment", + regex: ".+" + } ] }; From 03d5b5698993c1b61fc295f093d1988314b3b692 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 9 Apr 2010 18:16:46 +0200 Subject: [PATCH 040/392] support page down/up navigation --- Editor.js | 30 ++++++++++++++++++++++++++++++ VirtualRenderer.js | 12 ++++++++++++ 2 files changed, 42 insertions(+) diff --git a/Editor.js b/Editor.js index a77136bd..c1415702 100644 --- a/Editor.js +++ b/Editor.js @@ -3,6 +3,8 @@ var keys = { RIGHT: 39, DOWN: 40, LEFT: 37, + PAGEUP: 33, + PAGEDOWN: 34, POS1: 36, END: 35, DELETE: 46, @@ -67,6 +69,14 @@ function KeyBinding(element, host) } return lib.stopEvent(e); + case keys.PAGEDOWN: + host.scrollPageDown(); + return lib.stopEvent(e); + + case keys.PAGEUP: + host.scrollPageUp(); + return lib.stopEvent(e); + case keys.POS1: if (e.shiftKey) { host.selectLineStart(); @@ -350,6 +360,26 @@ function Editor(doc, renderer) this.removeLeft(); }, + getPageDownRow : function() { + return this.renderer.getLastVisibleRow() - 1; + }, + + getPageUpRow : function() + { + var firstRow = this.renderer.getFirstVisibleRow(); + var lastRow = this.renderer.getLastVisibleRow(); + + return firstRow - (lastRow-firstRow) + 1; + }, + + scrollPageDown : function() { + this.renderer.scrollToRow(this.getPageDownRow()); + }, + + scrollPageUp : function() { + this.renderer.scrollToRow(this.getPageUpRow()); + }, + navigateUp : function() { this.clearSelection(); diff --git a/VirtualRenderer.js b/VirtualRenderer.js index 385fcecd..fdc6967f 100644 --- a/VirtualRenderer.js +++ b/VirtualRenderer.js @@ -46,6 +46,14 @@ VirtualRenderer.prototype.getContainerElement = function() { return this.container; }; +VirtualRenderer.prototype.getFirstVisibleRow = function() { + return this.layerConfig.firstRow || 0; +}; + +VirtualRenderer.prototype.getLastVisibleRow = function() { + return this.layerConfig.lastRow || 0; +}; + VirtualRenderer.prototype.updateLines = function(firstRow, lastRow) { var layerConfig = this.layerConfig; @@ -157,6 +165,10 @@ VirtualRenderer.prototype.getScrollTop = function() { return this.scrollTop; }; +VirtualRenderer.prototype.scrollToRow = function(row) { + this.scrollToY(row*this.lineHeight); +} + VirtualRenderer.prototype.scrollToY = function(scrollTop) { var maxHeight = this.lines.length * this.lineHeight - this.scroller.clientHeight; From 119758358203fe04e33bbf540853a5d9c1d68c71 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 9 Apr 2010 18:17:03 +0200 Subject: [PATCH 041/392] fix viewport calculation --- GutterLayer.js | 2 +- VirtualRenderer.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GutterLayer.js b/GutterLayer.js index cddac987..c283bfa8 100644 --- a/GutterLayer.js +++ b/GutterLayer.js @@ -8,7 +8,7 @@ function GutterLayer(parentEl) GutterLayer.prototype.update = function(config) { var html = []; - for (var i=config.firstRow; i", diff --git a/VirtualRenderer.js b/VirtualRenderer.js index fdc6967f..0ff63a21 100644 --- a/VirtualRenderer.js +++ b/VirtualRenderer.js @@ -88,7 +88,7 @@ VirtualRenderer.prototype.draw = function() var lineCount = Math.ceil(minHeight / this.lineHeight); var firstRow = Math.round((this.scrollTop - offset) / this.lineHeight); - var lastRow = Math.min(lines.length, firstRow+lineCount); + var lastRow = Math.min(lines.length, firstRow+lineCount)-1; var layerConfig = this.layerConfig = { width: longestLine, @@ -97,7 +97,7 @@ VirtualRenderer.prototype.draw = function() lineHeight: this.lineHeight, characterWidth: this.characterWidth }; - + for (var i=0; i < this.layers.length; i++) { var layer = this.layers[i]; From 28887f1e6be1f906edac9cf5ae9cdc951adb1b69 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 12 Apr 2010 08:32:57 +0200 Subject: [PATCH 042/392] tweak background tokenizer settings --- BackgroundTokenizer.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/BackgroundTokenizer.js b/BackgroundTokenizer.js index fe190a30..079e93ab 100644 --- a/BackgroundTokenizer.js +++ b/BackgroundTokenizer.js @@ -20,6 +20,8 @@ function BackgroundTokenizer(tokenizer, onUpdate, onComplete) var startLine = self.currentLine; var textLines = self.textLines; + var processedLines = 0; + while (self.currentLine < textLines.length) { var line = textLines[self.currentLine]; @@ -27,10 +29,12 @@ function BackgroundTokenizer(tokenizer, onUpdate, onComplete) var state = self.currentLine == 0 ? "start" : self.lines[self.currentLine-1].state; self.lines[self.currentLine] = self.tokenizer.getLineTokens(line, state); - if ((new Date()-workerStart) > 80) + // only check every 30 lines + processedLines += 1; + if ((processedLines % 30 == 0) && (new Date()-workerStart) > 20) { self.onUpdate(startLine, self.currentLine); - return setTimeout(self._worker, 20); + return setTimeout(self._worker, 10); } self.currentLine++; From 40fa64995d29f07a8fd34d03eadba447b7ac2eb9 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 12 Apr 2010 08:46:06 +0200 Subject: [PATCH 043/392] add page up/down selection support --- Editor.js | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/Editor.js b/Editor.js index c1415702..95df4afc 100644 --- a/Editor.js +++ b/Editor.js @@ -70,11 +70,19 @@ function KeyBinding(element, host) return lib.stopEvent(e); case keys.PAGEDOWN: - host.scrollPageDown(); + if (e.shiftKey) { + host.selectPageDown(); + } else { + host.scrollPageDown(); + } return lib.stopEvent(e); case keys.PAGEUP: - host.scrollPageUp(); + if (e.shiftKey) { + host.selectPageUp(); + } else { + host.scrollPageUp(); + } return lib.stopEvent(e); case keys.POS1: @@ -360,6 +368,14 @@ function Editor(doc, renderer) this.removeLeft(); }, + getFirstVisibleRow : function() { + return this.renderer.getFirstVisibleRow(); + }, + + getLastVisibleRow : function() { + return this.renderer.getLastVisibleRow(); + }, + getPageDownRow : function() { return this.renderer.getLastVisibleRow() - 1; }, @@ -600,6 +616,30 @@ function Editor(doc, renderer) this._moveSelection(this.moveCursorLineEnd); }, + selectPageDown : function() + { + var visibleRows = this.getLastVisibleRow() - this.getFirstVisibleRow(); + var row = this.getPageDownRow() + Math.round(visibleRows / 2); + + this.scrollPageDown(); + + this._moveSelection(function() { + this.moveCursorTo(row, this.cursor.column); + }); + }, + + selectPageUp : function() + { + var visibleRows = this.getLastVisibleRow() - this.getFirstVisibleRow(); + var row = this.getPageUpRow() + Math.round(visibleRows / 2); + + this.scrollPageUp(); + + this._moveSelection(function() { + this.moveCursorTo(row, this.cursor.column); + }); + }, + selectCurrentLine : function() { this.setSelectionAnchor(this.cursor.row, 0); From 72b556efaac1364776121393a1fee5e9eee49694 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 12 Apr 2010 09:23:46 +0200 Subject: [PATCH 044/392] reorganize file structure and use labjs for loading --- .gitignore | 1 + editor.css => css/editor.css | 0 demo/LAB.js | 2 + demo/LAB.src.js | 358 ++++++++++++++++++ demo/editor.html | 52 +++ demo/fLAB.js | 191 ++++++++++ Editor.mm => doc/Editor.mm | 23 +- editor.html | 52 --- experiments/tokenizer.html | 7 +- experiments/triple_click.html | 2 +- .../BackgroundTokenizer.js | 0 CursorLayer.js => src/CursorLayer.js | 0 Editor.js => src/Editor.js | 0 GutterLayer.js => src/GutterLayer.js | 0 JavaScript.js => src/JavaScript.js | 0 MarkerLayer.js => src/MarkerLayer.js | 0 TextDocument.js => src/TextDocument.js | 0 TextInput.js => src/TextInput.js | 0 TextLayer.js => src/TextLayer.js | 0 Tokenizer.js => src/Tokenizer.js | 0 VirtualRenderer.js => src/VirtualRenderer.js | 0 XML.js => src/XML.js | 0 lib.js => src/lib.js | 0 23 files changed, 632 insertions(+), 56 deletions(-) create mode 100644 .gitignore rename editor.css => css/editor.css (100%) create mode 100755 demo/LAB.js create mode 100755 demo/LAB.src.js create mode 100644 demo/editor.html create mode 100755 demo/fLAB.js rename Editor.mm => doc/Editor.mm (61%) delete mode 100644 editor.html rename BackgroundTokenizer.js => src/BackgroundTokenizer.js (100%) rename CursorLayer.js => src/CursorLayer.js (100%) rename Editor.js => src/Editor.js (100%) rename GutterLayer.js => src/GutterLayer.js (100%) rename JavaScript.js => src/JavaScript.js (100%) rename MarkerLayer.js => src/MarkerLayer.js (100%) rename TextDocument.js => src/TextDocument.js (100%) rename TextInput.js => src/TextInput.js (100%) rename TextLayer.js => src/TextLayer.js (100%) rename Tokenizer.js => src/Tokenizer.js (100%) rename VirtualRenderer.js => src/VirtualRenderer.js (100%) rename XML.js => src/XML.js (100%) rename lib.js => src/lib.js (100%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..496ee2ca --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/editor.css b/css/editor.css similarity index 100% rename from editor.css rename to css/editor.css diff --git a/demo/LAB.js b/demo/LAB.js new file mode 100755 index 00000000..db515343 --- /dev/null +++ b/demo/LAB.js @@ -0,0 +1,2 @@ +// LAB.js (LABjs :: Loading And Blocking JavaScript) | v1.0.2rc1 (c) Kyle Simpson | MIT License +(function(j){var p="string",w="head",H="body",Y="script",t="readyState",k="preloaddone",y="loadtrigger",I="srcuri",D="preload",Z="complete",z="done",A="which",J="preserve",E="onreadystatechange",ba="onload",K="hasOwnProperty",bb="script/cache",L="[object ",bv=L+"Function]",bw=L+"Array]",e=null,h=true,i=false,s=j.document,bx=j.location,bc=j.ActiveXObject,B=j.setTimeout,bd=j.clearTimeout,M=function(a){return s.getElementsByTagName(a)},N=Object.prototype.toString,O=function(){},q={},P={},be=/^[^?#]*\//.exec(bx.href)[0],bf=/^\w+\:\/\/\/?[^\/]+/.exec(be)[0],by=M(Y),bg=j.opera&&N.call(j.opera)==L+"Opera]",bh=(function(a){a[a]=a+"";return a[a]!=a+""})(new String("__count__")),u={cache:!(bh||bg),order:bh||bg,xhr:h,dupe:h,base:"",which:w};u[J]=i;u[D]=h;q[w]=M(w);q[H]=M(H);function Q(a){return N.call(a)===bv}function R(a,b){var c=/^\w+\:\/\//,d;if(typeof a!==p)a="";if(typeof b!==p)b="";d=(c.test(a)?"":b)+a;return((c.test(d)?"":(d.charAt(0)==="/"?bf:be))+d)}function bz(a){return(R(a).indexOf(bf)===0)}function bA(a){var b,c=-1;while(b=by[++c]){if(typeof b.src===p&&a===R(b.src)&&b.type!==bb)return h}return i}function F(v,l){v=!(!v);if(l==e)l=u;var bi=i,C=v&&l[D],bj=C&&l.cache,G=C&&l.order,bk=C&&l.xhr,bB=l[J],bC=l.which,bD=l.base,bl=O,S=i,x,r=h,m={},T=[],U=e;C=bj||bk||G;function bm(a,b){if((a[t]&&a[t]!==Z&&a[t]!=="loaded")||b[z]){return i}a[ba]=a[E]=e;return h}function V(a,b,c){c=!(!c);if(!c&&!(bm(a,b)))return;b[z]=h;for(var d in m){if(m[K](d)&&!(m[d][z]))return}bi=h;bl()}function bn(a){if(Q(a[y])){a[y]();a[y]=e}}function bE(a,b){if(!bm(a,b))return;b[k]=h;B(function(){q[b[A]].removeChild(a);bn(b)},0)}function bF(a,b){if(a[t]===4){a[E]=O;b[k]=h;B(function(){bn(b)},0)}}function W(b,c,d,g,f,n){var o=b[A];B(function(){if("item"in q[o]){if(!q[o][0]){B(arguments.callee,25);return}q[o]=q[o][0]}var a=s.createElement(Y);a.type=d;if(typeof g===p)a.charset=g;if(Q(f)){a[ba]=a[E]=function(){f(a,b)};a.src=c}q[o].insertBefore(a,(o===w?q[o].firstChild:e));if(typeof n===p){a.text=n;V(a,b,h)}},0)}function bo(a,b,c,d){P[a[I]]=h;W(a,b,c,d,V)}function bp(a,b,c,d){var g=arguments;if(r&&a[k]==e){a[k]=i;W(a,b,bb,d,bE)}else if(!r&&a[k]!=e&&!a[k]){a[y]=function(){bp.apply(e,g)}}else if(!r){bo.apply(e,g)}}function bq(a,b,c,d){var g=arguments,f;if(r&&a[k]==e){a[k]=i;f=a.xhr=(bc?new bc("Microsoft.XMLHTTP"):new j.XMLHttpRequest());f[E]=function(){bF(f,a)};f.open("GET",b);f.send("")}else if(!r&&a[k]!=e&&!a[k]){a[y]=function(){bq.apply(e,g)}}else if(!r){P[a[I]]=h;W(a,b,c,d,e,a.xhr.responseText);a.xhr=e}}function br(a){if(a.allowDup==e)a.allowDup=l.dupe;var b=a.src,c=a.type,d=a.charset,g=a.allowDup,f=R(b,bD),n,o=bz(f);if(typeof c!==p)c="text/javascript";if(typeof d!==p)d=e;g=!(!g);if(!g&&((P[f]!=e)||(r&&m[f])||bA(f))){if(m[f]!=e&&m[f][k]&&!m[f][z]&&o){V(e,m[f],h)}return}if(m[f]==e)m[f]={};n=m[f];if(n[A]==e)n[A]=bC;n[z]=i;n[I]=f;S=h;if(!G&&bk&&o)bq(n,f,c,d);else if(!G&&bj)bp(n,f,c,d);else bo(n,f,c,d)}function bs(a){T.push(a)}function X(a){if(v&&!G)bs(a);if(!v||C)a()}function bt(a){var b=[],c;for(c=-1;++c") type dom-ready hack is present in the page + if ("item" in append_to[_script_which]) { // check if ref is still a live node list + if (!append_to[_script_which][0]) { // append_to node not yet ready + fSETTIMEOUT(arguments.callee,25); // try again in a little bit -- note, will recall the anonymous functoin in the outer setTimeout, not the parent createScriptTag() + return; + } + append_to[_script_which] = append_to[_script_which][0]; // reassign from live node list ref to pure node ref -- avoids nasty IE bug where changes to DOM invalidate live node lists + } + var scriptElem = oDOC.createElement(sSCRIPT); + scriptElem.type = type; + if (typeof charset === sSTRING) scriptElem.charset = charset; + if (isFunc(onload)) { // load script via 'src' attribute, set onload/onreadystatechange listeners + scriptElem[sONLOAD] = scriptElem[sONREADYSTATECHANGE] = function(){onload(scriptElem,scriptentry);}; + scriptElem.src = src; + } + // only for appending to , fix a bug in IE6 if tag is present -- otherwise, insertBefore(...,null) acts just like appendChild() + append_to[_script_which].insertBefore(scriptElem,(_script_which===sHEAD?append_to[_script_which].firstChild:nNULL)); + if (typeof scriptText === sSTRING) { // script text already avaiable from XHR preload, so just inject it + scriptElem.text = scriptText; + handleScriptLoad(scriptElem,scriptentry,bTRUE); // manually call 'load' callback function, skipReadyCheck=true + } + },0); + } + function loadScriptElem(scriptentry,src,type,charset) { + all_scripts[scriptentry[sSRCURI]] = bTRUE; + createScriptTag(scriptentry,src,type,charset,handleScriptLoad); + } + function loadScriptCache(scriptentry,src,type,charset) { + var args = arguments; + if (first_pass && scriptentry[sPRELOADDONE] == nNULL) { // need to preload into cache + scriptentry[sPRELOADDONE] = bFALSE; + createScriptTag(scriptentry,src,sSCRIPTCACHE,charset,handleScriptPreload); // fake mimetype causes a fetch into cache, but no execution + } + else if (!first_pass && scriptentry[sPRELOADDONE] != nNULL && !scriptentry[sPRELOADDONE]) { // preload still in progress, make sure trigger is set for execution later + scriptentry[sLOADTRIGGER] = function(){loadScriptCache.apply(nNULL,args);}; + } + else if (!first_pass) { // preload done, so reload (from cache, hopefully!) as regular script element + loadScriptElem.apply(nNULL,args); + } + } + function loadScriptXHR(scriptentry,src,type,charset) { + var args = arguments, xhr; + if (first_pass && scriptentry[sPRELOADDONE] == nNULL) { // need to preload + scriptentry[sPRELOADDONE] = bFALSE; + xhr = scriptentry.xhr = (oACTIVEX ? new oACTIVEX("Microsoft.XMLHTTP") : new global.XMLHttpRequest()); + xhr[sONREADYSTATECHANGE] = function(){handleXHRPreload(xhr,scriptentry);}; + xhr.open("GET",src); + xhr.send(""); + } + else if (!first_pass && scriptentry[sPRELOADDONE] != nNULL && !scriptentry[sPRELOADDONE]) { // preload XHR still in progress, make sure trigger is set for execution later + scriptentry[sLOADTRIGGER] = function(){loadScriptXHR.apply(nNULL,args);}; + } + else if (!first_pass) { // preload done, so "execute" script via injection + all_scripts[scriptentry[sSRCURI]] = bTRUE; + createScriptTag(scriptentry,src,type,charset,nNULL,scriptentry.xhr.responseText); + scriptentry.xhr = nNULL; + } + } + function loadScript(o) { + if (o.allowDup == nNULL) o.allowDup = opts.dupe; + var src = o.src, type = o.type, charset = o.charset, allowDup = o.allowDup, + src_uri = canonicalScriptURI(src,_base_path), scriptentry, same_domain = sameDomain(src_uri); + if (typeof type !== sSTRING) type = "text/javascript"; + if (typeof charset !== sSTRING) charset = nNULL; + allowDup = !(!allowDup); + if (!allowDup && + ( + (all_scripts[src_uri] != nNULL) || (first_pass && scripts[src_uri]) || scriptTagExists(src_uri) + ) + ) { + if (scripts[src_uri] != nNULL && scripts[src_uri][sPRELOADDONE] && !scripts[src_uri][sDONE] && same_domain) { + // this script was preloaded via XHR, but is a duplicate, and dupes are not allowed + handleScriptLoad(nNULL,scripts[src_uri],bTRUE); // mark the entry as done and check if chain group is done + } + return; + } + if (scripts[src_uri] == nNULL) scripts[src_uri] = {}; + scriptentry = scripts[src_uri]; + if (scriptentry[sWHICH] == nNULL) scriptentry[sWHICH] = _which; + scriptentry[sDONE] = bFALSE; + scriptentry[sSRCURI] = src_uri; + scripts_loading = bTRUE; + + if (!_use_script_order && _use_xhr_preload && same_domain) loadScriptXHR(scriptentry,src_uri,type,charset); + else if (!_use_script_order && _use_cache_preload) loadScriptCache(scriptentry,src_uri,type,charset); + else loadScriptElem(scriptentry,src_uri,type,charset); + } + function onlyQueue(execBody) { + exec.push(execBody); + } + function queueAndExecute(execBody) { // helper for publicAPI functions below + if (queueExec && !_use_script_order) onlyQueue(execBody); + if (!queueExec || _use_preload) execBody(); // if engine is either not queueing, or is queuing in preload mode, go ahead and execute + } + function serializeArgs(args) { + var sargs = [], idx; + for (idx=-1; ++idx 1.3.2 has been patched to take advantage of document.readyState, which is enabled by this hack. But 1.3.2 and before are **not** safe or + affected by this hack, and should therefore **not** be lazy-loaded by script loader tools such as LABjs. + */ + (function(addEvent,domLoaded,handler){ + if (oDOC[sREADYSTATE] == nNULL && oDOC[addEvent]){ + oDOC[sREADYSTATE] = "loading"; + oDOC[addEvent](domLoaded,handler = function(){ + oDOC.removeEventListener(domLoaded,handler,bFALSE); + oDOC[sREADYSTATE] = sCOMPLETE; + },bFALSE); + } + })("addEventListener","DOMContentLoaded"); + +})(window); \ No newline at end of file diff --git a/demo/editor.html b/demo/editor.html new file mode 100644 index 00000000..b7c3e4f5 --- /dev/null +++ b/demo/editor.html @@ -0,0 +1,52 @@ + + + + + + Editor + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + \ No newline at end of file diff --git a/demo/fLAB.js b/demo/fLAB.js new file mode 100755 index 00000000..fcabff0d --- /dev/null +++ b/demo/fLAB.js @@ -0,0 +1,191 @@ +// fLAB.js (file:// protocol adapter for LABjs 1.0+) +// v0.2 (c) Kyle Simpson +// MIT License + +(function(global){ + var orig_$LAB = global.$LAB, + oDOC = global.document, + oDOCLOC = oDOC.location, + local_filesystem = (oDOCLOC.protocol === "file:") + ; + if (!orig_$LAB || !local_filesystem) return; // only adapt LABjs with fLABjs wrapper if LABjs exists and we're currently in local filesystem + + var sUNDEF = "undefined", // constants used for compression optimization + sSTRING = "string", + sHEAD = "head", + sBODY = "body", + sFUNCTION = "function", + sSCRIPT = "script", + sSRCURI = "srcuri", + sDONE = "done", + sWHICH = "which", + bTRUE = true, + bFALSE = false, + fSETTIMEOUT = global.setTimeout, + fGETELEMENTSBYTAGNAME = function(tn){return oDOC.getElementsByTagName(tn);}, + fOBJTOSTRING = Object.prototype.toString, + fNOOP = function(){}, + append_to = {}, + all_scripts = {}, + PAGEROOT = /^[^?#]*\//.exec(oDOCLOC.href)[0], + DOCROOT = /^file:\/\/(localhost)?(\/[a-z]:)?/i.exec(PAGEROOT)[0], + docScripts = fGETELEMENTSBYTAGNAME(sSCRIPT), + + is_ie = !+"\v1", // feature detection based on Andrea Giammarchi's solution: http://webreflection.blogspot.com/2009/01/32-bytes-to-know-if-your-browser-is-ie.html + sync_script_loading = is_ie, // only IE is currently known to do synchronous loading of file:// scripts, others require core LABjs async functionality + + global_defs = { + dupe:bFALSE, // allow duplicate scripts? + preserve:bFALSE, // preserve execution order of all loaded scripts (regardless of preloading) + base:"", // base path to prepend to all non-absolute-path scripts + which:sHEAD // which DOM object ("head" or "body") to append scripts to + } + ; + + append_to[sHEAD] = fGETELEMENTSBYTAGNAME(sHEAD); + append_to[sBODY] = fGETELEMENTSBYTAGNAME(sBODY); + + function canonicalScriptURI(src,base_path) { + if (typeof src !== sSTRING) src = ""; + if (typeof base_path !== sSTRING) base_path = ""; + var ret = (/^file\:\/\//.test(src) ? "" : base_path) + src; + return ((/^file\:\/\//.test(ret) ? "" : (ret.charAt(0) === "/" ? DOCROOT : PAGEROOT)) + ret); + } + function scriptTagExists(uri) { // checks if a script uri has ever been loaded into this page's DOM + var i = 0, script; + while (script = docScripts[i++]) { + if (typeof script.src === sSTRING && uri === canonicalScriptURI(script.src)) return bTRUE; + } + return bFALSE; + } + function engine(opts) { + if (typeof opts === sUNDEF) opts = global_defs; + + var ready = bFALSE, + _which = opts.which, + _base_path = opts.base, + waitFunc = fNOOP, + scripts_loading = bFALSE, + publicAPI, + scripts = {}, + orig_engine = null + ; + + function createScriptTag(scriptentry,src,type,charset) { + if (append_to[scriptentry[sWHICH]][0] === null) { // append_to object not yet ready + fSETTIMEOUT(arguments.callee,25); + return; + } + var scriptElem = oDOC.createElement(sSCRIPT), fSETATTRIBUTE = function(attr,val){scriptElem.setAttribute(attr,val);}; + fSETATTRIBUTE("type",type); + if (typeof charset === sSTRING) fSETATTRIBUTE("charset",charset); + fSETATTRIBUTE("src",src); + append_to[scriptentry[sWHICH]][0].appendChild(scriptElem); + } + function loadScript(o) { + if (typeof o.allowDup === sUNDEF) o.allowDup = opts.dupe; + var src = o.src, type = o.type, charset = o.charset, allowDup = o.allowDup, + src_uri = canonicalScriptURI(src,_base_path), scriptentry; + if (typeof type !== sSTRING) type = "text/javascript"; + if (typeof charset !== sSTRING) charset = null; + allowDup = !(!allowDup); + + if (!allowDup && + ( + (typeof all_scripts[src_uri] !== sUNDEF && all_scripts[src_uri] !== null) || + scriptTagExists(src_uri) + ) + ) { + return; + } + if (typeof scripts[src_uri] === sUNDEF) scripts[src_uri] = {}; + scriptentry = scripts[src_uri]; + if (typeof scriptentry[sWHICH] === sUNDEF) scriptentry[sWHICH] = _which; + scriptentry[sDONE] = bFALSE; + scriptentry[sSRCURI] = src_uri; + scripts_loading = bTRUE; + + all_scripts[scriptentry[sSRCURI]] = bTRUE; + createScriptTag(scriptentry,src_uri,type,charset); + } + function serializeArgs(args) { + var sargs = [], i; + for (i=0; i + @@ -24,12 +25,32 @@ - + + + + + + + + + + + + + + + + + + + + + diff --git a/editor.html b/editor.html deleted file mode 100644 index dcd024fd..00000000 --- a/editor.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - Editor - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - \ No newline at end of file diff --git a/experiments/tokenizer.html b/experiments/tokenizer.html index c2f40f49..c1a6e48a 100644 --- a/experiments/tokenizer.html +++ b/experiments/tokenizer.html @@ -12,7 +12,10 @@ - + + + + + + - + + diff --git a/src/Editor.js b/src/Editor.js index 615ebb51..af0e11b0 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -1,180 +1,12 @@ if (!window.ace) ace = {}; -(function() { - -var keys = { - UP : 38, - RIGHT : 39, - DOWN : 40, - LEFT : 37, - PAGEUP : 33, - PAGEDOWN : 34, - POS1 : 36, - END : 35, - DELETE : 46, - BACKSPACE : 8, - TAB : 9, - A : 65, - D: 68 -}; - -var KeyBinding = function(element, host) { - ace.addListener(element, "keydown", function(e) { - var key = e.keyCode; - - switch (key) { - case keys.A: - if (e.metaKey) { - host.selectAll(); - return ace.stopEvent(e); - } - break; - - case keys.D: - if (e.metaKey) { - host.removeLine(); - return ace.stopEvent(e); - } - break; - - case keys.UP: - if (e.metaKey && e.shiftKey) { - host.selectFileStart(); - } - else if (e.metaKey) { - host.navigateFileStart(); - } - if (e.shiftKey) { - host.selectUp(); - } - else { - host.navigateUp(); - } - return ace.stopEvent(e); - - case keys.DOWN: - if (e.metaKey && e.shiftKey) { - host.selectFileEnd(); - } - else if (e.metaKey) { - host.navigateFileEnd(); - } - if (e.shiftKey) { - host.selectDown(); - } - else { - host.navigateDown(); - } - return ace.stopEvent(e); - - case keys.LEFT: - if (e.altKey && e.shiftKey) { - host.selectWordLeft(); - } - else if (e.altKey) { - host.navigateWordLeft(); - } - else if (e.metaKey && e.shiftKey) { - host.selectLineStart(); - } - else if (e.metaKey) { - host.navigateLineStart(); - } - else if (e.shiftKey) { - host.selectLeft(); - } - else { - host.navigateLeft(); - } - return ace.stopEvent(e); - - case keys.RIGHT: - if (e.altKey && e.shiftKey) { - host.selectWordRight(); - } - else if (e.altKey) { - host.navigateWordRight(); - } - else if (e.metaKey && e.shiftKey) { - host.selectLineEnd(); - } - else if (e.metaKey) { - host.navigateLineEnd(); - } - else if (e.shiftKey) { - host.selectRight(); - } - else { - host.navigateRight(); - } - return ace.stopEvent(e); - - case keys.PAGEDOWN: - if (e.shiftKey) { - host.selectPageDown(); - } - else { - host.scrollPageDown(); - } - return ace.stopEvent(e); - - case keys.PAGEUP: - if (e.shiftKey) { - host.selectPageUp(); - } - else { - host.scrollPageUp(); - } - return ace.stopEvent(e); - - case keys.POS1: - if (e.shiftKey) { - host.selectLineStart(); - } - else { - host.navigateLineStart(); - } - return ace.stopEvent(e); - - case keys.END: - if (e.shiftKey) { - host.selectLineEnd(); - } - else { - host.navigateLineEnd(); - } - return ace.stopEvent(e); - - case keys.DELETE: - host.removeRight(); - return ace.stopEvent(e); - - case keys.BACKSPACE: - host.removeLeft(); - return ace.stopEvent(e); - - case keys.TAB: - if (host.hasMultiLineSelection()) { - if (e.shiftKey) { - host.blockOutdent(); - } else { - host.blockIndent(); - } - } else { - host.onTextInput(" "); - } - return ace.stopEvent(e); - } - }); -}; - ace.Editor = function(doc, renderer) { var container = renderer.getContainerElement(); this.renderer = renderer; this.textInput = new ace.TextInput(container, this); - new KeyBinding(container, this); + new ace.KeyBinding(container, this); ace.addListener(container, "mousedown", ace .bind(this.onMouseDown, this)); @@ -819,6 +651,4 @@ ace.Editor.prototype.selectLine = function() { this._moveSelection(function() { this.moveCursorTo(this.cursor.row + 1, 0); }); -}; - -})(); \ No newline at end of file +}; \ No newline at end of file diff --git a/src/KeyBinding.js b/src/KeyBinding.js new file mode 100644 index 00000000..a347f6f6 --- /dev/null +++ b/src/KeyBinding.js @@ -0,0 +1,172 @@ +if (!window.ace) + ace = {}; + +(function() { + +var keys = { + UP : 38, + RIGHT : 39, + DOWN : 40, + LEFT : 37, + PAGEUP : 33, + PAGEDOWN : 34, + POS1 : 36, + END : 35, + DELETE : 46, + BACKSPACE : 8, + TAB : 9, + A : 65, + D: 68 +}; + +ace.KeyBinding = function(element, host) { + ace.addListener(element, "keydown", function(e) { + var key = e.keyCode; + + switch (key) { + case keys.A: + if (e.metaKey) { + host.selectAll(); + return ace.stopEvent(e); + } + break; + + case keys.D: + if (e.metaKey) { + host.removeLine(); + return ace.stopEvent(e); + } + break; + + case keys.UP: + if (e.metaKey && e.shiftKey) { + host.selectFileStart(); + } + else if (e.metaKey) { + host.navigateFileStart(); + } + if (e.shiftKey) { + host.selectUp(); + } + else { + host.navigateUp(); + } + return ace.stopEvent(e); + + case keys.DOWN: + if (e.metaKey && e.shiftKey) { + host.selectFileEnd(); + } + else if (e.metaKey) { + host.navigateFileEnd(); + } + if (e.shiftKey) { + host.selectDown(); + } + else { + host.navigateDown(); + } + return ace.stopEvent(e); + + case keys.LEFT: + if (e.altKey && e.shiftKey) { + host.selectWordLeft(); + } + else if (e.altKey) { + host.navigateWordLeft(); + } + else if (e.metaKey && e.shiftKey) { + host.selectLineStart(); + } + else if (e.metaKey) { + host.navigateLineStart(); + } + else if (e.shiftKey) { + host.selectLeft(); + } + else { + host.navigateLeft(); + } + return ace.stopEvent(e); + + case keys.RIGHT: + if (e.altKey && e.shiftKey) { + host.selectWordRight(); + } + else if (e.altKey) { + host.navigateWordRight(); + } + else if (e.metaKey && e.shiftKey) { + host.selectLineEnd(); + } + else if (e.metaKey) { + host.navigateLineEnd(); + } + else if (e.shiftKey) { + host.selectRight(); + } + else { + host.navigateRight(); + } + return ace.stopEvent(e); + + case keys.PAGEDOWN: + if (e.shiftKey) { + host.selectPageDown(); + } + else { + host.scrollPageDown(); + } + return ace.stopEvent(e); + + case keys.PAGEUP: + if (e.shiftKey) { + host.selectPageUp(); + } + else { + host.scrollPageUp(); + } + return ace.stopEvent(e); + + case keys.POS1: + if (e.shiftKey) { + host.selectLineStart(); + } + else { + host.navigateLineStart(); + } + return ace.stopEvent(e); + + case keys.END: + if (e.shiftKey) { + host.selectLineEnd(); + } + else { + host.navigateLineEnd(); + } + return ace.stopEvent(e); + + case keys.DELETE: + host.removeRight(); + return ace.stopEvent(e); + + case keys.BACKSPACE: + host.removeLeft(); + return ace.stopEvent(e); + + case keys.TAB: + if (host.hasMultiLineSelection()) { + if (e.shiftKey) { + host.blockOutdent(); + } else { + host.blockIndent(); + } + } else { + host.onTextInput(" "); + } + return ace.stopEvent(e); + } + }); +}; + +})(); \ No newline at end of file From 09c873c7808e6fcef85648da4fffa4ad8a2d6208 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 13 Apr 2010 10:10:56 +0200 Subject: [PATCH 058/392] goto line functionality --- src/Editor.js | 28 ++++++++++++++++++++--- src/KeyBinding.js | 13 ++++++++++- test/MockRenderer.js | 4 ++-- test/NavigationTest.js | 50 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 6 deletions(-) diff --git a/src/Editor.js b/src/Editor.js index af0e11b0..eda02314 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -21,7 +21,7 @@ ace.Editor = function(doc, renderer) { renderer.setDocument(doc); this.tokenizer = new ace.BackgroundTokenizer(new ace.Tokenizer( - ace.XML.RULES), ace.bind(this.onTokenizerUpdate, this)); + ace.JavaScript.RULES), ace.bind(this.onTokenizerUpdate, this)); this.tokenizer.setLines(doc.lines); renderer.setTokenizer(this.tokenizer); @@ -31,6 +31,8 @@ ace.Editor = function(doc, renderer) { column : 0 }; + + this.selectionAnchor = null; this.selectionLead = null; this.selection = null; @@ -260,6 +262,14 @@ ace.Editor.prototype.getLastVisibleRow = function() { return this.renderer.getLastVisibleRow(); }; +ace.Editor.prototype.isRowVisible = function(row) { + return (row >= this.getFirstVisibleRow() && row <= this.getLastVisibleRow()); +}; + +ace.Editor.prototype.getVisibleRowCount = function() { + return this.getLastVisibleRow() - this.getFirstVisibleRow() + 1; +}; + ace.Editor.prototype.getPageDownRow = function() { return this.renderer.getLastVisibleRow() - 1; }; @@ -283,6 +293,12 @@ ace.Editor.prototype.scrollToRow = function(row) { this.renderer.scrollToRow(row); }; +ace.Editor.prototype.navigateTo = function(row, column) { + this.clearSelection(); + this.moveCursorTo(row, column); + this.renderer.scrollCursorIntoView(); +}; + ace.Editor.prototype.navigateUp = function() { this.clearSelection(); this.moveCursorUp(); @@ -486,6 +502,13 @@ ace.Editor.prototype.moveCursorTo = function(row, column) { this.updateCursor(); }; +ace.Editor.prototype.gotoLine = function(lineNumber) { + this.moveCursorTo(lineNumber, 0); + if (!this.isRowVisible(this.cursor.row)) { + this.scrollToRow(lineNumber - Math.floor(this.getVisibleRowCount() / 2)); + } +}, + ace.Editor.prototype.getCursorPosition = function() { return { row : this.cursor.row, @@ -609,8 +632,7 @@ ace.Editor.prototype.selectLineEnd = function() { }; ace.Editor.prototype.selectPageDown = function() { - var visibleRows = this.getLastVisibleRow() - this.getFirstVisibleRow(); - var row = this.getPageDownRow() + Math.round(visibleRows / 2); + var row = this.getPageDownRow() + Math.floor(this.getVisibleRowCount() / 2); this.scrollPageDown(); diff --git a/src/KeyBinding.js b/src/KeyBinding.js index a347f6f6..1e09a731 100644 --- a/src/KeyBinding.js +++ b/src/KeyBinding.js @@ -16,7 +16,8 @@ var keys = { BACKSPACE : 8, TAB : 9, A : 65, - D: 68 + D: 68, + L: 76 }; ace.KeyBinding = function(element, host) { @@ -38,6 +39,16 @@ ace.KeyBinding = function(element, host) { } break; + case keys.L: + if (e.metaKey) { + var line = parseInt(prompt("Enter line number:")); + if (!isNaN(line)) { + host.gotoLine(line); + return ace.stopEvent(e); + } + } + break; + case keys.UP: if (e.metaKey && e.shiftKey) { host.selectFileStart(); diff --git a/test/MockRenderer.js b/test/MockRenderer.js index a634693a..dfe202ee 100644 --- a/test/MockRenderer.js +++ b/test/MockRenderer.js @@ -1,11 +1,11 @@ -MockRenderer = function() { +MockRenderer = function(visibleRowCount) { this.container = document.createElement("div"); this.cursor = { row : 0, column : 0 }; - this.visibleRowCount = 20; + this.visibleRowCount = visibleRowCount || 20; this.layerConfig = { firstVisibleRow : 0, diff --git a/test/NavigationTest.js b/test/NavigationTest.js index b06578fd..1d71fc0e 100644 --- a/test/NavigationTest.js +++ b/test/NavigationTest.js @@ -194,5 +194,55 @@ var NavigationTest = TestCase("NavigationTest", assertPosition(0, 0, selection.start); assertPosition(0, 3, selection.end); + }, + + "test: goto hidden line should scroll the line into the middle of the viewport" : function() { + var editor = new ace.Editor(this.createTextDocument(200, 5), + new MockRenderer()); + + editor.navigateTo(0, 0); + editor.gotoLine(100); + assertPosition(100, 0, editor.getCursorPosition()); + assertEquals(90, editor.getFirstVisibleRow()); + + editor.navigateTo(100, 0); + editor.gotoLine(10); + assertPosition(10, 0, editor.getCursorPosition()); + assertEquals(0, editor.getFirstVisibleRow()); + + editor.navigateTo(100, 0); + editor.gotoLine(5); + assertPosition(5, 0, editor.getCursorPosition()); + assertEquals(0, editor.getFirstVisibleRow()); + + editor.navigateTo(100, 0); + editor.gotoLine(0); + assertPosition(0, 0, editor.getCursorPosition()); + assertEquals(0, editor.getFirstVisibleRow()); + + editor.navigateTo(0, 0); + editor.gotoLine(190); + assertPosition(190, 0, editor.getCursorPosition()); + assertEquals(180, editor.getFirstVisibleRow()); + + editor.navigateTo(0, 0); + editor.gotoLine(195); + assertPosition(195, 0, editor.getCursorPosition()); + assertEquals(180, editor.getFirstVisibleRow()); + }, + + "test: goto visible line should only move the cursor and not scroll": function() { + var editor = new ace.Editor(this.createTextDocument(200, 5), + new MockRenderer()); + + editor.navigateTo(0, 0); + editor.gotoLine(11); + assertPosition(11, 0, editor.getCursorPosition()); + assertEquals(0, editor.getFirstVisibleRow()); + + editor.navigateTo(30, 0); + editor.gotoLine(32); + assertPosition(32, 0, editor.getCursorPosition()); + assertEquals(30, editor.getFirstVisibleRow()); } }); From 2e906f4a353e3cc9194d1bb35a622fdd1c043f24 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 13 Apr 2010 12:09:48 +0200 Subject: [PATCH 059/392] highlight matching brackets --- css/editor.css | 6 +++ src/Editor.js | 33 ++++++++++++++ src/TextDocument.js | 92 ++++++++++++++++++++++++++++++++++++++++ test/TextDocumentTest.js | 35 +++++++++++++++ 4 files changed, 166 insertions(+) create mode 100644 test/TextDocumentTest.js diff --git a/css/editor.css b/css/editor.css index 2cf1701a..5000cb73 100644 --- a/css/editor.css +++ b/css/editor.css @@ -92,4 +92,10 @@ .marker-layer .selection { position: absolute; background: rgba(77, 151, 255, 0.33); +} + +.marker-layer .bracket { + position: absolute; + margin: -1px 0 0 -1px; + border: 1px solid rgb(192, 192, 192); } \ No newline at end of file diff --git a/src/Editor.js b/src/Editor.js index eda02314..769546d3 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -46,8 +46,41 @@ ace.Editor.prototype.resize = function() { ace.Editor.prototype.updateCursor = function() { this.renderer.updateCursor(this.cursor); + this._highlightBrackets(); }; +ace.Editor.prototype._highlightBrackets = function() { + + if (this._bracketHighlight) { + this.renderer.removeMarker(this._bracketHighlight); + this._bracketHighlight = null; + } + + if (this._highlightPending) { + return; + } + + // perform highlight async to not block the browser during navigation + var self = this; + this._highlightPending = true; + setTimeout(function() { + self._highlightPending = false; + + var pos = self.doc.findMatchingBracket(self.cursor); + if (pos) { + range = { + start: pos, + end: { + row: pos.row, + column: pos.column+1 + } + }; + self._bracketHighlight = self.renderer.addMarker(range, "bracket"); + } + }, 10); +}; + + ace.Editor.prototype.onFocus = function() { this.renderer.showCursor(); this.renderer.visualizeFocus(); diff --git a/src/TextDocument.js b/src/TextDocument.js index d6b66e75..5c286081 100644 --- a/src/TextDocument.js +++ b/src/TextDocument.js @@ -90,6 +90,98 @@ ace.TextDocument.prototype.getTextRange = function(range) { } }; +ace.TextDocument.prototype.findMatchingBracket = function(position) { + if (position.column == 0) return null; + + var charBeforeCursor = this.getLine(position.row).charAt(position.column-1); + if (charBeforeCursor == "") return null; + + var match = charBeforeCursor.match(/([\(\[\{])|([\)\]\}])/); + if (!match) { + return null; + } + + if (match[1]) { + return this._findClosingBracket(match[1], position); + } else { + return this._findOpeningBracket(match[2], position); + } +}; + +ace.TextDocument.prototype._brackets = { + ")": "(", + "(": ")", + "]": "[", + "[": "]", + "{": "}", + "}": "{" +}; + +ace.TextDocument.prototype._findOpeningBracket = function(bracket, position) { + var openBracket = this._brackets[bracket]; + + var column = position.column - 2; + var row = position.row; + var depth = 1; + + var line = this.getLine(row); + + while (true) { + while(column >= 0) { + var char = line.charAt(column); + if (char == openBracket) { + depth -= 1; + if (depth == 0) { + return {row: row, column: column}; + } + } + else if (char == bracket) { + depth +=1; + } + column -= 1; + } + row -=1; + if (row < 0) break; + + var line = this.getLine(row); + var column = line.length-1; + } + return null; +}; + +ace.TextDocument.prototype._findClosingBracket = function(bracket, position) { + var closingBracket = this._brackets[bracket]; + + var column = position.column; + var row = position.row; + var depth = 1; + + var line = this.getLine(row); + var lineCount = this.getLength(); + + while (true) { + while(column < line.length) { + var char = line.charAt(column); + if (char == closingBracket) { + depth -= 1; + if (depth == 0) { + return {row: row, column: column}; + } + } + else if (char == bracket) { + depth +=1; + } + column += 1; + } + row +=1; + if (row >= lineCount) break; + + var line = this.getLine(row); + var column = 0; + } + return null; +}; + ace.TextDocument.prototype.insert = function(position, text) { var end = this._insert(position, text); this.fireChangeEvent(position.row, position.row == end.row ? position.row diff --git a/test/TextDocumentTest.js b/test/TextDocumentTest.js new file mode 100644 index 00000000..39f6cbaa --- /dev/null +++ b/test/TextDocumentTest.js @@ -0,0 +1,35 @@ +var TextDocumentTest = new TestCase("TextDocumentTest", { + + "test: find matching opening bracket" : function() { + var doc = new ace.TextDocument(["(()(", "())))"].join("\n")); + + assertPosition(0, 1, doc.findMatchingBracket({row: 0, column: 3})); + assertPosition(1, 0, doc.findMatchingBracket({row: 1, column: 2})); + assertPosition(0, 3, doc.findMatchingBracket({row: 1, column: 3})); + assertPosition(0, 0, doc.findMatchingBracket({row: 1, column: 4})); + assertEquals(null, doc.findMatchingBracket({row: 1, column: 5})); + }, + + "test: find matching closing bracket" : function() { + var doc = new ace.TextDocument(["(()(", "())))"].join("\n")); + + assertPosition(1, 1, doc.findMatchingBracket({row: 1, column: 1})); + assertPosition(1, 1, doc.findMatchingBracket({row: 1, column: 1})); + assertPosition(1, 2, doc.findMatchingBracket({row: 0, column: 4})); + assertPosition(0, 2, doc.findMatchingBracket({row: 0, column: 2})); + assertPosition(1, 3, doc.findMatchingBracket({row: 0, column: 1})); + assertEquals(null, doc.findMatchingBracket({row: 0, column: 0})); + }, + + "test: match different bracket types" : function() { + var doc = new ace.TextDocument(["({[", ")]}"].join("\n")); + + assertPosition(1, 0, doc.findMatchingBracket({row: 0, column: 1})); + assertPosition(1, 2, doc.findMatchingBracket({row: 0, column: 2})); + assertPosition(1, 1, doc.findMatchingBracket({row: 0, column: 3})); + + assertPosition(0, 0, doc.findMatchingBracket({row: 1, column: 1})); + assertPosition(0, 2, doc.findMatchingBracket({row: 1, column: 2})); + assertPosition(0, 1, doc.findMatchingBracket({row: 1, column: 3})); + } +}); \ No newline at end of file From ee1c4a0169dc78910e91a77b075c987f6554ccf8 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 13 Apr 2010 18:23:34 +0200 Subject: [PATCH 060/392] add Ruben's text selection mode --- src/Editor.js | 8 +- src/MarkerLayer.js | 171 +++++++++++++++++++++++++++-------------- src/VirtualRenderer.js | 1 + 3 files changed, 118 insertions(+), 62 deletions(-) diff --git a/src/Editor.js b/src/Editor.js index 769546d3..0cd9e1a7 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -31,8 +31,6 @@ ace.Editor = function(doc, renderer) { column : 0 }; - - this.selectionAnchor = null; this.selectionLead = null; this.selection = null; @@ -40,7 +38,9 @@ ace.Editor = function(doc, renderer) { this.renderer.draw(); }; -ace.Editor.prototype.resize = function() { +ace.Editor.prototype.resize = function() +{ + this.renderer.scrollToY(this.renderer.getScrollTop()); this.renderer.draw(); }; @@ -636,7 +636,7 @@ ace.Editor.prototype._moveSelection = function(mover) { this.renderer.removeMarker(this.selection); } this.selection = this.renderer.addMarker(this.getSelectionRange(), - "selection"); + "selection", "text"); this.renderer.scrollCursorIntoView(); }; diff --git a/src/MarkerLayer.js b/src/MarkerLayer.js index c02ea039..4a10916c 100644 --- a/src/MarkerLayer.js +++ b/src/MarkerLayer.js @@ -10,11 +10,15 @@ ace.MarkerLayer = function(parentEl) { this._markerId = 1; }; -ace.MarkerLayer.prototype.addMarker = function(range, clazz) { +ace.MarkerLayer.prototype.setDocument = function(doc) { + this.doc = doc; +}; + +ace.MarkerLayer.prototype.addMarker = function(range, clazz, type) { var id = this._markerId++; this.markers[id] = { range : range, - type : "line", + type : type || "line", clazz : clazz }; @@ -40,68 +44,119 @@ ace.MarkerLayer.prototype.update = function(config) { var html = []; for ( var key in this.markers) { var marker = this.markers[key]; - var range = marker.range; + var range = { + start: marker.range.start, + end: marker.range.end + }; + + // clip + if (range.start.row > config.lastRow) continue; + if (range.end.row < config.firstRow) continue; + + if (range.end.row > config.lastRow) { + range.end = { + row: config.lastRow, + column: this.doc.getLine(config.lastRow).length + }; + } + + if (range.start.row < config.firstRow) { + range.start = { + row: config.firstRow, + column: 0 + }; + } if (range.start.row !== range.end.row) { - if (range.start.row >= config.firstRow - && range.start.row <= config.lastRow) { - html - .push( - "
"); + if (marker.type == "line") { + this.drawTextMarker(html, range, marker.clazz, config); + } else { + this.drawMultiLineMarker(html, range, marker.clazz, config); } - - 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.drawSingleLineMarker(html, range, marker.clazz, config); } } this.element.innerHTML = html.join(""); +}; + +ace.MarkerLayer.prototype.drawTextMarker = function(stringBuilder, range, clazz, layerConfig) { + + // selection start + var row = range.start.row; + var lineRange = { start: {}, end: {}}; + + lineRange.start.row = row; + lineRange.start.column = range.start.column; + lineRange.end.row = row; + lineRange.end.column = this.doc.getLine(row).length; + this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig); + + // selection end + var row = range.end.row; + lineRange.start.row = row; + lineRange.start.column = 0; + lineRange.end.row = row; + lineRange.end.column = range.end.column; + this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig); + + for (var row = range.start.row + 1; row < range.end.row; row++) { + lineRange.start.row = row; + lineRange.end.row = row; + lineRange.end.column = this.doc.getLine(row).length; + this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig); + } +}; + +ace.MarkerLayer.prototype.drawMultiLineMarker = function(stringBuilder, range, clazz, layerConfig) { + + var height = layerConfig.lineHeight; + var width = Math.round(layerConfig.width - (range.start.column * layerConfig.characterWidth)); + var top = (range.start.row - layerConfig.firstRow) * layerConfig.lineHeight; + var left = Math.round(range.start.column * layerConfig.characterWidth); + + stringBuilder.push( + "
" + ); + + var top = (range.end.row - layerConfig.firstRow) * layerConfig.lineHeight; + var width = Math.round(range.end.column * layerConfig.characterWidth); + + stringBuilder.push( + "
" + ); + + for (var row = range.start.row + 1; row < range.end.row; row++) { + var top = (row - layerConfig.firstRow) * layerConfig.lineHeight; + stringBuilder.push( + "
" + ); + } +}; + +ace.MarkerLayer.prototype.drawSingleLineMarker = function(stringBuilder, range, clazz, layerConfig) { + + var height = layerConfig.lineHeight; + var width = Math.round((range.end.column - range.start.column) * layerConfig.characterWidth); + var top = (range.start.row - layerConfig.firstRow) * layerConfig.lineHeight; + var left = Math.round(range.start.column * layerConfig.characterWidth); + + stringBuilder.push( + "
" + ); }; \ No newline at end of file diff --git a/src/VirtualRenderer.js b/src/VirtualRenderer.js index 3034e12b..1d07c96e 100644 --- a/src/VirtualRenderer.js +++ b/src/VirtualRenderer.js @@ -37,6 +37,7 @@ ace.VirtualRenderer = function(container) { ace.VirtualRenderer.prototype.setDocument = function(doc) { this.lines = doc.lines; this.doc = doc; + this.markerLayer.setDocument(doc); }; ace.VirtualRenderer.prototype.setTokenizer = function(tokenizer) { From b9391734b6e77a053b9b3d4e80bc3b7ffb5201fa Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 14 Apr 2010 09:53:17 +0200 Subject: [PATCH 061/392] introduce ace.provide function to create namespaces --- demo/editor.html | 2 +- src/BackgroundTokenizer.js | 3 +- src/CursorLayer.js | 3 +- src/Editor.js | 3 +- src/GutterLayer.js | 3 +- src/JavaScript.js | 209 ++++++++++++++++++------------------- src/KeyBinding.js | 3 +- src/MarkerLayer.js | 3 +- src/TextDocument.js | 3 +- src/TextInput.js | 3 +- src/TextLayer.js | 3 +- src/Tokenizer.js | 3 +- src/VirtualRenderer.js | 3 +- src/XML.js | 129 +++++++++++------------ src/ace.js | 171 ++++++++++++++++++++++++++++++ src/lib.js | 163 ----------------------------- 16 files changed, 349 insertions(+), 358 deletions(-) create mode 100644 src/ace.js delete mode 100644 src/lib.js diff --git a/demo/editor.html b/demo/editor.html index 20bc64a7..6505694e 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -19,7 +19,7 @@ - + diff --git a/src/BackgroundTokenizer.js b/src/BackgroundTokenizer.js index 6b441601..4698b31c 100644 --- a/src/BackgroundTokenizer.js +++ b/src/BackgroundTokenizer.js @@ -1,5 +1,4 @@ -if (!window.ace) - ace = {}; +ace.provide("ace.BackgroundTokenizer"); ace.BackgroundTokenizer = function(tokenizer, onUpdate, onComplete) { this.running = false; diff --git a/src/CursorLayer.js b/src/CursorLayer.js index 76ef0640..dcf4c2a7 100644 --- a/src/CursorLayer.js +++ b/src/CursorLayer.js @@ -1,5 +1,4 @@ -if (!window.ace) - ace = {}; +ace.provide("ace.CursorLayer"); ace.CursorLayer = function(parentEl) { this.element = document.createElement("div"); diff --git a/src/Editor.js b/src/Editor.js index 0cd9e1a7..58353809 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -1,5 +1,4 @@ -if (!window.ace) - ace = {}; +ace.provide("ace.Editor"); ace.Editor = function(doc, renderer) { var container = renderer.getContainerElement(); diff --git a/src/GutterLayer.js b/src/GutterLayer.js index 42e027cd..4256a4e9 100644 --- a/src/GutterLayer.js +++ b/src/GutterLayer.js @@ -1,5 +1,4 @@ -if (!window.ace) - ace = {}; +ace.provide("ace.GutterLayer"); ace.GutterLayer = function(parentEl) { this.element = document.createElement("div"); diff --git a/src/JavaScript.js b/src/JavaScript.js index 254e6e94..f6747ffe 100644 --- a/src/JavaScript.js +++ b/src/JavaScript.js @@ -1,114 +1,111 @@ -if (!window.ace) - ace = {}; +ace.provide("ace.JavaScript"); (function() { - ace.JavaScript = {}; +var keywords = { + "break" : 1, + "case" : 1, + "catch" : 1, + "continue" : 1, + "default" : 1, + "delete" : 1, + "do" : 1, + "else" : 1, + "finally" : 1, + "for" : 1, + "function" : 1, + "if" : 1, + "in" : 1, + "instanceof" : 1, + "new" : 1, + "return" : 1, + "switch" : 1, + "throw" : 1, + "try" : 1, + "typeof" : 1, + "var" : 1, + "while" : 1, + "with" : 1 +}; - var keywords = { - "break" : 1, - "case" : 1, - "catch" : 1, - "continue" : 1, - "default" : 1, - "delete" : 1, - "do" : 1, - "else" : 1, - "finally" : 1, - "for" : 1, - "function" : 1, - "if" : 1, - "in" : 1, - "instanceof" : 1, - "new" : 1, - "return" : 1, - "switch" : 1, - "throw" : 1, - "try" : 1, - "typeof" : 1, - "var" : 1, - "while" : 1, - "with" : 1 - }; +// regexp must not have capturing parentheses +// regexps are ordered -> the first match is used - // regexp must not have capturing parentheses - // regexps are ordered -> the first match is used - - ace.JavaScript.RULES = { - start : [ { - token : "comment", - regex : "\\/\\/.*$" - }, { - token : "comment", // multi line comment in one line - regex : "\\/\\*.*?\\*\\/" - }, { - token : "comment", // multi line comment start - regex : "\\/\\*.*$", - next : "comment" - }, { - token : "string", // single line - regex : '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' - }, { - token : "string", // multi line string start - regex : '["].*\\\\$', - next : "qqstring" - }, { - token : "string", // single line - regex : "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']" - }, { - token : "string", // multi line string start - regex : "['].*\\\\$", - next : "qstring" - }, { - token : "number", // hex - regex : "0[xX][0-9a-fA-F]+\\b" - }, { - token : "number", // float - regex : "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b" - }, { - token : function(value) { - if (keywords[value]) { - return "keyword"; - } - else { - return "identifier"; - } - }, - regex : "[a-zA-Z_][a-zA-Z0-9_]*\\b" - }, { - token : function(value) { - // return parens[value]; - return "text"; +ace.JavaScript.RULES = { + start : [ { + token : "comment", + regex : "\\/\\/.*$" + }, { + token : "comment", // multi line comment in one line + regex : "\\/\\*.*?\\*\\/" + }, { + token : "comment", // multi line comment start + regex : "\\/\\*.*$", + next : "comment" + }, { + token : "string", // single line + regex : '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' + }, { + token : "string", // multi line string start + regex : '["].*\\\\$', + next : "qqstring" + }, { + token : "string", // single line + regex : "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']" + }, { + token : "string", // multi line string start + regex : "['].*\\\\$", + next : "qstring" + }, { + token : "number", // hex + regex : "0[xX][0-9a-fA-F]+\\b" + }, { + token : "number", // float + regex : "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b" + }, { + token : function(value) { + if (keywords[value]) { + return "keyword"; + } + else { + return "identifier"; + } }, - regex : "[\\[\\]\\(\\)\\{\\}]" - }, { - token : "text", - regex : "\\s+" - } ], - "comment" : [ { - token : "comment", // closing comment - regex : ".*?\\*\\/", - next : "start" - }, { - token : "comment", // comment spanning whole line - regex : ".+" - } ], - "qqstring" : [ { - token : "string", - regex : '(?:(?:\\\\.)|(?:[^"\\\\]))*?"', - next : "start" - }, { - token : "string", - regex : '.+' - } ], - "qstring" : [ { - token : "string", - regex : "(?:(?:\\\\.)|(?:[^'\\\\]))*?'", - next : "start" - }, { - token : "string", - regex : '.+' - } ] - }; + regex : "[a-zA-Z_][a-zA-Z0-9_]*\\b" + }, { + token : function(value) { + // return parens[value]; + return "text"; + }, + regex : "[\\[\\]\\(\\)\\{\\}]" + }, { + token : "text", + regex : "\\s+" + } ], + "comment" : [ { + token : "comment", // closing comment + regex : ".*?\\*\\/", + next : "start" + }, { + token : "comment", // comment spanning whole line + regex : ".+" + } ], + "qqstring" : [ { + token : "string", + regex : '(?:(?:\\\\.)|(?:[^"\\\\]))*?"', + next : "start" + }, { + token : "string", + regex : '.+' + } ], + "qstring" : [ { + token : "string", + regex : "(?:(?:\\\\.)|(?:[^'\\\\]))*?'", + next : "start" + }, { + token : "string", + regex : '.+' + } ] +}; })(); \ No newline at end of file diff --git a/src/KeyBinding.js b/src/KeyBinding.js index 1e09a731..f7dd006c 100644 --- a/src/KeyBinding.js +++ b/src/KeyBinding.js @@ -1,5 +1,4 @@ -if (!window.ace) - ace = {}; +ace.provide("ace.KeyBinding"); (function() { diff --git a/src/MarkerLayer.js b/src/MarkerLayer.js index 4a10916c..cfa019e2 100644 --- a/src/MarkerLayer.js +++ b/src/MarkerLayer.js @@ -1,5 +1,4 @@ -if (!window.ace) - ace = {}; +ace.provide("ace.MarkerLayer"); ace.MarkerLayer = function(parentEl) { this.element = document.createElement("div"); diff --git a/src/TextDocument.js b/src/TextDocument.js index 5c286081..f8d26198 100644 --- a/src/TextDocument.js +++ b/src/TextDocument.js @@ -1,5 +1,4 @@ -if (!window.ace) - ace = {}; +ace.provide("ace.TextDocument"); ace.TextDocument = function(text) { this.lines = this._split(text); diff --git a/src/TextInput.js b/src/TextInput.js index ede8a9bd..4a3c3c25 100644 --- a/src/TextInput.js +++ b/src/TextInput.js @@ -1,5 +1,4 @@ -if (!window.ace) - ace = {}; +ace.provide("ace.TextInput"); ace.TextInput = function(parentNode, host) { diff --git a/src/TextLayer.js b/src/TextLayer.js index bfa95410..d91ba5ed 100644 --- a/src/TextLayer.js +++ b/src/TextLayer.js @@ -1,5 +1,4 @@ -if (!window.ace) - ace = {}; +ace.provide("ace.TextLayer"); ace.TextLayer = function(parentEl) { this.element = document.createElement("div"); diff --git a/src/Tokenizer.js b/src/Tokenizer.js index 923f605e..fb1d539e 100644 --- a/src/Tokenizer.js +++ b/src/Tokenizer.js @@ -1,5 +1,4 @@ -if (!window.ace) - ace = {}; +ace.provide("ace.Tokenizer"); ace.Tokenizer = function(rules) { this.rules = rules; diff --git a/src/VirtualRenderer.js b/src/VirtualRenderer.js index 1d07c96e..001adbe5 100644 --- a/src/VirtualRenderer.js +++ b/src/VirtualRenderer.js @@ -1,5 +1,4 @@ -if (!window.ace) - ace = {}; +ace.provide("ace.VirtualRenderer"); ace.VirtualRenderer = function(container) { this.container = container; diff --git a/src/XML.js b/src/XML.js index 96389128..842f4497 100644 --- a/src/XML.js +++ b/src/XML.js @@ -1,75 +1,72 @@ -if (!window.ace) - ace = {}; +ace.provide("ace.XML"); (function() { - ace.XML = {}; +// regexp must not have capturing parentheses +// regexps are ordered -> the first match is used - // regexp must not have capturing parentheses - // regexps are ordered -> the first match is used +ace.XML.RULES = { + start : [ { + token : "text", + regex : "<\\!\\[CDATA\\[", + next : "cdata" + }, { + token : "xml_pe", + regex : "<\\?.*?\\?>" + }, { + token : "comment", + regex : "<\\!--", + next : "comment" + }, { + token : "text", // opening tag + regex : "<", + next : "tag" + }, { + token : "text", + regex : "\\s+" + }, { + token : "text", + regex : ".+" + } ], - ace.XML.RULES = { - start : [ { - token : "text", - regex : "<\\!\\[CDATA\\[", - next : "cdata" - }, { - token : "xml_pe", - regex : "<\\?.*?\\?>" - }, { - token : "comment", - regex : "<\\!--", - next : "comment" - }, { - token : "text", // opening tag - regex : "<", - next : "tag" - }, { - token : "text", - regex : "\\s+" - }, { - token : "text", - regex : ".+" - } ], + tag : [ { + token : "text", + regex : ">", + next : "start" + }, { + token : "keyword", + regex : "[-_a-zA-Z0-9:]+" + }, { + token : "text", + regex : "\\s+" + }, { + token : "string", + regex : '".*?"' + }, { + token : "string", + regex : "'.*?'" + } ], - tag : [ { - token : "text", - regex : ">", - next : "start" - }, { - token : "keyword", - regex : "[-_a-zA-Z0-9:]+" - }, { - token : "text", - regex : "\\s+" - }, { - token : "string", - regex : '".*?"' - }, { - token : "string", - regex : "'.*?'" - } ], + cdata : [ { + token : "text", + regex : "\\]\\]>", + next : "start" + }, { + token : "text", + regex : "\\s+" + }, { + token : "text", + regex : ".+" + } ], - cdata : [ { - token : "text", - regex : "\\]\\]>", - next : "start" - }, { - token : "text", - regex : "\\s+" - }, { - token : "text", - regex : ".+" - } ], - - comment : [ { - token : "comment", - regex : ".*?-->", - next : "start" - }, { - token : "comment", - regex : ".+" - } ] - }; + comment : [ { + token : "comment", + regex : ".*?-->", + next : "start" + }, { + token : "comment", + regex : ".+" + } ] +}; })(); \ No newline at end of file diff --git a/src/ace.js b/src/ace.js new file mode 100644 index 00000000..ebcef88a --- /dev/null +++ b/src/ace.js @@ -0,0 +1,171 @@ +if (!window.ace) + ace = {}; + +ace.provide = function(namespace) { + var parts = namespace.split("."); + var obj = window; + for (var i=0; i Date: Wed, 14 Apr 2010 11:39:42 +0200 Subject: [PATCH 062/392] reorganize language mode support --- demo/editor.html | 13 ++- jsTestDriver.conf | 3 + src/Editor.js | 40 +++++++--- src/JavaScript.js | 111 -------------------------- src/XML.js | 72 ----------------- src/ace.js | 16 ++-- src/mode/JavaScript.js | 7 ++ src/mode/JavaScriptHighlightRules.js | 114 +++++++++++++++++++++++++++ src/mode/Text.js | 9 +++ src/mode/Xml.js | 6 ++ src/mode/XmlHighlightRules.js | 75 ++++++++++++++++++ test/NavigationTest.js | 44 +++++------ test/TextEditTest.js | 10 +-- 13 files changed, 287 insertions(+), 233 deletions(-) delete mode 100644 src/JavaScript.js delete mode 100644 src/XML.js create mode 100644 src/mode/JavaScript.js create mode 100644 src/mode/JavaScriptHighlightRules.js create mode 100644 src/mode/Text.js create mode 100644 src/mode/Xml.js create mode 100644 src/mode/XmlHighlightRules.js diff --git a/demo/editor.html b/demo/editor.html index 6505694e..c0bdfb1d 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -20,9 +20,12 @@ + + + + + - - @@ -42,7 +45,11 @@ diff --git a/jsTestDriver.conf b/jsTestDriver.conf index 0a1df7c2..48f4c221 100644 --- a/jsTestDriver.conf +++ b/jsTestDriver.conf @@ -6,4 +6,6 @@ load: - src/mode/Text.js - src/mode/*.js - src/*.js - - test/*.js \ No newline at end of file + + - test/*.js + - test/mode/*.js \ No newline at end of file diff --git a/src/BackgroundTokenizer.js b/src/BackgroundTokenizer.js index 4698b31c..161b8b5d 100644 --- a/src/BackgroundTokenizer.js +++ b/src/BackgroundTokenizer.js @@ -47,6 +47,13 @@ ace.BackgroundTokenizer = function(tokenizer, onUpdate, onComplete) { }; }; +ace.BackgroundTokenizer.prototype.setTokenizer = function(tokenizer) { + this.tokenizer = tokenizer; + this.lines = []; + + this.start(0); +}; + ace.BackgroundTokenizer.prototype.setLines = function(textLines) { this.textLines = textLines; this.lines = []; diff --git a/src/Editor.js b/src/Editor.js index af71ed83..6efd21db 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -4,8 +4,8 @@ ace.Editor = function(renderer, doc, mode) { var container = renderer.getContainerElement(); this.renderer = renderer; - this.setDocument(doc || new ace.TextDocument("")); this.setMode(mode || new ace.mode.Text()); + this.setDocument(doc || new ace.TextDocument("")); this.textInput = new ace.TextInput(container, this); new ace.KeyBinding(container, this); @@ -39,20 +39,23 @@ ace.Editor.prototype.setDocument = function(doc) { this.doc = doc; doc.addChangeListener(ace.bind(this.onDocumentChange, this)); this.renderer.setDocument(doc); + + this.bgTokenizer.setLines(this.doc.lines); }; ace.Editor.prototype.setMode = function(mode) { - // TODO: mode change is not yet supported - if (this.mode) { - throw new Error("TODO: mode change is not yet supported"); - } this.mode = mode; + var tokenizer = mode.getTokenizer(); - this.tokenizer = new ace.BackgroundTokenizer(mode.getTokenizer(), ace.bind(this.onTokenizerUpdate, this)); + if (!this.bgTokenizer) { + var onUpdate = ace.bind(this.onTokenizerUpdate, this); + this.bgTokenizer = new ace.BackgroundTokenizer(tokenizer, onUpdate); + } else { + this.bgTokenizer.setTokenizer(tokenizer); + } - this.tokenizer.setLines(this.doc.lines); - this.renderer.setTokenizer(this.tokenizer); + this.renderer.setTokenizer(this.bgTokenizer); }; ace.Editor.prototype.resize = function() @@ -109,7 +112,7 @@ ace.Editor.prototype.onBlur = function() { }; ace.Editor.prototype.onDocumentChange = function(startRow, endRow) { - this.tokenizer.start(startRow); + this.bgTokenizer.start(startRow); this.renderer.updateLines(startRow, endRow); }; @@ -259,35 +262,30 @@ ace.Editor.prototype.removeLine = function() { }; ace.Editor.prototype.blockIndent = function(indentString) { - if (!this.hasSelection()) { - return; - }; - - var range = this.getSelectionRange(); + if (!this.hasSelection()) return; var indentString = indentString || " "; - this.doc.indentRows(range, indentString); + var addedColumns = this.doc.indentRows(this.getSelectionRange(), indentString); - this.setSelectionAnchor(range.start.row, range.start.column + indentString.length); - this._moveSelection(function() { - this.moveCursorTo(range.end.row, range.end.column + indentString.length); - }); + this.shiftSelection(addedColumns); }; ace.Editor.prototype.blockOutdent = function(indentString) { - if (!this.hasSelection()) { - return; - }; - - var range = this.getSelectionRange(); + if (!this.hasSelection()) return; var indentString = indentString || " "; - var removedColumns = this.doc.outdentRows(range, indentString); + var addedColumns = this.doc.outdentRows(this.getSelectionRange(), indentString); - this.setSelectionAnchor(range.start.row, range.start.column - removedColumns); - this._moveSelection(function() { - this.moveCursorTo(range.end.row, range.end.column - removedColumns); - }); + this.shiftSelection(addedColumns); +}; + +ace.Editor.prototype.toggleCommentLines = function() { + if (!this.hasSelection()) return; + + var selection = this.getSelectionRange(); + var addedColumns = this.mode.toggleCommentLines(this.doc, selection); + + this.shiftSelection(addedColumns); }; ace.Editor.prototype.onCompositionStart = function() { @@ -591,6 +589,40 @@ ace.Editor.prototype.setSelectionAnchor = function(row, column) { this.selectionLead = null; }; +ace.Editor.prototype.getSelectionAnchor = function() { + if (this.selectionAnchor) { + return { + row: this.selectionAnchor.row, + column: this.selectionAnchor.column + }; + } else { + return null; + } +}; + +ace.Editor.prototype.getSelectionLead = function() { + if (this.selectionLead) { + return { + row: this.selectionLead.row, + column: this.selectionLead.column + }; + } else { + return null; + } +}; + +ace.Editor.prototype.shiftSelection = function(columns) { + if (!this.hasSelection()) return; + + var anchor = this.getSelectionAnchor(); + var lead = this.getSelectionLead(); + + this.setSelectionAnchor(anchor.row, anchor.column + columns); + this._moveSelection(function() { + this.moveCursorTo(lead.row, lead.column + columns); + }); +}; + ace.Editor.prototype.getSelectionRange = function() { var anchor = this.selectionAnchor; var lead = this.selectionLead; diff --git a/src/KeyBinding.js b/src/KeyBinding.js index f7dd006c..f53dfddd 100644 --- a/src/KeyBinding.js +++ b/src/KeyBinding.js @@ -16,7 +16,8 @@ var keys = { TAB : 9, A : 65, D: 68, - L: 76 + L: 76, + "7": 55 }; ace.KeyBinding = function(element, host) { @@ -48,6 +49,13 @@ ace.KeyBinding = function(element, host) { } break; + case keys["7"]: + if (e.metaKey) { + host.toggleCommentLines(); + return ace.stopEvent(e); + }; + break; + case keys.UP: if (e.metaKey && e.shiftKey) { host.selectFileStart(); diff --git a/src/TextDocument.js b/src/TextDocument.js index f8d26198..5934b835 100644 --- a/src/TextDocument.js +++ b/src/TextDocument.js @@ -89,6 +89,10 @@ ace.TextDocument.prototype.getTextRange = function(range) { } }; +ace.TextDocument.prototype.getLines = function(firstRow, lastRow) { + return this.lines.slice(firstRow, lastRow+1); +}; + ace.TextDocument.prototype.findMatchingBracket = function(position) { if (position.column == 0) return null; @@ -279,6 +283,7 @@ ace.TextDocument.prototype.indentRows = function(range, indentString) { this.lines[i] = indentString + this.getLine(i); } this.fireChangeEvent(range.start.row, range.end.row); + return indentString.length; }; ace.TextDocument.prototype.outdentRows = function(range, indentString) { @@ -295,5 +300,5 @@ ace.TextDocument.prototype.outdentRows = function(range, indentString) { } this.fireChangeEvent(range.start.row, range.end.row); - return outdentLength; + return -outdentLength; }; \ No newline at end of file diff --git a/src/mode/JavaScript.js b/src/mode/JavaScript.js index 0f6003a6..4dbab043 100644 --- a/src/mode/JavaScript.js +++ b/src/mode/JavaScript.js @@ -5,3 +5,10 @@ ace.mode.JavaScript = function() { }; ace.inherits(ace.mode.JavaScript, ace.mode.Text); +ace.mode.JavaScript.prototype.toggleCommentLines = function(doc, range) { + var addedRows = doc.outdentRows(range, "//"); + if (addedRows == 0) { + var addedRows = doc.indentRows(range, "//"); + }; + return addedRows; +}; \ No newline at end of file diff --git a/src/mode/Text.js b/src/mode/Text.js index c48f4cf1..5b35cfbc 100644 --- a/src/mode/Text.js +++ b/src/mode/Text.js @@ -1,9 +1,19 @@ ace.provide("ace.mode.Text"); ace.mode.Text = function() { - this.$tokenizer = new ace.Tokenizer({}); + var rules = { + "start" : [ { + token : "text", + regex : ".+" + } ] + }; + this.$tokenizer = new ace.Tokenizer(rules); }; ace.mode.Text.prototype.getTokenizer = function() { return this.$tokenizer; +}; + +ace.mode.Text.prototype.toggleCommentLines = function(doc, range) { + return 0; }; \ No newline at end of file diff --git a/src/mode/Xml.js b/src/mode/Xml.js index 23ccc4b1..dbef442c 100644 --- a/src/mode/Xml.js +++ b/src/mode/Xml.js @@ -1,6 +1,6 @@ ace.provide("ace.mode.Xml"); ace.mode.Xml = function() { - this.$tokenizer = new Tokenizer(new ace.mode.XmlHighlightRules().getRules()); + this.$tokenizer = new ace.Tokenizer(new ace.mode.XmlHighlightRules().getRules()); }; ace.inherits(ace.mode.Xml, ace.mode.Text); diff --git a/test/TextEditTest.js b/test/TextEditTest.js index 55f0ea4e..d2ab4796 100644 --- a/test/TextEditTest.js +++ b/test/TextEditTest.js @@ -73,5 +73,39 @@ var TextEditTest = TestCase("TextEditTest", var selection = editor.getSelectionRange(); assertPosition(0, 1, selection.start); assertPosition(2, 1, selection.end); + }, + + "test: comment lines should perserve selection" : function() { + var doc = new ace.TextDocument([" abc", "cde"].join("\n")); + var editor = new ace.Editor(new MockRenderer(), doc, new ace.mode.JavaScript()); + + editor.moveCursorTo(0, 2); + editor.selectDown(); + + editor.toggleCommentLines(); + + assertEquals(["// abc", "//cde"].join("\n"), doc.toString()); + + var selection = editor.getSelectionRange(); + assertPosition(0, 4, selection.start); + assertPosition(1, 4, selection.end); + }, + + "test: uncomment lines should perserve selection" : function() { + var doc = new ace.TextDocument(["// abc", "//cde"].join("\n")); + var editor = new ace.Editor(new MockRenderer(), doc, new ace.mode.JavaScript()); + + editor.moveCursorTo(0, 1); + editor.selectDown(); + editor.selectRight(); + editor.selectRight(); + + editor.toggleCommentLines(); + + assertEquals([" abc", "cde"].join("\n"), doc.toString()); + + var selection = editor.getSelectionRange(); + assertPosition(0, 0, selection.start); + assertPosition(1, 1, selection.end); } }); \ No newline at end of file diff --git a/test/mode/JavaScriptTest.js b/test/mode/JavaScriptTest.js new file mode 100644 index 00000000..2a0ce050 --- /dev/null +++ b/test/mode/JavaScriptTest.js @@ -0,0 +1,51 @@ +var JavaScriptTest = new TestCase("mode.JavaScriptTest", { + + setUp : function() { + this.mode = new ace.mode.JavaScript(); + }, + + "test: getTokenizer() (smoke test)" : function() { + var tokenizer = this.mode.getTokenizer(); + + assertTrue(tokenizer instanceof ace.Tokenizer); + + var tokens = tokenizer.getLineTokens("'juhu'", "start").tokens; + assertEquals("string", tokens[0].type); + }, + + "test: toggle comment lines should prepend '//' to each line" : function() { + var doc = new ace.TextDocument([" abc", "cde", "fg"].join("\n")); + + var range = { + start: {row: 0, column: 3}, + end: {row: 1, column: 1} + }; + + var comment = this.mode.toggleCommentLines(doc, range); + assertEquals(["// abc", "//cde", "fg"].join("\n"), doc.toString()); + }, + + "test: toggle comment on commented lines should remove leading '//' chars" : function() { + var doc = new ace.TextDocument(["// abc", "//cde", "fg"].join("\n")); + + var range = { + start: {row: 0, column: 3}, + end: {row: 1, column: 1} + }; + + var comment = this.mode.toggleCommentLines(doc, range); + assertEquals([" abc", "cde", "fg"].join("\n"), doc.toString()); + }, + + "test: toggle comment on multiple lines with one commented line prepend '//' to each line" : function() { + var doc = new ace.TextDocument(["// abc", "//cde", "fg"].join("\n")); + + var range = { + start: {row: 0, column: 3}, + end: {row: 2, column: 1} + }; + + var comment = this.mode.toggleCommentLines(doc, range); + assertEquals(["//// abc", "////cde", "//fg"].join("\n"), doc.toString()); + } +}); \ No newline at end of file diff --git a/test/mode/XmlTest.js b/test/mode/XmlTest.js new file mode 100644 index 00000000..80e6e733 --- /dev/null +++ b/test/mode/XmlTest.js @@ -0,0 +1,27 @@ +var XmlTest = new TestCase("mode.XmlTest", { + + setUp : function() { + this.mode = new ace.mode.Xml(); + }, + + "test: getTokenizer() (smoke test)" : function() { + var tokenizer = this.mode.getTokenizer(); + + assertTrue(tokenizer instanceof ace.Tokenizer); + + var tokens = tokenizer.getLineTokens("", "start").tokens; + assertEquals("keyword", tokens[1].type); + }, + + "test: toggle comment lines should not do anything" : function() { + var doc = new ace.TextDocument([" abc", "cde", "fg"].join("\n")); + + var range = { + start: {row: 0, column: 3}, + end: {row: 1, column: 1} + }; + + var comment = this.mode.toggleCommentLines(doc, range); + assertEquals([" abc", "cde", "fg"].join("\n"), doc.toString()); + } +}); \ No newline at end of file From 960d6abc349e1de0672ed5b246bb837d05768f9a Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 14 Apr 2010 16:04:22 +0200 Subject: [PATCH 064/392] minor fix to xml tokenizer --- src/Tokenizer.js | 2 +- src/mode/XmlHighlightRules.js | 4 ++-- test/mode/XmlTokenizerTest.js | 21 +++++++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 test/mode/XmlTokenizerTest.js diff --git a/src/Tokenizer.js b/src/Tokenizer.js index fb1d539e..fd7d54d9 100644 --- a/src/Tokenizer.js +++ b/src/Tokenizer.js @@ -14,7 +14,7 @@ ace.Tokenizer = function(rules) { ; this.regExps[key] = new RegExp("(?:(" + ruleRegExps.join(")|(") - + ")|(.))", "g"); + + ")|(.+))", "g"); } }; diff --git a/src/mode/XmlHighlightRules.js b/src/mode/XmlHighlightRules.js index 1ec228d2..2bf7f642 100644 --- a/src/mode/XmlHighlightRules.js +++ b/src/mode/XmlHighlightRules.js @@ -19,14 +19,14 @@ ace.mode.XmlHighlightRules = function() { next : "comment" }, { token : "text", // opening tag - regex : "<", + regex : "<\\/?", next : "tag" }, { token : "text", regex : "\\s+" }, { token : "text", - regex : ".+" + regex : "[^<]+" } ], tag : [ { diff --git a/test/mode/XmlTokenizerTest.js b/test/mode/XmlTokenizerTest.js new file mode 100644 index 00000000..23b8801d --- /dev/null +++ b/test/mode/XmlTokenizerTest.js @@ -0,0 +1,21 @@ +var XmlTest = new TestCase("mode.XmlTest", { + + setUp : function() { + this.tokenizer = new ace.mode.Xml().getTokenizer(); + }, + + "test: tokenize1" : function() { + + var line = "//Juhu Kinners"; + var tokens = this.tokenizer.getLineTokens(line, "start").tokens; + + assertEquals(7, tokens.length); + assertEquals("text", tokens[0].type); + assertEquals("keyword", tokens[1].type); + assertEquals("text", tokens[2].type); + assertEquals("text", tokens[3].type); + assertEquals("text", tokens[4].type); + assertEquals("keyword", tokens[5].type); + assertEquals("text", tokens[6].type); + } +}); \ No newline at end of file From 34d851b24e66cddf234aa535e00cad73984e3a8e Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 14 Apr 2010 17:51:28 +0200 Subject: [PATCH 065/392] support moving lines up/down (alt-up/down) --- src/Editor.js | 89 +++++++++++++++++++++++++--------------- src/KeyBinding.js | 14 +++++-- src/TextDocument.js | 26 ++++++++++++ test/TextDocumentTest.js | 32 +++++++++++++++ test/TextEditTest.js | 66 +++++++++++++++++++++++++++++ 5 files changed, 191 insertions(+), 36 deletions(-) diff --git a/src/Editor.js b/src/Editor.js index 6efd21db..c43e326c 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -288,6 +288,35 @@ ace.Editor.prototype.toggleCommentLines = function() { this.shiftSelection(addedColumns); }; +ace.Editor.prototype.moveLinesDown = function() { + this._moveLines(function(firstRow, lastRow) { + return this.doc.moveLinesDown(firstRow, lastRow); + }); +}; + +ace.Editor.prototype.moveLinesUp = function() { + this._moveLines(function(firstRow, lastRow) { + return this.doc.moveLinesUp(firstRow, lastRow); + }); +}; + +ace.Editor.prototype._moveLines = function(mover) { + var range = this.getSelectionRange(); + var firstRow = range.start.row; + var lastRow = range.end.row; + if (range.end.column == 0 && (range.start.row !== range.end.row)) { + lastRow -= 1; + } + + var linesMoved = mover.call(this, firstRow, lastRow); + + this.setSelectionAnchor(lastRow+linesMoved+1, 0); + this._moveSelection(function() { + this.moveCursorTo(firstRow+linesMoved, 0); + }); +}; + + ace.Editor.prototype.onCompositionStart = function() { this.renderer.showComposition(this.cursor); this.onTextInput(" "); @@ -532,21 +561,27 @@ ace.Editor.prototype.moveCursorToPosition = function(position) { this.moveCursorTo(position.row, position.column); }; -ace.Editor.prototype.moveCursorTo = function(row, column) { +ace.Editor.prototype._clipPositionToDocument = function(row, column) { + var pos = {}; + if (row >= this.doc.getLength()) { - this.cursor.row = this.doc.getLength() - 1; - this.cursor.column = this.doc.getLine(this.cursor.row).length; + pos.row = this.doc.getLength() - 1; + pos.column = this.doc.getLine(pos.row).length; } else if (row < 0) { - this.cursor.row = 0; - this.cursor.column = 0; + pos.row = 0; + pos.column = 0; } else { - this.cursor.row = row; - this.cursor.column = Math - .min(this.doc.getLine(this.cursor.row).length, Math - .max(0, column)); + pos.row = row; + pos.column = Math.min(this.doc.getLine(pos.row).length, + Math.max(0, column)); } + return pos; +}; + +ace.Editor.prototype.moveCursorTo = function(row, column) { + this.cursor = this._clipPositionToDocument(row, column); this.updateCursor(); }; @@ -580,12 +615,7 @@ ace.Editor.prototype.hasMultiLineSelection = function() { ace.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)) - }; - + this.selectionAnchor = this._clipPositionToDocument(row, column); this.selectionLead = null; }; @@ -624,26 +654,21 @@ ace.Editor.prototype.shiftSelection = function(columns) { }; ace.Editor.prototype.getSelectionRange = function() { - var anchor = this.selectionAnchor; - var lead = this.selectionLead; + var anchor = this.selectionAnchor || this.cursor; + var lead = this.selectionLead || this.cursor; - if (!anchor) { - return null; + if (anchor.row > lead.row + || (anchor.row == lead.row && anchor.column > lead.column)) { + return { + start : lead, + end : anchor + }; } else { - if (anchor.row > lead.row - || (anchor.row == lead.row && anchor.column > lead.column)) { - return { - start : lead, - end : anchor - }; - } - else { - return { - start : anchor, - end : lead - }; - } + return { + start : anchor, + end : lead + }; } }; diff --git a/src/KeyBinding.js b/src/KeyBinding.js index f53dfddd..7d1a5262 100644 --- a/src/KeyBinding.js +++ b/src/KeyBinding.js @@ -57,13 +57,16 @@ ace.KeyBinding = function(element, host) { break; case keys.UP: - if (e.metaKey && e.shiftKey) { + if (e.altKey) { + host.moveLinesUp(); + } + else if (e.metaKey && e.shiftKey) { host.selectFileStart(); } else if (e.metaKey) { host.navigateFileStart(); } - if (e.shiftKey) { + else if (e.shiftKey) { host.selectUp(); } else { @@ -72,13 +75,16 @@ ace.KeyBinding = function(element, host) { return ace.stopEvent(e); case keys.DOWN: - if (e.metaKey && e.shiftKey) { + if (e.altKey) { + host.moveLinesDown(); + } + else if (e.metaKey && e.shiftKey) { host.selectFileEnd(); } else if (e.metaKey) { host.navigateFileEnd(); } - if (e.shiftKey) { + else if (e.shiftKey) { host.selectDown(); } else { diff --git a/src/TextDocument.js b/src/TextDocument.js index 5934b835..20349760 100644 --- a/src/TextDocument.js +++ b/src/TextDocument.js @@ -301,4 +301,30 @@ ace.TextDocument.prototype.outdentRows = function(range, indentString) { this.fireChangeEvent(range.start.row, range.end.row); return -outdentLength; +}; + +ace.TextDocument.prototype.moveLinesUp = function(firstRow, lastRow) { + if (firstRow <= 0) return 0; + + var removed = this.lines.splice(firstRow, lastRow-firstRow+1); + + var args = [firstRow - 1, 0]; + args.push.apply(args, removed); + this.lines.splice.apply(this.lines, args); + + this.fireChangeEvent(firstRow-1, lastRow); + return -1; +}; + +ace.TextDocument.prototype.moveLinesDown = function(firstRow, lastRow) { + if (lastRow >= this.lines.length-1) return 0; + + var removed = this.lines.splice(firstRow, lastRow-firstRow+1); + + var args = [firstRow + 1, 0]; + args.push.apply(args, removed); + this.lines.splice.apply(this.lines, args); + + this.fireChangeEvent(firstRow, lastRow+1); + return 1; }; \ No newline at end of file diff --git a/test/TextDocumentTest.js b/test/TextDocumentTest.js index 39f6cbaa..6a169841 100644 --- a/test/TextDocumentTest.js +++ b/test/TextDocumentTest.js @@ -31,5 +31,37 @@ var TextDocumentTest = new TestCase("TextDocumentTest", { assertPosition(0, 0, doc.findMatchingBracket({row: 1, column: 1})); assertPosition(0, 2, doc.findMatchingBracket({row: 1, column: 2})); assertPosition(0, 1, doc.findMatchingBracket({row: 1, column: 3})); + }, + + "test: move lines down" : function() { + var doc = new ace.TextDocument(["1", "2", "3", "4"].join("\n")); + + doc.moveLinesDown(0, 1); + assertEquals(["3", "1", "2", "4"].join("\n"), doc.toString()); + + doc.moveLinesDown(1, 2); + assertEquals(["3", "4", "1", "2"].join("\n"), doc.toString()); + + doc.moveLinesDown(2, 3); + assertEquals(["3", "4", "1", "2"].join("\n"), doc.toString()); + + doc.moveLinesDown(2, 2); + assertEquals(["3", "4", "2", "1"].join("\n"), doc.toString()); + }, + + "test: move lines up" : function() { + var doc = new ace.TextDocument(["1", "2", "3", "4"].join("\n")); + + doc.moveLinesUp(2, 3); + assertEquals(["1", "3", "4", "2"].join("\n"), doc.toString()); + + doc.moveLinesUp(1, 2); + assertEquals(["3", "4", "1", "2"].join("\n"), doc.toString()); + + doc.moveLinesUp(0, 1); + assertEquals(["3", "4", "1", "2"].join("\n"), doc.toString()); + + doc.moveLinesUp(2, 2); + assertEquals(["3", "1", "4", "2"].join("\n"), doc.toString()); } }); \ No newline at end of file diff --git a/test/TextEditTest.js b/test/TextEditTest.js index d2ab4796..50fbb046 100644 --- a/test/TextEditTest.js +++ b/test/TextEditTest.js @@ -107,5 +107,71 @@ var TextEditTest = TestCase("TextEditTest", var selection = editor.getSelectionRange(); assertPosition(0, 0, selection.start); assertPosition(1, 1, selection.end); + }, + + "test: move lines down should select moved lines" : function() { + var doc = new ace.TextDocument(["11", "22", "33", "44"].join("\n")); + var editor = new ace.Editor(new MockRenderer(), doc); + + editor.moveCursorTo(0, 1); + editor.selectDown(); + + editor.moveLinesDown(); + assertEquals(["33", "11", "22", "44"].join("\n"), doc.toString()); + assertPosition(1, 0, editor.getCursorPosition()); + assertPosition(3, 0, editor.getSelectionAnchor()); + assertPosition(1, 0, editor.getSelectionLead()); + + editor.moveLinesDown(); + assertEquals(["33", "44", "11", "22"].join("\n"), doc.toString()); + assertPosition(2, 0, editor.getCursorPosition()); + assertPosition(3, 2, editor.getSelectionAnchor()); + assertPosition(2, 0, editor.getSelectionLead()); + + // moving again should have no effect + editor.moveLinesDown(); + assertEquals(["33", "44", "11", "22"].join("\n"), doc.toString()); + assertPosition(2, 0, editor.getCursorPosition()); + assertPosition(3, 2, editor.getSelectionAnchor()); + assertPosition(2, 0, editor.getSelectionLead()); + }, + + "test: move lines up should select moved lines" : function() { + var doc = new ace.TextDocument(["11", "22", "33", "44"].join("\n")); + var editor = new ace.Editor(new MockRenderer(), doc); + + editor.moveCursorTo(2, 1); + editor.selectDown(); + + editor.moveLinesUp(); + assertEquals(["11", "33", "44", "22"].join("\n"), doc.toString()); + assertPosition(1, 0, editor.getCursorPosition()); + assertPosition(3, 0, editor.getSelectionAnchor()); + assertPosition(1, 0, editor.getSelectionLead()); + + editor.moveLinesUp(); + assertEquals(["33", "44", "11", "22"].join("\n"), doc.toString()); + assertPosition(0, 0, editor.getCursorPosition()); + assertPosition(2, 0, editor.getSelectionAnchor()); + assertPosition(0, 0, editor.getSelectionLead()); + }, + + "test: move line without active selection should move cursor to start of the moved line" : function() + { + var doc = new ace.TextDocument(["11", "22", "33", "44"].join("\n")); + var editor = new ace.Editor(new MockRenderer(), doc); + + editor.moveCursorTo(1, 1); + editor.clearSelection(); + + editor.moveLinesDown(); + assertEquals(["11", "33", "22", "44"].join("\n"), doc.toString()); + assertPosition(2, 0, editor.getCursorPosition()); + + editor.clearSelection(); + + editor.moveLinesUp(); + assertEquals(["11", "22", "33", "44"].join("\n"), doc.toString()); + assertPosition(1, 0, editor.getCursorPosition()); } }); \ No newline at end of file From e9fe18f65767c1ffc64a252a4b94f3daf1e579be Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 14 Apr 2010 19:15:52 +0200 Subject: [PATCH 066/392] first support for auto indent --- src/BackgroundTokenizer.js | 16 ++++++--- src/Editor.js | 52 ++++++++++++++++++++++------ src/KeyBinding.js | 12 +++---- src/mode/JavaScript.js | 21 +++++++++++ src/mode/JavaScriptHighlightRules.js | 4 +-- src/mode/Text.js | 4 +++ test/TextEditTest.js | 15 ++++++++ test/mode/JavaScriptTest.js | 30 ++++++++++++++++ 8 files changed, 129 insertions(+), 25 deletions(-) diff --git a/src/BackgroundTokenizer.js b/src/BackgroundTokenizer.js index 161b8b5d..f2481584 100644 --- a/src/BackgroundTokenizer.js +++ b/src/BackgroundTokenizer.js @@ -77,14 +77,20 @@ ace.BackgroundTokenizer.prototype.stop = function() { }; ace.BackgroundTokenizer.prototype.getTokens = function(row) { - if (this.lines[row]) { - return this.lines[row].tokens; - } - else { + return this._tokenizeRow(row).tokens; +}; + +ace.BackgroundTokenizer.prototype.getState = function(row) { + return this._tokenizeRow(row).state; +}; + +ace.BackgroundTokenizer.prototype._tokenizeRow = function(row) { + if (!this.lines[row]) { var state = "start"; if (row > 0 && this.lines[row - 1]) { state = this.lines[row - 1].state; } - return this.tokenizer.getLineTokens(this.textLines[row] || "", state).tokens; + this.lines[row] = this.tokenizer.getLineTokens(this.textLines[row] || "", state); } + return this.lines[row]; }; \ No newline at end of file diff --git a/src/Editor.js b/src/Editor.js index c43e326c..e999c5fc 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -221,15 +221,40 @@ ace.Editor.prototype.onCut = function() { ace.Editor.prototype.onTextInput = function(text) { if (this.hasSelection()) { - this.moveCursorToPosition(this.doc.replace(this.getSelectionRange(), text)); + var end = this.doc.replace(this.getSelectionRange(), text); this.clearSelection(); } else { - this.moveCursorToPosition(this.doc.insert(this.cursor, text)); + var end = this.doc.insert(this.cursor, text); } + + // multi line insert + var row = this.cursor.row; + if (row !== end.row) { + var line = this.doc.getLine(row); + var lineState = this.bgTokenizer.getState(row); + var indent = this.mode.getNextLineIndent(line, lineState, this.getTabString()); + if (indent) { + var indentRange = { + start: { + row: row+1, + column: 0 + }, + end : end + }; + end.column += this.doc.indentRows(indentRange, indent); + } + } + + this.moveCursorToPosition(end); this.renderer.scrollCursorIntoView(); }; + +ace.Editor.prototype.getTabString = function() { + return " "; +}; + ace.Editor.prototype.removeRight = function() { if (!this.hasSelection()) { this.selectRight(); @@ -262,18 +287,14 @@ ace.Editor.prototype.removeLine = function() { }; ace.Editor.prototype.blockIndent = function(indentString) { - if (!this.hasSelection()) return; - - var indentString = indentString || " "; + var indentString = indentString || this.getTabString(); var addedColumns = this.doc.indentRows(this.getSelectionRange(), indentString); this.shiftSelection(addedColumns); }; ace.Editor.prototype.blockOutdent = function(indentString) { - if (!this.hasSelection()) return; - - var indentString = indentString || " "; + var indentString = indentString || this.getTabString(); var addedColumns = this.doc.outdentRows(this.getSelectionRange(), indentString); this.shiftSelection(addedColumns); @@ -626,7 +647,10 @@ ace.Editor.prototype.getSelectionAnchor = function() { column: this.selectionAnchor.column }; } else { - return null; + return { + row: this.cursor.row, + column: this.cursor.column + }; } }; @@ -637,12 +661,18 @@ ace.Editor.prototype.getSelectionLead = function() { column: this.selectionLead.column }; } else { - return null; + return { + row: this.cursor.row, + column: this.cursor.column + }; } }; ace.Editor.prototype.shiftSelection = function(columns) { - if (!this.hasSelection()) return; + if (!this.hasSelection()) { + this.moveCursorTo(this.cursor.row, this.cursor.column + columns); + return; + }; var anchor = this.getSelectionAnchor(); var lead = this.getSelectionLead(); diff --git a/src/KeyBinding.js b/src/KeyBinding.js index 7d1a5262..fdd692d0 100644 --- a/src/KeyBinding.js +++ b/src/KeyBinding.js @@ -179,14 +179,12 @@ ace.KeyBinding = function(element, host) { return ace.stopEvent(e); case keys.TAB: - if (host.hasMultiLineSelection()) { - if (e.shiftKey) { - host.blockOutdent(); - } else { - host.blockIndent(); - } + if (e.shiftKey) { + host.blockOutdent(); + } else if (host.hasMultiLineSelection()) { + host.blockIndent(); } else { - host.onTextInput(" "); + host.onTextInput(host.getTabString()); } return ace.stopEvent(e); } diff --git a/src/mode/JavaScript.js b/src/mode/JavaScript.js index 4dbab043..65800a53 100644 --- a/src/mode/JavaScript.js +++ b/src/mode/JavaScript.js @@ -11,4 +11,25 @@ ace.mode.JavaScript.prototype.toggleCommentLines = function(doc, range) { var addedRows = doc.indentRows(range, "//"); }; return addedRows; +}; + +ace.mode.JavaScript.prototype.increaseIndentPatterns = { + "start" : /^(\s*).*[\{\(\[]\s*$/ +}; + +ace.mode.JavaScript.prototype.getNextLineIndent = function(line, state, tab) { + var re = this.increaseIndentPatterns[state]; + if (!re) return + + var match = line.match(re); + if (match) { + return (match[1] || "") + tab; + } + + var match = line.match(/^(\s+).*$/); + if (match) { + return match[1]; + } + + return ""; }; \ No newline at end of file diff --git a/src/mode/JavaScriptHighlightRules.js b/src/mode/JavaScriptHighlightRules.js index 6c366559..767a436c 100644 --- a/src/mode/JavaScriptHighlightRules.js +++ b/src/mode/JavaScriptHighlightRules.js @@ -76,8 +76,8 @@ ace.mode.JavaScriptHighlightRules = function() { token : function(value) { // return parens[value]; return "text"; - }, - regex : "[\\[\\]\\(\\)\\{\\}]" + }, + regex : "[\\[\\]\\(\\)\\{\\}]" }, { token : "text", regex : "\\s+" diff --git a/src/mode/Text.js b/src/mode/Text.js index 5b35cfbc..d60719a2 100644 --- a/src/mode/Text.js +++ b/src/mode/Text.js @@ -16,4 +16,8 @@ ace.mode.Text.prototype.getTokenizer = function() { ace.mode.Text.prototype.toggleCommentLines = function(doc, range) { return 0; +}; + +ace.mode.Text.prototype.getNextLineIndent = function(line, state, tab) { + return ""; }; \ No newline at end of file diff --git a/test/TextEditTest.js b/test/TextEditTest.js index 50fbb046..35aa7a8a 100644 --- a/test/TextEditTest.js +++ b/test/TextEditTest.js @@ -44,6 +44,8 @@ var TextEditTest = TestCase("TextEditTest", assertEquals(["a12345", " b12345", " c12345"].join("\n"), doc.toString()); + assertPosition(2, 7, editor.getCursorPosition()); + var selection = editor.getSelectionRange(); assertPosition(1, 7, selection.start); assertPosition(2, 7, selection.end); @@ -61,6 +63,8 @@ var TextEditTest = TestCase("TextEditTest", assertEquals([" a12345", "b12345", " c12345"].join("\n"), doc.toString()); + assertPosition(2, 1, editor.getCursorPosition()); + var selection = editor.getSelectionRange(); assertPosition(0, 1, selection.start); assertPosition(2, 1, selection.end); @@ -75,6 +79,17 @@ var TextEditTest = TestCase("TextEditTest", assertPosition(2, 1, selection.end); }, + "test: outent without a selection should update cursor" : function() { + var doc = new ace.TextDocument(" 12"); + var editor = new ace.Editor(new MockRenderer(), doc); + + editor.moveCursorTo(0, 3); + editor.blockOutdent(" "); + + assertEquals(" 12", doc.toString()); + assertPosition(0, 1, editor.getCursorPosition()); + }, + "test: comment lines should perserve selection" : function() { var doc = new ace.TextDocument([" abc", "cde"].join("\n")); var editor = new ace.Editor(new MockRenderer(), doc, new ace.mode.JavaScript()); diff --git a/test/mode/JavaScriptTest.js b/test/mode/JavaScriptTest.js index 2a0ce050..3e6183d4 100644 --- a/test/mode/JavaScriptTest.js +++ b/test/mode/JavaScriptTest.js @@ -47,5 +47,35 @@ var JavaScriptTest = new TestCase("mode.JavaScriptTest", { var comment = this.mode.toggleCommentLines(doc, range); assertEquals(["//// abc", "////cde", "//fg"].join("\n"), doc.toString()); + }, + + "test: auto indent after opening brace" : function() { + var doc = new ace.TextDocument(["if () {"].join("\n")); + var editor = new ace.Editor(new MockRenderer(), doc, this.mode); + + editor.navigateLineEnd(); + editor.onTextInput("\n"); + + assertEquals(["if () {", editor.getTabString()].join("\n"), doc.toString()); + }, + + "test: no auto indent after opening brace in multi line comment" : function() { + var doc = new ace.TextDocument(["/*if () {"].join("\n")); + var editor = new ace.Editor(new MockRenderer(), doc, this.mode); + + editor.navigateLineEnd(); + editor.onTextInput("\n"); + + assertEquals(["/*if () {", ""].join("\n"), doc.toString()); + }, + + "test: no auto indent should add to existing indent" : function() { + var doc = new ace.TextDocument([" if () {"].join("\n")); + var editor = new ace.Editor(new MockRenderer(), doc, this.mode); + + editor.navigateLineEnd(); + editor.onTextInput("\n"); + + assertEquals([" if () {", " " + editor.getTabString()].join("\n"), doc.toString()); } }); \ No newline at end of file From 58d2b6917f0d670634b8052f6e7bf82aee5427ff Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 15 Apr 2010 10:34:04 +0200 Subject: [PATCH 067/392] experimental file drag and drop support for FF --- demo/editor.html | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/demo/editor.html b/demo/editor.html index 385a2b56..0fce1956 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -96,7 +96,41 @@ window.onresize = function() { editor.resize(); }; +ace.addListener(container, "dragover", function(e) { + return ace.preventDefault(e); +}); +ace.addListener(container, "drop", function(e) { + try { + var file = e.dataTransfer.files[0]; + } catch(e) { + return ace.stopEvent(); + } + + if (window.FileReader) { + var reader = new FileReader(); + reader.onload = function(e) { + editor.clearSelection(); + editor.moveCursorTo(0, 0); + editor.selectFileEnd(); + + var mode = "text"; + if (/^.*\.js$/i.test(file.name)) { + mode = "javascript"; + } else if (/^.*\.xml$/i.test(file.name)) { + mode = "xml"; + } + + editor.onTextInput(reader.result); + + modeEl.value = mode; + editor.setMode(modes[mode]); + } + reader.readAsText(file); + } + + return ace.preventDefault(e); +}); From b7e69d5da56f83aacbd54afb20f0501530d72ae2 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 15 Apr 2010 10:34:23 +0200 Subject: [PATCH 068/392] minor fixes --- src/Editor.js | 1 + src/Tokenizer.js | 15 ++++++--------- src/mode/JavaScript.js | 9 +++++---- test/mode/JavaScriptTokenizerTest.js | 20 ++++++++++++++++++++ 4 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 test/mode/JavaScriptTokenizerTest.js diff --git a/src/Editor.js b/src/Editor.js index e999c5fc..30d92c25 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -44,6 +44,7 @@ ace.Editor.prototype.setDocument = function(doc) { }; ace.Editor.prototype.setMode = function(mode) { + if (this.mode == mode) return; this.mode = mode; var tokenizer = mode.getTokenizer(); diff --git a/src/Tokenizer.js b/src/Tokenizer.js index fd7d54d9..fb50fc74 100644 --- a/src/Tokenizer.js +++ b/src/Tokenizer.js @@ -10,11 +10,10 @@ ace.Tokenizer = function(rules) { for ( var i = 0; i < state.length; i++) { ruleRegExps.push(state[i].regex); - } - ; + }; this.regExps[key] = new RegExp("(?:(" + ruleRegExps.join(")|(") - + ")|(.+))", "g"); + + ")|(.))", "g"); } }; @@ -37,7 +36,7 @@ ace.Tokenizer.prototype.getLineTokens = function(line, startState) { if (re.lastIndex == lastIndex) { throw new Error("tokenizer error"); } lastIndex = re.lastIndex; - // console.log(match); +// window.LOG && console.log(match); for ( var i = 0; i < state.length; i++) { if (match[i + 1]) { @@ -58,14 +57,12 @@ ace.Tokenizer.prototype.getLineTokens = function(line, startState) { } break; } - } - ; + }; tokens.push(token); - } - ; + }; - // console.log(tokens, currentState) +// window.LOG && console.log(tokens, currentState); return { tokens : tokens, diff --git a/src/mode/JavaScript.js b/src/mode/JavaScript.js index 65800a53..c4f25fd6 100644 --- a/src/mode/JavaScript.js +++ b/src/mode/JavaScript.js @@ -19,11 +19,12 @@ ace.mode.JavaScript.prototype.increaseIndentPatterns = { ace.mode.JavaScript.prototype.getNextLineIndent = function(line, state, tab) { var re = this.increaseIndentPatterns[state]; - if (!re) return - var match = line.match(re); - if (match) { - return (match[1] || "") + tab; + if (re) { + var match = line.match(re); + if (match) { + return (match[1] || "") + tab; + } } var match = line.match(/^(\s+).*$/); diff --git a/test/mode/JavaScriptTokenizerTest.js b/test/mode/JavaScriptTokenizerTest.js new file mode 100644 index 00000000..0e5e019e --- /dev/null +++ b/test/mode/JavaScriptTokenizerTest.js @@ -0,0 +1,20 @@ +var JavaScriptTokenizerTest = new TestCase("mode.JavaScriptTokenizerTest", { + + setUp : function() { + this.tokenizer = new ace.mode.JavaScript().getTokenizer(); + }, + + "test: tokenize1" : function() { + var line = "foo = function"; + + var tokens = this.tokenizer.getLineTokens(line, "start").tokens; + + assertEquals(5, tokens.length); + assertEquals("identifier", tokens[0].type); + assertEquals("text", tokens[1].type); + assertEquals("text", tokens[2].type); + assertEquals("text", tokens[3].type); + assertEquals("keyword", tokens[4].type); + } + +}); \ No newline at end of file From 7529f90080fe6930d0ae7f51db19be1fcbe3ef3d Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 15 Apr 2010 13:58:49 +0200 Subject: [PATCH 069/392] refactor selection support into a separate class Merge branch 'master' into HEAD Conflicts: src/Editor.js src/KeyBinding.js src/Selection.js --- demo/editor.html | 2 + src/Editor.js | 598 +++++++++++---------------------------- src/KeyBinding.js | 83 +++--- src/MEventEmitter.js | 27 ++ src/Selection.js | 359 +++++++++++++++++++++++ src/TextDocument.js | 5 + src/TextLayer.js | 6 +- src/ace.js | 22 ++ test/EventEmitterTest.js | 20 ++ test/NavigationTest.js | 187 +----------- test/SelectionTest.js | 177 ++++++++++++ test/TextEditTest.js | 81 ++++-- 12 files changed, 887 insertions(+), 680 deletions(-) create mode 100644 src/MEventEmitter.js create mode 100644 src/Selection.js create mode 100644 test/EventEmitterTest.js create mode 100644 test/SelectionTest.js diff --git a/demo/editor.html b/demo/editor.html index 0fce1956..fee837f5 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -38,6 +38,8 @@ + + diff --git a/src/Editor.js b/src/Editor.js index 30d92c25..6779f8a3 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -15,19 +15,15 @@ ace.Editor = function(renderer, doc, mode) { ace.addListener(container, "dblclick", ace .bind(this.onMouseDoubleClick, this)); ace.addMouseWheelListener(container, ace.bind(this.onMouseWheel, this)); - ace.addTripleClickListener(container, ace.bind(this.selectLine, - this)); + ace.addTripleClickListener(container, ace.bind(this.selection.selectLine, + this.selection)); - this.cursor = { - row : 0, - column : 0 - }; - - this.selectionAnchor = null; - this.selectionLead = null; - this.selection = null; + this.selectionMarker = null; + this._blockScrolling = false; this.renderer.draw(); + this.onCursorChange(); + this.onSelectionChange(); }; ace.Editor.prototype.setDocument = function(doc) { @@ -37,12 +33,25 @@ ace.Editor.prototype.setDocument = function(doc) { } this.doc = doc; + doc.addChangeListener(ace.bind(this.onDocumentChange, this)); this.renderer.setDocument(doc); + this.selection = doc.getSelection(); + + var onCursorChange = ace.bind(this.onCursorChange, this); + this.selection.addEventListener("changeCursor", onCursorChange); + + var onSelectionChange = ace.bind(this.onSelectionChange, this); + this.selection.addEventListener("changeSelection", onSelectionChange); + this.bgTokenizer.setLines(this.doc.lines); }; +ace.Editor.prototype.getSelection = function() { + return this.selection; +}; + ace.Editor.prototype.setMode = function(mode) { if (this.mode == mode) return; @@ -59,17 +68,13 @@ ace.Editor.prototype.setMode = function(mode) { this.renderer.setTokenizer(this.bgTokenizer); }; + ace.Editor.prototype.resize = function() { this.renderer.scrollToY(this.renderer.getScrollTop()); this.renderer.draw(); }; -ace.Editor.prototype.updateCursor = function() { - this.renderer.updateCursor(this.cursor); - this._highlightBrackets(); -}; - ace.Editor.prototype._highlightBrackets = function() { if (this._bracketHighlight) { @@ -87,7 +92,7 @@ ace.Editor.prototype._highlightBrackets = function() { setTimeout(function() { self._highlightPending = false; - var pos = self.doc.findMatchingBracket(self.cursor); + var pos = self.doc.findMatchingBracket(self.getCursorPosition()); if (pos) { range = { start: pos, @@ -121,12 +126,35 @@ ace.Editor.prototype.onTokenizerUpdate = function(startRow, endRow) { this.renderer.updateLines(startRow, endRow); }; +ace.Editor.prototype.onCursorChange = function() { + this._highlightBrackets(); + this.renderer.updateCursor(this.getCursorPosition()); + + if (!this._blockScrolling) { + this.renderer.scrollCursorIntoView(); + } +}; + +ace.Editor.prototype.onSelectionChange = function() { + if (this.selectionMarker) { + this.renderer.removeMarker(this.selectionMarker); + } + this.selectionMarker = null; + + if (!this.selection.isEmpty()) { + var range = this.selection.getRange(); + this.selectionMarker = this.renderer.addMarker(range, "selection", "text"); + } + + this.onCursorChange(); +}; + ace.Editor.prototype.onMouseDown = function(e) { this.textInput.focus(); var pos = this.renderer.screenToTextCoordinates(e.pageX, e.pageY); this.moveCursorToPosition(pos); - this.setSelectionAnchor(pos.row, pos.column); + this.selection.setSelectionAnchor(pos.row, pos.column); this.renderer.scrollCursorIntoView(); var _self = this; @@ -148,7 +176,7 @@ ace.Editor.prototype.onMouseDown = function(e) { selectionLead = _self.renderer.screenToTextCoordinates(mousePageX, mousePageY); - _self._moveSelection(function() { + _self.selection._moveSelection(function() { _self.moveCursorToPosition(selectionLead); }); _self.renderer.scrollCursorIntoView(); @@ -164,8 +192,10 @@ ace.Editor.prototype.tokenRe = /^[\w\d]+/g; ace.Editor.prototype.nonTokenRe = /^[^\w\d]+/g; ace.Editor.prototype.onMouseDoubleClick = function(e) { - var line = this.doc.getLine(this.cursor.row); - var column = this.cursor.column; + var cursor = this.selection.getCursor(); + + var line = this.doc.getLine(cursor.row); + var column = cursor.column; var inToken = false; if (column > 0) { @@ -192,9 +222,10 @@ ace.Editor.prototype.onMouseDoubleClick = function(e) { end++; } - this.setSelectionAnchor(this.cursor.row, start); - this._moveSelection(function() { - this.moveCursorTo(this.cursor.row, end); + var selection = this.selection; + selection.setSelectionAnchor(cursor.row, start); + selection._moveSelection(function() { + selection.moveCursorTo(cursor.row, end); }); }; @@ -205,7 +236,7 @@ ace.Editor.prototype.onMouseWheel = function(e) { }; ace.Editor.prototype.getCopyText = function() { - if (this.hasSelection()) { + if (!this.selection.isEmpty()) { return this.doc.getTextRange(this.getSelectionRange()); } else { @@ -214,23 +245,29 @@ ace.Editor.prototype.getCopyText = function() { }; ace.Editor.prototype.onCut = function() { - if (this.hasSelection()) { + if (!this.selection.isEmpty()) { this.moveCursorToPosition(this.doc.remove(this.getSelectionRange())); this.clearSelection(); } }; ace.Editor.prototype.onTextInput = function(text) { - if (this.hasSelection()) { + var cursor = this.getCursorPosition(); + + if (this.getUseSoftTabs()) { + text = text.replace(/\t/g, this.getTabString()); + } + + if (!this.selection.isEmpty()) { var end = this.doc.replace(this.getSelectionRange(), text); this.clearSelection(); } else { - var end = this.doc.insert(this.cursor, text); + var end = this.doc.insert(cursor, text); } // multi line insert - var row = this.cursor.row; + var row = cursor.row; if (row !== end.row) { var line = this.doc.getLine(row); var lineState = this.bgTokenizer.getState(row); @@ -253,37 +290,59 @@ ace.Editor.prototype.onTextInput = function(text) { ace.Editor.prototype.getTabString = function() { - return " "; + if (this.getUseSoftTabs()) { + return new Array(this.getTabSize()+1).join(" "); + } + return "\t"; +}; + +ace.Editor.prototype._useSoftTabs = true; +ace.Editor.prototype.setUseSoftTabs = function(useSoftTabs) { + if (this._useSoftTabs === useSoftTabs) return; + + this._useSoftTabs = useSoftTabs; +}; + +ace.Editor.prototype.getUseSoftTabs = function() { + return this._useSoftTabs; +}; + +ace.Editor.prototype._tabSize = 4; +ace.Editor.prototype.setTabSize = function(tabSize) { + if (this._tabSize === tabSize) return; + + this._tabSize = tabSize; + this.renderer.draw(); +}; + +ace.Editor.prototype.getTabSize = function() { + return this._tabSize; }; ace.Editor.prototype.removeRight = function() { - if (!this.hasSelection()) { - this.selectRight(); + if (this.selection.isEmpty()) { + this.selection.selectRight(); } this.moveCursorToPosition(this.doc.remove(this.getSelectionRange())); this.clearSelection(); - - this.renderer.scrollCursorIntoView(); }; ace.Editor.prototype.removeLeft = function() { - if (!this.hasSelection()) { - this.selectLeft(); + if (this.selection.isEmpty()) { + this.selection.selectLeft(); } this.moveCursorToPosition(this.doc.remove(this.getSelectionRange())); this.clearSelection(); - - this.renderer.scrollCursorIntoView(); -}, +}; ace.Editor.prototype.removeLine = function() { - this.selectLine(); + this.selection.selectLine(); this.moveCursorToPosition(this.doc.remove(this.getSelectionRange())); this.clearSelection(); - if (this.cursor.row == this.doc.getLength() - 1) { + if (this.getCursorPosition().row == this.doc.getLength() - 1) { this.removeLeft(); - this.moveCursorLineStart(); + this.selection.moveCursorLineStart(); } }; @@ -291,23 +350,23 @@ ace.Editor.prototype.blockIndent = function(indentString) { var indentString = indentString || this.getTabString(); var addedColumns = this.doc.indentRows(this.getSelectionRange(), indentString); - this.shiftSelection(addedColumns); + this.selection.shiftSelection(addedColumns); }; ace.Editor.prototype.blockOutdent = function(indentString) { var indentString = indentString || this.getTabString(); var addedColumns = this.doc.outdentRows(this.getSelectionRange(), indentString); - this.shiftSelection(addedColumns); + this.selection.shiftSelection(addedColumns); }; ace.Editor.prototype.toggleCommentLines = function() { - if (!this.hasSelection()) return; + if (this.selection.isEmpty()) return; - var selection = this.getSelectionRange(); - var addedColumns = this.mode.toggleCommentLines(this.doc, selection); + var range = this.getSelectionRange(); + var addedColumns = this.mode.toggleCommentLines(this.doc, range); - this.shiftSelection(addedColumns); + this.selection.shiftSelection(addedColumns); }; ace.Editor.prototype.moveLinesDown = function() { @@ -332,15 +391,16 @@ ace.Editor.prototype._moveLines = function(mover) { var linesMoved = mover.call(this, firstRow, lastRow); - this.setSelectionAnchor(lastRow+linesMoved+1, 0); - this._moveSelection(function() { - this.moveCursorTo(firstRow+linesMoved, 0); + var selection = this.selection; + selection.setSelectionAnchor(lastRow+linesMoved+1, 0); + selection._moveSelection(function() { + selection.moveCursorTo(firstRow+linesMoved, 0); }); }; ace.Editor.prototype.onCompositionStart = function() { - this.renderer.showComposition(this.cursor); + this.renderer.showComposition(this.getCursorPosition()); this.onTextInput(" "); }; @@ -353,6 +413,7 @@ ace.Editor.prototype.onCompositionEnd = function() { this.removeLeft(); }; + ace.Editor.prototype.getFirstVisibleRow = function() { return this.renderer.getFirstVisibleRow(); }; @@ -380,6 +441,7 @@ ace.Editor.prototype.getPageUpRow = function() { return firstRow - (lastRow - firstRow) + 1; }; + ace.Editor.prototype.scrollPageDown = function() { this.scrollToRow(this.getPageDownRow()); }; @@ -392,423 +454,101 @@ ace.Editor.prototype.scrollToRow = function(row) { this.renderer.scrollToRow(row); }; -ace.Editor.prototype.navigateTo = function(row, column) { - this.clearSelection(); - this.moveCursorTo(row, column); - this.renderer.scrollCursorIntoView(); + +ace.Editor.prototype.getCursorPosition = function() { + return this.selection.getCursor(); }; -ace.Editor.prototype.navigateUp = function() { - this.clearSelection(); - this.moveCursorUp(); - this.renderer.scrollCursorIntoView(); +ace.Editor.prototype.getSelectionRange = function() { + return this.selection.getRange(); }; -ace.Editor.prototype.navigateDown = function() { - this.clearSelection(); - this.moveCursorDown(); - this.renderer.scrollCursorIntoView(); -}; - -ace.Editor.prototype.navigateLeft = function() { - if (this.hasSelection()) { - var selectionStart = this.getSelectionRange().start; - this.moveCursorToPosition(selectionStart); - } - else { - this.moveCursorLeft(); - } - this.clearSelection(); - - this.renderer.scrollCursorIntoView(); -}; - -ace.Editor.prototype.navigateRight = function() { - if (this.hasSelection()) { - var selectionEnd = this.getSelectionRange().end; - this.moveCursorToPosition(selectionEnd); - } - else { - this.moveCursorRight(); - } - this.clearSelection(); - - this.renderer.scrollCursorIntoView(); -}, - -ace.Editor.prototype.navigateLineStart = function() { - this.clearSelection(); - this.moveCursorLineStart(); - this.renderer.scrollCursorIntoView(); -}; - -ace.Editor.prototype.navigateLineEnd = function() { - this.clearSelection(); - this.moveCursorLineEnd(); - this.renderer.scrollCursorIntoView(); -}; - -ace.Editor.prototype.navigateFileEnd = function() { - this.clearSelection(); - this.moveCursorFileEnd(); - this.renderer.scrollCursorIntoView(); -}, - -ace.Editor.prototype.navigateFileStart = function() { - this.clearSelection(); - this.moveCursorFileStart(); - this.renderer.scrollCursorIntoView(); -}, - -ace.Editor.prototype.navigateWordRight = function() { - this.clearSelection(); - this.moveCursorWordRight(); - this.renderer.scrollCursorIntoView(); -}, - -ace.Editor.prototype.navigateWordLeft = function() { - this.clearSelection(); - this.moveCursorWordLeft(); - this.renderer.scrollCursorIntoView(); -}, - -ace.Editor.prototype.moveCursorUp = function() { - this.moveCursorBy(-1, 0); -}; - -ace.Editor.prototype.moveCursorDown = function() { - this.moveCursorBy(1, 0); -}; - -ace.Editor.prototype.moveCursorLeft = function() { - if (this.cursor.column == 0) { - if (this.cursor.row > 0) { - this.moveCursorTo(this.cursor.row - 1, this.doc - .getLine(this.cursor.row - 1).length); - } - } - else { - this.moveCursorBy(0, -1); - } -}; - -ace.Editor.prototype.moveCursorRight = function() { - if (this.cursor.column == this.doc.getLine(this.cursor.row).length) { - if (this.cursor.row < this.doc.getLength() - 1) { - this.moveCursorTo(this.cursor.row + 1, 0); - } - } - else { - this.moveCursorBy(0, 1); - } -}; - -ace.Editor.prototype.moveCursorLineStart = function() { - this.moveCursorTo(this.cursor.row, 0); -}; - -ace.Editor.prototype.moveCursorLineEnd = function() { - this.moveCursorTo(this.cursor.row, - this.doc.getLine(this.cursor.row).length); -}; - -ace.Editor.prototype.moveCursorFileEnd = function() { - var row = this.doc.getLength() - 1; - var column = this.doc.getLine(row).length; - this.moveCursorTo(row, column); -}; - -ace.Editor.prototype.moveCursorFileStart = function() { - this.moveCursorTo(0, 0); -}; - -ace.Editor.prototype.moveCursorWordRight = function() { - var row = this.cursor.row; - var column = this.cursor.column; - var line = this.doc.getLine(row); - var rightOfCursor = line.substring(column); - - var match; - this.nonTokenRe.lastIndex = 0; - this.tokenRe.lastIndex = 0; - - if (column == line.length) { - this.moveCursorRight(); - return; - } - else if (match = this.nonTokenRe.exec(rightOfCursor)) { - column += this.nonTokenRe.lastIndex; - this.nonTokenRe.lastIndex = 0; - } - else if (match = this.tokenRe.exec(rightOfCursor)) { - column += this.tokenRe.lastIndex; - this.tokenRe.lastIndex = 0; - } - - this.moveCursorTo(row, column); -}; - -ace.Editor.prototype.moveCursorWordLeft = function() { - var row = this.cursor.row; - var column = this.cursor.column; - var line = this.doc.getLine(row); - var leftOfCursor = ace.stringReverse(line.substring(0, column)); - - var match; - this.nonTokenRe.lastIndex = 0; - this.tokenRe.lastIndex = 0; - - if (column == 0) { - this.moveCursorLeft(); - return; - } - else if (match = this.nonTokenRe.exec(leftOfCursor)) { - column -= this.nonTokenRe.lastIndex; - this.nonTokenRe.lastIndex = 0; - } - else if (match = this.tokenRe.exec(leftOfCursor)) { - column -= this.tokenRe.lastIndex; - this.tokenRe.lastIndex = 0; - } - - this.moveCursorTo(row, column); -}; - -ace.Editor.prototype.moveCursorBy = function(rows, chars) { - this.moveCursorTo(this.cursor.row + rows, this.cursor.column + chars); -}; - - -ace.Editor.prototype.moveCursorToPosition = function(position) { - this.moveCursorTo(position.row, position.column); -}; - -ace.Editor.prototype._clipPositionToDocument = function(row, column) { - var pos = {}; - - if (row >= this.doc.getLength()) { - pos.row = this.doc.getLength() - 1; - pos.column = this.doc.getLine(pos.row).length; - } - else if (row < 0) { - pos.row = 0; - pos.column = 0; - } - else { - pos.row = row; - pos.column = Math.min(this.doc.getLine(pos.row).length, - Math.max(0, column)); - } - return pos; +ace.Editor.prototype.clearSelection = function() { + this.selection.clearSelection(); }; ace.Editor.prototype.moveCursorTo = function(row, column) { - this.cursor = this._clipPositionToDocument(row, column); - this.updateCursor(); + this.selection.moveCursorTo(row, column); }; +ace.Editor.prototype.moveCursorToPosition = function(pos) { + this.selection.moveCursorToPosition(pos); +}; + + ace.Editor.prototype.gotoLine = function(lineNumber) { + this._blockScrolling = true; this.moveCursorTo(lineNumber, 0); - if (!this.isRowVisible(this.cursor.row)) { + this._blockScrolling = false; + + if (!this.isRowVisible(this.getCursorPosition().row)) { this.scrollToRow(lineNumber - Math.floor(this.getVisibleRowCount() / 2)); } }, -ace.Editor.prototype.getCursorPosition = function() { - return { - row : this.cursor.row, - column : this.cursor.column - }; -}; - -ace.Editor.prototype.hasSelection = function() { - return !!this.selectionLead; -}; - -ace.Editor.prototype.hasMultiLineSelection = function() { - if (!this.hasSelection()) { - return false; - } - - var range = this.getSelectionRange(); - return (range.start.row !== range.end.row); -}; - -ace.Editor.prototype.setSelectionAnchor = function(row, column) { +ace.Editor.prototype.navigateTo = function(row, column) { this.clearSelection(); - - this.selectionAnchor = this._clipPositionToDocument(row, column); - this.selectionLead = null; + this.moveCursorTo(row, column); }; -ace.Editor.prototype.getSelectionAnchor = function() { - if (this.selectionAnchor) { - return { - row: this.selectionAnchor.row, - column: this.selectionAnchor.column - }; - } else { - return { - row: this.cursor.row, - column: this.cursor.column - }; - } +ace.Editor.prototype.navigateUp = function() { + this.clearSelection(); + this.selection.moveCursorUp(); }; -ace.Editor.prototype.getSelectionLead = function() { - if (this.selectionLead) { - return { - row: this.selectionLead.row, - column: this.selectionLead.column - }; - } else { - return { - row: this.cursor.row, - column: this.cursor.column - }; - } +ace.Editor.prototype.navigateDown = function() { + this.clearSelection(); + this.selection.moveCursorDown(); }; -ace.Editor.prototype.shiftSelection = function(columns) { - if (!this.hasSelection()) { - this.moveCursorTo(this.cursor.row, this.cursor.column + columns); - return; - }; - - var anchor = this.getSelectionAnchor(); - var lead = this.getSelectionLead(); - - this.setSelectionAnchor(anchor.row, anchor.column + columns); - this._moveSelection(function() { - this.moveCursorTo(lead.row, lead.column + columns); - }); -}; - -ace.Editor.prototype.getSelectionRange = function() { - var anchor = this.selectionAnchor || this.cursor; - var lead = this.selectionLead || this.cursor; - - if (anchor.row > lead.row - || (anchor.row == lead.row && anchor.column > lead.column)) { - return { - start : lead, - end : anchor - }; +ace.Editor.prototype.navigateLeft = function() { + if (!this.selection.isEmpty()) { + var selectionStart = this.getSelectionRange().start; + this.moveCursorToPosition(selectionStart); } else { - return { - start : anchor, - end : lead - }; + this.selection.moveCursorLeft(); } + this.clearSelection(); }; -ace.Editor.prototype.clearSelection = function() { - this.selectionLead = null; - this.selectionAnchor = null; - - if (this.selection) { - this.renderer.removeMarker(this.selection); - this.selection = null; +ace.Editor.prototype.navigateRight = function() { + if (!this.selection.isEmpty()) { + var selectionEnd = this.getSelectionRange().end; + this.moveCursorToPosition(selectionEnd); } -}; - -ace.Editor.prototype.selectAll = function() { - var lastRow = this.doc.getLength() - 1; - this.setSelectionAnchor(lastRow, this.doc.getLine(lastRow).length); - - this._moveSelection(function() { - this.moveCursorTo(0, 0); - }); -}; - -ace.Editor.prototype._moveSelection = function(mover) { - if (!this.selectionAnchor) { - this.selectionAnchor = { - row : this.cursor.row, - column : this.cursor.column - }; + else { + this.selection.moveCursorRight(); } - - mover.call(this); - - this.selectionLead = { - row : this.cursor.row, - column : this.cursor.column - }; - - if (this.selection) { - this.renderer.removeMarker(this.selection); - } - this.selection = this.renderer.addMarker(this.getSelectionRange(), - "selection", "text"); - this.renderer.scrollCursorIntoView(); + this.clearSelection(); }; -ace.Editor.prototype.selectUp = function() { - this._moveSelection(this.moveCursorUp); +ace.Editor.prototype.navigateLineStart = function() { + this.clearSelection(); + this.selection.moveCursorLineStart(); }; -ace.Editor.prototype.selectDown = function() { - this._moveSelection(this.moveCursorDown); +ace.Editor.prototype.navigateLineEnd = function() { + this.clearSelection(); + this.selection.moveCursorLineEnd(); }; -ace.Editor.prototype.selectRight = function() { - this._moveSelection(this.moveCursorRight); +ace.Editor.prototype.navigateFileEnd = function() { + this.clearSelection(); + this.selection.moveCursorFileEnd(); }; -ace.Editor.prototype.selectLeft = function() { - this._moveSelection(this.moveCursorLeft); +ace.Editor.prototype.navigateFileStart = function() { + this.clearSelection(); + this.selection.moveCursorFileStart(); }; -ace.Editor.prototype.selectLineStart = function() { - this._moveSelection(this.moveCursorLineStart); +ace.Editor.prototype.navigateWordRight = function() { + this.clearSelection(); + this.selection.moveCursorWordRight(); }; -ace.Editor.prototype.selectLineEnd = function() { - this._moveSelection(this.moveCursorLineEnd); -}; - -ace.Editor.prototype.selectPageDown = function() { - var row = this.getPageDownRow() + Math.floor(this.getVisibleRowCount() / 2); - - this.scrollPageDown(); - - this._moveSelection(function() { - this.moveCursorTo(row, this.cursor.column); - }); -}; - -ace.Editor.prototype.selectPageUp = function() { - var visibleRows = this.getLastVisibleRow() - this.getFirstVisibleRow(); - var row = this.getPageUpRow() + Math.round(visibleRows / 2); - - this.scrollPageUp(); - - this._moveSelection(function() { - this.moveCursorTo(row, this.cursor.column); - }); -}; - -ace.Editor.prototype.selectFileEnd = function() { - this._moveSelection(this.moveCursorFileEnd); -}; - -ace.Editor.prototype.selectFileStart = function() { - this._moveSelection(this.moveCursorFileStart); -}; - -ace.Editor.prototype.selectWordRight = function() { - this._moveSelection(this.moveCursorWordRight); -}; - -ace.Editor.prototype.selectWordLeft = function() { - this._moveSelection(this.moveCursorWordLeft); -}; - -ace.Editor.prototype.selectLine = function() { - this.setSelectionAnchor(this.cursor.row, 0); - this._moveSelection(function() { - this.moveCursorTo(this.cursor.row + 1, 0); - }); +ace.Editor.prototype.navigateWordLeft = function() { + this.clearSelection(); + this.selection.moveCursorWordLeft(); }; \ No newline at end of file diff --git a/src/KeyBinding.js b/src/KeyBinding.js index fdd692d0..48417b97 100644 --- a/src/KeyBinding.js +++ b/src/KeyBinding.js @@ -20,21 +20,22 @@ var keys = { "7": 55 }; -ace.KeyBinding = function(element, host) { +ace.KeyBinding = function(element, editor) { ace.addListener(element, "keydown", function(e) { var key = e.keyCode; + var selection = editor.getSelection(); switch (key) { case keys.A: if (e.metaKey) { - host.selectAll(); + selection.selectAll(); return ace.stopEvent(e); } break; case keys.D: if (e.metaKey) { - host.removeLine(); + editor.removeLine(); return ace.stopEvent(e); } break; @@ -43,7 +44,7 @@ ace.KeyBinding = function(element, host) { if (e.metaKey) { var line = parseInt(prompt("Enter line number:")); if (!isNaN(line)) { - host.gotoLine(line); + editor.gotoLine(line); return ace.stopEvent(e); } } @@ -51,140 +52,140 @@ ace.KeyBinding = function(element, host) { case keys["7"]: if (e.metaKey) { - host.toggleCommentLines(); + editor.toggleCommentLines(); return ace.stopEvent(e); }; break; case keys.UP: if (e.altKey) { - host.moveLinesUp(); + editor.moveLinesUp(); } else if (e.metaKey && e.shiftKey) { - host.selectFileStart(); + selection.selectFileStart(); } else if (e.metaKey) { - host.navigateFileStart(); + editor.navigateFileStart(); } else if (e.shiftKey) { - host.selectUp(); + selection.selectUp(); } else { - host.navigateUp(); + editor.navigateUp(); } return ace.stopEvent(e); case keys.DOWN: if (e.altKey) { - host.moveLinesDown(); + editor.moveLinesDown(); } else if (e.metaKey && e.shiftKey) { - host.selectFileEnd(); + selection.selectFileEnd(); } else if (e.metaKey) { - host.navigateFileEnd(); + editor.navigateFileEnd(); } else if (e.shiftKey) { - host.selectDown(); + selection.selectDown(); } else { - host.navigateDown(); + editor.navigateDown(); } return ace.stopEvent(e); case keys.LEFT: if (e.altKey && e.shiftKey) { - host.selectWordLeft(); + selection.selectWordLeft(); } else if (e.altKey) { - host.navigateWordLeft(); + editor.navigateWordLeft(); } else if (e.metaKey && e.shiftKey) { - host.selectLineStart(); + selection.selectLineStart(); } else if (e.metaKey) { - host.navigateLineStart(); + editor.navigateLineStart(); } else if (e.shiftKey) { - host.selectLeft(); + selection.selectLeft(); } else { - host.navigateLeft(); + editor.navigateLeft(); } return ace.stopEvent(e); case keys.RIGHT: if (e.altKey && e.shiftKey) { - host.selectWordRight(); + selection.selectWordRight(); } else if (e.altKey) { - host.navigateWordRight(); + editor.navigateWordRight(); } else if (e.metaKey && e.shiftKey) { - host.selectLineEnd(); + selection.selectLineEnd(); } else if (e.metaKey) { - host.navigateLineEnd(); + editor.navigateLineEnd(); } else if (e.shiftKey) { - host.selectRight(); + selection.selectRight(); } else { - host.navigateRight(); + editor.navigateRight(); } return ace.stopEvent(e); case keys.PAGEDOWN: if (e.shiftKey) { - host.selectPageDown(); + selection.selectPageDown(); } else { - host.scrollPageDown(); + editor.scrollPageDown(); } return ace.stopEvent(e); case keys.PAGEUP: if (e.shiftKey) { - host.selectPageUp(); + selection.selectPageUp(); } else { - host.scrollPageUp(); + editor.scrollPageUp(); } return ace.stopEvent(e); case keys.POS1: if (e.shiftKey) { - host.selectLineStart(); + selection.selectLineStart(); } else { - host.navigateLineStart(); + editor.navigateLineStart(); } return ace.stopEvent(e); case keys.END: if (e.shiftKey) { - host.selectLineEnd(); + selection.selectLineEnd(); } else { - host.navigateLineEnd(); + editor.navigateLineEnd(); } return ace.stopEvent(e); case keys.DELETE: - host.removeRight(); + editor.removeRight(); return ace.stopEvent(e); case keys.BACKSPACE: - host.removeLeft(); + editor.removeLeft(); return ace.stopEvent(e); case keys.TAB: if (e.shiftKey) { - host.blockOutdent(); - } else if (host.hasMultiLineSelection()) { - host.blockIndent(); + editor.blockOutdent(); + } else if (selection.isMultiLineSelection()) { + editor.blockIndent(); } else { - host.onTextInput(host.getTabString()); + editor.onTextInput("\t"); } return ace.stopEvent(e); } diff --git a/src/MEventEmitter.js b/src/MEventEmitter.js new file mode 100644 index 00000000..6ae1983f --- /dev/null +++ b/src/MEventEmitter.js @@ -0,0 +1,27 @@ +ace.provide("ace.MEventEmitter"); + +ace.MEventEmitter.$initEvents = function() { + this._eventRegistry = {}; +}; + +ace.MEventEmitter.$dispatchEvent = function(eventName, e) { + var listeners = this._eventRegistry[eventName]; + if (!listeners) return; + + var e = e || {}; + e.type = eventName; + + for (var i=0; i lead.row + || (anchor.row == lead.row && anchor.column > lead.column)) { + return { + start : lead, + end : anchor + }; + } + else { + return { + start : anchor, + end : lead + }; + } +}; + +ace.Selection.prototype.clearSelection = function() { + this.selectionLead = null; + this.selectionAnchor = null; + this.updateSelection(); +}; + + +ace.Selection.prototype.selectAll = function() { + var lastRow = this.doc.getLength() - 1; + this.setSelectionAnchor(lastRow, this.doc.getLine(lastRow).length); + + this._moveSelection(function() { + this.moveCursorTo(0, 0); + }); +}; + +ace.Selection.prototype._moveSelection = function(mover) { + if (!this.selectionAnchor) { + this.selectionAnchor = { + row : this.cursor.row, + column : this.cursor.column + }; + } + + mover.call(this); + + this.selectionLead = { + row : this.cursor.row, + column : this.cursor.column + }; + + this.updateSelection(); +}; + +ace.Selection.prototype.selectUp = function() { + this._moveSelection(this.moveCursorUp); +}; + +ace.Selection.prototype.selectDown = function() { + this._moveSelection(this.moveCursorDown); +}; + +ace.Selection.prototype.selectRight = function() { + this._moveSelection(this.moveCursorRight); +}; + +ace.Selection.prototype.selectLeft = function() { + this._moveSelection(this.moveCursorLeft); +}; + +ace.Selection.prototype.selectLineStart = function() { + this._moveSelection(this.moveCursorLineStart); +}; + +ace.Selection.prototype.selectLineEnd = function() { + this._moveSelection(this.moveCursorLineEnd); +}; + +ace.Selection.prototype.selectPageDown = function() { + var row = this.getPageDownRow() + Math.floor(this.getVisibleRowCount() / 2); + + this.scrollPageDown(); + + this._moveSelection(function() { + this.moveCursorTo(row, this.cursor.column); + }); +}; + +ace.Selection.prototype.selectPageUp = function() { + var visibleRows = this.getLastVisibleRow() - this.getFirstVisibleRow(); + var row = this.getPageUpRow() + Math.round(visibleRows / 2); + + this.scrollPageUp(); + + this._moveSelection(function() { + this.moveCursorTo(row, this.cursor.column); + }); +}; + +ace.Selection.prototype.selectFileEnd = function() { + this._moveSelection(this.moveCursorFileEnd); +}; + +ace.Selection.prototype.selectFileStart = function() { + this._moveSelection(this.moveCursorFileStart); +}; + +ace.Selection.prototype.tokenRe = /^[\w\d]+/g; +ace.Selection.prototype.nonTokenRe = /^[^\w\d]+/g; + +ace.Selection.prototype.selectWordRight = function() { + this._moveSelection(this.moveCursorWordRight); +}; + +ace.Selection.prototype.selectWordLeft = function() { + this._moveSelection(this.moveCursorWordLeft); +}; + +ace.Selection.prototype.selectLine = function() { + this.setSelectionAnchor(this.cursor.row, 0); + this._moveSelection(function() { + this.moveCursorTo(this.cursor.row + 1, 0); + }); +}; + +ace.Selection.prototype.moveCursorUp = function() { + this.moveCursorBy(-1, 0); +}; + +ace.Selection.prototype.moveCursorDown = function() { + this.moveCursorBy(1, 0); +}; + +ace.Selection.prototype.moveCursorLeft = function() { + if (this.cursor.column == 0) { + if (this.cursor.row > 0) { + this.moveCursorTo(this.cursor.row - 1, this.doc + .getLine(this.cursor.row - 1).length); + } + } + else { + this.moveCursorBy(0, -1); + } +}; + +ace.Selection.prototype.moveCursorRight = function() { + if (this.cursor.column == this.doc.getLine(this.cursor.row).length) { + if (this.cursor.row < this.doc.getLength() - 1) { + this.moveCursorTo(this.cursor.row + 1, 0); + } + } + else { + this.moveCursorBy(0, 1); + } +}; + +ace.Selection.prototype.moveCursorLineStart = function() { + this.moveCursorTo(this.cursor.row, 0); +}; + +ace.Selection.prototype.moveCursorLineEnd = function() { + this.moveCursorTo(this.cursor.row, + this.doc.getLine(this.cursor.row).length); +}; + +ace.Selection.prototype.moveCursorFileEnd = function() { + var row = this.doc.getLength() - 1; + var column = this.doc.getLine(row).length; + this.moveCursorTo(row, column); +}; + +ace.Selection.prototype.moveCursorFileStart = function() { + this.moveCursorTo(0, 0); +}; + +ace.Selection.prototype.moveCursorWordRight = function() { + var row = this.cursor.row; + var column = this.cursor.column; + var line = this.doc.getLine(row); + var rightOfCursor = line.substring(column); + + var match; + this.nonTokenRe.lastIndex = 0; + this.tokenRe.lastIndex = 0; + + if (column == line.length) { + this.moveCursorRight(); + return; + } + else if (match = this.nonTokenRe.exec(rightOfCursor)) { + column += this.nonTokenRe.lastIndex; + this.nonTokenRe.lastIndex = 0; + } + else if (match = this.tokenRe.exec(rightOfCursor)) { + column += this.tokenRe.lastIndex; + this.tokenRe.lastIndex = 0; + } + + this.moveCursorTo(row, column); +}; + +ace.Selection.prototype.moveCursorWordLeft = function() { + var row = this.cursor.row; + var column = this.cursor.column; + var line = this.doc.getLine(row); + var leftOfCursor = ace.stringReverse(line.substring(0, column)); + + var match; + this.nonTokenRe.lastIndex = 0; + this.tokenRe.lastIndex = 0; + + if (column == 0) { + this.moveCursorLeft(); + return; + } + else if (match = this.nonTokenRe.exec(leftOfCursor)) { + column -= this.nonTokenRe.lastIndex; + this.nonTokenRe.lastIndex = 0; + } + else if (match = this.tokenRe.exec(leftOfCursor)) { + column -= this.tokenRe.lastIndex; + this.tokenRe.lastIndex = 0; + } + + this.moveCursorTo(row, column); +}; + +ace.Selection.prototype.moveCursorBy = function(rows, chars) { + this.moveCursorTo(this.cursor.row + rows, this.cursor.column + chars); +}; + + +ace.Selection.prototype.moveCursorToPosition = function(position) { + this.moveCursorTo(position.row, position.column); +}; + +ace.Selection.prototype.moveCursorTo = function(row, column) { + this.cursor = this._clipPositionToDocument(row, column); + this.updateCursor(); +}; + +ace.Selection.prototype.moveCursorUp = function() { + this.moveCursorBy(-1, 0); +}; + +ace.Selection.prototype._clipPositionToDocument = function(row, column) { + var pos = {}; + + if (row >= this.doc.getLength()) { + pos.row = this.doc.getLength() - 1; + pos.column = this.doc.getLine(pos.row).length; + } + else if (row < 0) { + pos.row = 0; + pos.column = 0; + } + else { + pos.row = row; + pos.column = Math.min(this.doc.getLine(pos.row).length, + Math.max(0, column)); + } + return pos; +}; + +ace.Selection.prototype._clone = function(pos) { + return { + row: pos.row, + column: pos.column + }; +}; \ No newline at end of file diff --git a/src/TextDocument.js b/src/TextDocument.js index 20349760..53c73d2e 100644 --- a/src/TextDocument.js +++ b/src/TextDocument.js @@ -3,6 +3,7 @@ ace.provide("ace.TextDocument"); ace.TextDocument = function(text) { this.lines = this._split(text); this.modified = true; + this.selection = new ace.Selection(this); this.listeners = []; }; @@ -15,6 +16,10 @@ ace.TextDocument.prototype.toString = function() { return this.lines.join("\n"); }; +ace.TextDocument.prototype.getSelection = function() { + return this.selection; +}; + ace.TextDocument.prototype.addChangeListener = function(listener) { this.listeners.push(listener); }; diff --git a/src/TextLayer.js b/src/TextLayer.js index d91ba5ed..d3cc4ed7 100644 --- a/src/TextLayer.js +++ b/src/TextLayer.js @@ -53,8 +53,7 @@ ace.TextLayer.prototype.updateLines = function(layerConfig, firstRow, lastRow) { var lineElement = lineElements[i - layerConfig.firstRow]; lineElement.innerHTML = html.join(""); - } - ; + }; }; ace.TextLayer.prototype.update = function(config) { @@ -84,6 +83,5 @@ ace.TextLayer.prototype.renderLine = function(stringBuilder, row) { else { stringBuilder.push(output); } - } - ; + }; }; \ No newline at end of file diff --git a/src/ace.js b/src/ace.js index 4a938586..a49b2805 100644 --- a/src/ace.js +++ b/src/ace.js @@ -21,6 +21,12 @@ ace.inherits = function(ctor, superCtor) { ctor.prototype.constructor = ctor; }; +ace.mixin = function(obj, mixin) { + for (var key in mixin) { + obj[key] = mixin[key]; + } +}; + ace.addListener = function(elem, type, callback) { if (elem.addEventListener) { return elem.addEventListener(type, callback, false); @@ -111,6 +117,22 @@ ace.stringReverse = function(string) { return string.split("").reverse().join(""); }; +if (Array.prototype.indexOf) { + ace.arrayIndexOf = function(array, searchElement) { + return array.indexOf(searchElement); + }; +} +else { + ace.arrayIndexOf = function(array, searchElement) { + for (var i=0; i= cursor.row); }, - "test: navigate to start of file should place the cursor on the first row and column" : function() { - var doc = this.createTextDocument(200, 10); - var editor = new ace.Editor(new MockRenderer(), doc); - - editor.navigateFileStart(); - assertPosition(0, 0, editor.getCursorPosition()); - }, - "test: navigate to start of file should scroll the first row into view" : function() { var doc = this.createTextDocument(200, 10); var editor = new ace.Editor(new MockRenderer(), doc); @@ -43,190 +27,37 @@ var NavigationTest = TestCase("NavigationTest", assertEquals(0, editor.getFirstVisibleRow()); }, - "test: move selection lead to end of file" : function() { - var doc = this.createTextDocument(200, 10); - var editor = new ace.Editor(new MockRenderer(), doc); - - editor.moveCursorTo(100, 5); - editor.selectFileEnd(); - - var selection = editor.getSelectionRange(); - - assertPosition(100, 5, selection.start); - assertPosition(199, 10, selection.end); - }, - - "test: move selection lead to start of file" : function() { - var doc = this.createTextDocument(200, 10); - var editor = new ace.Editor(new MockRenderer(), doc); - - editor.moveCursorTo(100, 5); - editor.selectFileStart(); - - var selection = editor.getSelectionRange(); - - assertPosition(0, 0, selection.start); - assertPosition(100, 5, selection.end); - }, - - "test: navigate word right" : function() { - var doc = new ace.TextDocument( ["ab", - " Juhu Kinners (abc, 12)", " cde"].join("\n")); - var editor = new ace.Editor(new MockRenderer(), doc); - - editor.navigateDown(); - assertPosition(1, 0, editor.getCursorPosition()); - - editor.navigateWordRight(); - assertPosition(1, 1, editor.getCursorPosition()); - - editor.navigateWordRight(); - assertPosition(1, 5, editor.getCursorPosition()); - - editor.navigateWordRight(); - assertPosition(1, 6, editor.getCursorPosition()); - - editor.navigateWordRight(); - assertPosition(1, 13, editor.getCursorPosition()); - - editor.navigateWordRight(); - assertPosition(1, 15, editor.getCursorPosition()); - - editor.navigateWordRight(); - assertPosition(1, 18, editor.getCursorPosition()); - - editor.navigateWordRight(); - assertPosition(1, 20, editor.getCursorPosition()); - - editor.navigateWordRight(); - assertPosition(1, 22, editor.getCursorPosition()); - - editor.navigateWordRight(); - assertPosition(1, 23, editor.getCursorPosition()); - - // wrap line - editor.navigateWordRight(); - assertPosition(2, 0, editor.getCursorPosition()); - }, - - "test: select word right if cursor in word" : function() { - var doc = new ace.TextDocument("Juhu Kinners"); - var editor = new ace.Editor(new MockRenderer(), doc); - - editor.moveCursorTo(0, 2); - - editor.navigateWordRight(); - assertPosition(0, 4, editor.getCursorPosition()); - }, - - "test: navigate word left" : function() { - var doc = new ace.TextDocument( ["ab", - " Juhu Kinners (abc, 12)", " cde"].join("\n")); - var editor = new ace.Editor(new MockRenderer(), doc); - - editor.navigateDown(); - editor.navigateLineEnd(); - assertPosition(1, 23, editor.getCursorPosition()); - - editor.navigateWordLeft(); - assertPosition(1, 22, editor.getCursorPosition()); - - editor.navigateWordLeft(); - assertPosition(1, 20, editor.getCursorPosition()); - - editor.navigateWordLeft(); - assertPosition(1, 18, editor.getCursorPosition()); - - editor.navigateWordLeft(); - assertPosition(1, 15, editor.getCursorPosition()); - - editor.navigateWordLeft(); - assertPosition(1, 13, editor.getCursorPosition()); - - editor.navigateWordLeft(); - assertPosition(1, 6, editor.getCursorPosition()); - - editor.navigateWordLeft(); - assertPosition(1, 5, editor.getCursorPosition()); - - editor.navigateWordLeft(); - assertPosition(1, 1, editor.getCursorPosition()); - - editor.navigateWordLeft(); - assertPosition(1, 0, editor.getCursorPosition()); - - // wrap line - editor.navigateWordLeft(); - assertPosition(0, 2, editor.getCursorPosition()); - }, - - "test: select word left if cursor in word" : function() { - var doc = new ace.TextDocument("Juhu Kinners"); - var editor = new ace.Editor(new MockRenderer(), doc); - - editor.moveCursorTo(0, 8); - - editor.navigateWordLeft(); - assertPosition(0, 5, editor.getCursorPosition()); - }, - - "test: select word right and select" : function() { - var doc = new ace.TextDocument("Juhu Kinners"); - var editor = new ace.Editor(new MockRenderer(), doc); - - editor.moveCursorTo(0, 0); - editor.selectWordRight(); - - var selection = editor.getSelectionRange(); - - assertPosition(0, 0, selection.start); - assertPosition(0, 4, selection.end); - }, - - "test: select word left and select" : function() { - var doc = new ace.TextDocument("Juhu Kinners"); - var editor = new ace.Editor(new MockRenderer(), doc); - - editor.moveCursorTo(0, 3); - editor.selectWordLeft(); - - var selection = editor.getSelectionRange(); - - assertPosition(0, 0, selection.start); - assertPosition(0, 3, selection.end); - }, - "test: goto hidden line should scroll the line into the middle of the viewport" : function() { var editor = new ace.Editor(new MockRenderer(), this.createTextDocument(200, 5)); editor.navigateTo(0, 0); editor.gotoLine(100); - assertPosition(100, 0, editor.getCursorPosition()); + assertPosition(100, 0, editor.getSelection().getCursor()); assertEquals(90, editor.getFirstVisibleRow()); editor.navigateTo(100, 0); editor.gotoLine(10); - assertPosition(10, 0, editor.getCursorPosition()); + assertPosition(10, 0, editor.getSelection().getCursor()); assertEquals(0, editor.getFirstVisibleRow()); editor.navigateTo(100, 0); editor.gotoLine(5); - assertPosition(5, 0, editor.getCursorPosition()); + assertPosition(5, 0, editor.getSelection().getCursor()); assertEquals(0, editor.getFirstVisibleRow()); editor.navigateTo(100, 0); editor.gotoLine(0); - assertPosition(0, 0, editor.getCursorPosition()); + assertPosition(0, 0, editor.getSelection().getCursor()); assertEquals(0, editor.getFirstVisibleRow()); editor.navigateTo(0, 0); editor.gotoLine(190); - assertPosition(190, 0, editor.getCursorPosition()); + assertPosition(190, 0, editor.getSelection().getCursor()); assertEquals(180, editor.getFirstVisibleRow()); editor.navigateTo(0, 0); editor.gotoLine(195); - assertPosition(195, 0, editor.getCursorPosition()); + assertPosition(195, 0, editor.getSelection().getCursor()); assertEquals(180, editor.getFirstVisibleRow()); }, @@ -235,12 +66,12 @@ var NavigationTest = TestCase("NavigationTest", editor.navigateTo(0, 0); editor.gotoLine(11); - assertPosition(11, 0, editor.getCursorPosition()); + assertPosition(11, 0, editor.getSelection().getCursor()); assertEquals(0, editor.getFirstVisibleRow()); editor.navigateTo(30, 0); editor.gotoLine(32); - assertPosition(32, 0, editor.getCursorPosition()); + assertPosition(32, 0, editor.getSelection().getCursor()); assertEquals(30, editor.getFirstVisibleRow()); } }); \ No newline at end of file diff --git a/test/SelectionTest.js b/test/SelectionTest.js new file mode 100644 index 00000000..a42278a6 --- /dev/null +++ b/test/SelectionTest.js @@ -0,0 +1,177 @@ +var SelectionTest = TestCase("SelectionTest", +{ + createTextDocument : function(rows, cols) { + var line = new Array(cols + 1).join("a"); + var text = new Array(rows).join(line + "\n") + line; + return new ace.TextDocument(text); + }, + + "test: move cursor to end of file should place the cursor on last row and column" : function() { + var doc = this.createTextDocument(200, 10); + var selection = doc.getSelection(); + + selection.moveCursorFileEnd(); + assertPosition(199, 10, selection.getCursor()); + }, + + "test: moveCursor to start of file should place the cursor on the first row and column" : function() { + var doc = this.createTextDocument(200, 10); + var selection = doc.getSelection(); + + selection.moveCursorFileStart(); + assertPosition(0, 0, selection.getCursor()); + }, + + "test: move selection lead to end of file" : function() { + var doc = this.createTextDocument(200, 10); + var selection = doc.getSelection(); + + selection.moveCursorTo(100, 5); + selection.selectFileEnd(); + + var range = selection.getRange(); + + assertPosition(100, 5, range.start); + assertPosition(199, 10, range.end); + }, + + "test: move selection lead to start of file" : function() { + var doc = this.createTextDocument(200, 10); + var selection = doc.getSelection(); + + selection.moveCursorTo(100, 5); + selection.selectFileStart(); + + var range = selection.getRange(); + + assertPosition(0, 0, range.start); + assertPosition(100, 5, range.end); + }, + + "test: move cursor word right" : function() { + var doc = new ace.TextDocument( ["ab", + " Juhu Kinners (abc, 12)", " cde"].join("\n")); + var selection = doc.getSelection(); + + selection.moveCursorDown(); + assertPosition(1, 0, selection.getCursor()); + + selection.moveCursorWordRight(); + assertPosition(1, 1, selection.getCursor()); + + selection.moveCursorWordRight(); + assertPosition(1, 5, selection.getCursor()); + + selection.moveCursorWordRight(); + assertPosition(1, 6, selection.getCursor()); + + selection.moveCursorWordRight(); + assertPosition(1, 13, selection.getCursor()); + + selection.moveCursorWordRight(); + assertPosition(1, 15, selection.getCursor()); + + selection.moveCursorWordRight(); + assertPosition(1, 18, selection.getCursor()); + + selection.moveCursorWordRight(); + assertPosition(1, 20, selection.getCursor()); + + selection.moveCursorWordRight(); + assertPosition(1, 22, selection.getCursor()); + + selection.moveCursorWordRight(); + assertPosition(1, 23, selection.getCursor()); + + // wrap line + selection.moveCursorWordRight(); + assertPosition(2, 0, selection.getCursor()); + }, + + "test: select word right if cursor in word" : function() { + var doc = new ace.TextDocument("Juhu Kinners"); + var selection = doc.getSelection(); + + selection.moveCursorTo(0, 2); + selection.moveCursorWordRight(); + + assertPosition(0, 4, selection.getCursor()); + }, + + "test: moveCursor word left" : function() { + var doc = new ace.TextDocument( ["ab", + " Juhu Kinners (abc, 12)", " cde"].join("\n")); + var selection = doc.getSelection(); + + selection.moveCursorDown(); + selection.moveCursorLineEnd(); + assertPosition(1, 23, selection.getCursor()); + + selection.moveCursorWordLeft(); + assertPosition(1, 22, selection.getCursor()); + + selection.moveCursorWordLeft(); + assertPosition(1, 20, selection.getCursor()); + + selection.moveCursorWordLeft(); + assertPosition(1, 18, selection.getCursor()); + + selection.moveCursorWordLeft(); + assertPosition(1, 15, selection.getCursor()); + + selection.moveCursorWordLeft(); + assertPosition(1, 13, selection.getCursor()); + + selection.moveCursorWordLeft(); + assertPosition(1, 6, selection.getCursor()); + + selection.moveCursorWordLeft(); + assertPosition(1, 5, selection.getCursor()); + + selection.moveCursorWordLeft(); + assertPosition(1, 1, selection.getCursor()); + + selection.moveCursorWordLeft(); + assertPosition(1, 0, selection.getCursor()); + + // wrap line + selection.moveCursorWordLeft(); + assertPosition(0, 2, selection.getCursor()); + }, + + "test: select word left if cursor in word" : function() { + var doc = new ace.TextDocument("Juhu Kinners"); + var selection = doc.getSelection(); + + selection.moveCursorTo(0, 8); + + selection.moveCursorWordLeft(); + assertPosition(0, 5, selection.getCursor()); + }, + + "test: select word right and select" : function() { + var doc = new ace.TextDocument("Juhu Kinners"); + var selection = doc.getSelection(); + + selection.moveCursorTo(0, 0); + selection.selectWordRight(); + + var range = selection.getRange(); + + assertPosition(0, 0, range.start); + assertPosition(0, 4, range.end); + }, + + "test: select word left and select" : function() { + var doc = new ace.TextDocument("Juhu Kinners"); + var selection = doc.getSelection(); + + selection.moveCursorTo(0, 3); + selection.selectWordLeft(); + + var range = selection.getRange(); + + assertPosition(0, 0, range.start); + assertPosition(0, 3, range.end); + } +}); \ No newline at end of file diff --git a/test/TextEditTest.js b/test/TextEditTest.js index 35aa7a8a..32af630a 100644 --- a/test/TextEditTest.js +++ b/test/TextEditTest.js @@ -37,7 +37,7 @@ var TextEditTest = TestCase("TextEditTest", var editor = new ace.Editor(new MockRenderer(), doc); editor.moveCursorTo(1, 3); - editor.selectDown(); + editor.getSelection().selectDown(); editor.blockIndent(" "); @@ -46,9 +46,9 @@ var TextEditTest = TestCase("TextEditTest", assertPosition(2, 7, editor.getCursorPosition()); - var selection = editor.getSelectionRange(); - assertPosition(1, 7, selection.start); - assertPosition(2, 7, selection.end); + var range = editor.getSelectionRange(); + assertPosition(1, 7, range.start); + assertPosition(2, 7, range.end); }, "test: outdent block" : function() { @@ -56,8 +56,8 @@ var TextEditTest = TestCase("TextEditTest", var editor = new ace.Editor(new MockRenderer(), doc); editor.moveCursorTo(0, 3); - editor.selectDown(); - editor.selectDown(); + editor.getSelection().selectDown(); + editor.getSelection().selectDown(); editor.blockOutdent(" "); assertEquals([" a12345", "b12345", " c12345"].join("\n"), @@ -65,18 +65,18 @@ var TextEditTest = TestCase("TextEditTest", assertPosition(2, 1, editor.getCursorPosition()); - var selection = editor.getSelectionRange(); - assertPosition(0, 1, selection.start); - assertPosition(2, 1, selection.end); + var range = editor.getSelectionRange(); + assertPosition(0, 1, range.start); + assertPosition(2, 1, range.end); editor.blockOutdent(" "); assertEquals([" a12345", "b12345", " c12345"].join("\n"), doc.toString()); - var selection = editor.getSelectionRange(); - assertPosition(0, 1, selection.start); - assertPosition(2, 1, selection.end); + var range = editor.getSelectionRange(); + assertPosition(0, 1, range.start); + assertPosition(2, 1, range.end); }, "test: outent without a selection should update cursor" : function() { @@ -95,7 +95,7 @@ var TextEditTest = TestCase("TextEditTest", var editor = new ace.Editor(new MockRenderer(), doc, new ace.mode.JavaScript()); editor.moveCursorTo(0, 2); - editor.selectDown(); + editor.getSelection().selectDown(); editor.toggleCommentLines(); @@ -111,9 +111,9 @@ var TextEditTest = TestCase("TextEditTest", var editor = new ace.Editor(new MockRenderer(), doc, new ace.mode.JavaScript()); editor.moveCursorTo(0, 1); - editor.selectDown(); - editor.selectRight(); - editor.selectRight(); + editor.getSelection().selectDown(); + editor.getSelection().selectRight(); + editor.getSelection().selectRight(); editor.toggleCommentLines(); @@ -129,26 +129,26 @@ var TextEditTest = TestCase("TextEditTest", var editor = new ace.Editor(new MockRenderer(), doc); editor.moveCursorTo(0, 1); - editor.selectDown(); + editor.getSelection().selectDown(); editor.moveLinesDown(); assertEquals(["33", "11", "22", "44"].join("\n"), doc.toString()); assertPosition(1, 0, editor.getCursorPosition()); - assertPosition(3, 0, editor.getSelectionAnchor()); - assertPosition(1, 0, editor.getSelectionLead()); + assertPosition(3, 0, editor.getSelection().getSelectionAnchor()); + assertPosition(1, 0, editor.getSelection().getSelectionLead()); editor.moveLinesDown(); assertEquals(["33", "44", "11", "22"].join("\n"), doc.toString()); assertPosition(2, 0, editor.getCursorPosition()); - assertPosition(3, 2, editor.getSelectionAnchor()); - assertPosition(2, 0, editor.getSelectionLead()); + assertPosition(3, 2, editor.getSelection().getSelectionAnchor()); + assertPosition(2, 0, editor.getSelection().getSelectionLead()); // moving again should have no effect editor.moveLinesDown(); assertEquals(["33", "44", "11", "22"].join("\n"), doc.toString()); assertPosition(2, 0, editor.getCursorPosition()); - assertPosition(3, 2, editor.getSelectionAnchor()); - assertPosition(2, 0, editor.getSelectionLead()); + assertPosition(3, 2, editor.getSelection().getSelectionAnchor()); + assertPosition(2, 0, editor.getSelection().getSelectionLead()); }, "test: move lines up should select moved lines" : function() { @@ -156,19 +156,19 @@ var TextEditTest = TestCase("TextEditTest", var editor = new ace.Editor(new MockRenderer(), doc); editor.moveCursorTo(2, 1); - editor.selectDown(); + editor.getSelection().selectDown(); editor.moveLinesUp(); assertEquals(["11", "33", "44", "22"].join("\n"), doc.toString()); assertPosition(1, 0, editor.getCursorPosition()); - assertPosition(3, 0, editor.getSelectionAnchor()); - assertPosition(1, 0, editor.getSelectionLead()); + assertPosition(3, 0, editor.getSelection().getSelectionAnchor()); + assertPosition(1, 0, editor.getSelection().getSelectionLead()); editor.moveLinesUp(); assertEquals(["33", "44", "11", "22"].join("\n"), doc.toString()); assertPosition(0, 0, editor.getCursorPosition()); - assertPosition(2, 0, editor.getSelectionAnchor()); - assertPosition(0, 0, editor.getSelectionLead()); + assertPosition(2, 0, editor.getSelection().getSelectionAnchor()); + assertPosition(0, 0, editor.getSelection().getSelectionLead()); }, "test: move line without active selection should move cursor to start of the moved line" : function() @@ -188,5 +188,30 @@ var TextEditTest = TestCase("TextEditTest", editor.moveLinesUp(); assertEquals(["11", "22", "33", "44"].join("\n"), doc.toString()); assertPosition(1, 0, editor.getCursorPosition()); + }, + + "test: input a tab with soft tab should convert it to spaces" : function() { + var doc = new ace.TextDocument(""); + var editor = new ace.Editor(new MockRenderer(), doc); + + editor.setTabSize(2); + editor.setUseSoftTabs(true); + + editor.onTextInput("\t"); + assertEquals(" ", doc.toString()); + + editor.setTabSize(5); + editor.onTextInput("\t"); + assertEquals(" ", doc.toString()); + }, + + "test: input tab without soft tabs should keep the tab character" : function() { + var doc = new ace.TextDocument(""); + var editor = new ace.Editor(new MockRenderer(), doc); + + editor.setUseSoftTabs(false); + + editor.onTextInput("\t"); + assertEquals("\t", doc.toString()); } }); \ No newline at end of file From 1f82e32175a44b9595689d669636462ee31795b7 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 15 Apr 2010 15:58:13 +0200 Subject: [PATCH 070/392] move word selection from the editor to the selection class --- src/Editor.js | 38 ++------------------------------------ src/Selection.js | 37 +++++++++++++++++++++++++++++++++++++ test/SelectionTest.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 36 deletions(-) diff --git a/src/Editor.js b/src/Editor.js index 6779f8a3..ed8a6e87 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -34,7 +34,7 @@ ace.Editor.prototype.setDocument = function(doc) { this.doc = doc; - doc.addChangeListener(ace.bind(this.onDocumentChange, this)); + doc.addEventListener("change", ace.bind(this.onDocumentChange, this)); this.renderer.setDocument(doc); this.selection = doc.getSelection(); @@ -192,41 +192,7 @@ ace.Editor.prototype.tokenRe = /^[\w\d]+/g; ace.Editor.prototype.nonTokenRe = /^[^\w\d]+/g; ace.Editor.prototype.onMouseDoubleClick = function(e) { - var cursor = this.selection.getCursor(); - - var line = this.doc.getLine(cursor.row); - var column = cursor.column; - - var inToken = false; - if (column > 0) { - inToken = !!line.charAt(column - 1).match(this.tokenRe); - } - - if (!inToken) { - inToken = !!line.charAt(column).match(this.tokenRe); - } - - var re = inToken ? this.tokenRe : this.nonTokenRe; - - var start = column; - if (start > 0) { - do { - start--; - } - while (start >= 0 && line.charAt(start).match(re)); - start++; - } - - var end = column; - while (end < line.length && line.charAt(end).match(re)) { - end++; - } - - var selection = this.selection; - selection.setSelectionAnchor(cursor.row, start); - selection._moveSelection(function() { - selection.moveCursorTo(cursor.row, end); - }); + this.selection.selectWord(); }; ace.Editor.prototype.onMouseWheel = function(e) { diff --git a/src/Selection.js b/src/Selection.js index 3430330b..dc9102c7 100644 --- a/src/Selection.js +++ b/src/Selection.js @@ -205,6 +205,43 @@ ace.Selection.prototype.selectWordLeft = function() { this._moveSelection(this.moveCursorWordLeft); }; +ace.Selection.prototype.selectWord = function() { + var cursor = this.cursor; + + var line = this.doc.getLine(cursor.row); + var column = cursor.column; + + var inToken = false; + if (column > 0) { + inToken = !!line.charAt(column - 1).match(this.tokenRe); + } + + if (!inToken) { + inToken = !!line.charAt(column).match(this.tokenRe); + } + + var re = inToken ? this.tokenRe : this.nonTokenRe; + + var start = column; + if (start > 0) { + do { + start--; + } + while (start >= 0 && line.charAt(start).match(re)); + start++; + } + + var end = column; + while (end < line.length && line.charAt(end).match(re)) { + end++; + } + + this.setSelectionAnchor(cursor.row, start); + this._moveSelection(function() { + this.moveCursorTo(cursor.row, end); + }); +}; + ace.Selection.prototype.selectLine = function() { this.setSelectionAnchor(this.cursor.row, 0); this._moveSelection(function() { diff --git a/test/SelectionTest.js b/test/SelectionTest.js index a42278a6..95c81b14 100644 --- a/test/SelectionTest.js +++ b/test/SelectionTest.js @@ -173,5 +173,48 @@ var SelectionTest = TestCase("SelectionTest", assertPosition(0, 0, range.start); assertPosition(0, 3, range.end); + }, + + "test: select word with cursor in word should select the word" : function() { + var doc = new ace.TextDocument("Juhu Kinners 123"); + var selection = doc.getSelection(); + + selection.moveCursorTo(0, 8); + selection.selectWord(); + + var range = selection.getRange(); + assertPosition(0, 5, range.start); + assertPosition(0, 12, range.end); + }, + + "test: select word with cursor betwen white space and word should select the word" : function() { + var doc = new ace.TextDocument("Juhu Kinners"); + var selection = doc.getSelection(); + + selection.moveCursorTo(0, 4); + selection.selectWord(); + + var range = selection.getRange(); + assertPosition(0, 0, range.start); + assertPosition(0, 4, range.end); + + selection.moveCursorTo(0, 5); + selection.selectWord(); + + var range = selection.getRange(); + assertPosition(0, 5, range.start); + assertPosition(0, 12, range.end); + }, + + "test: select word with cursor in white space should select white space" : function() { + var doc = new ace.TextDocument("Juhu Kinners"); + var selection = doc.getSelection(); + + selection.moveCursorTo(0, 5); + selection.selectWord(); + + var range = selection.getRange(); + assertPosition(0, 4, range.start); + assertPosition(0, 6, range.end); } }); \ No newline at end of file From 844a440ca82be976f9144f020afe7ecaa1343a45 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 15 Apr 2010 15:58:31 +0200 Subject: [PATCH 071/392] use events to publish document changes --- src/TextDocument.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/TextDocument.js b/src/TextDocument.js index 53c73d2e..0f741463 100644 --- a/src/TextDocument.js +++ b/src/TextDocument.js @@ -6,7 +6,11 @@ ace.TextDocument = function(text) { this.selection = new ace.Selection(this); this.listeners = []; + + this.$initEvents(); }; +ace.mixin(ace.TextDocument.prototype, ace.MEventEmitter); + ace.TextDocument.prototype._split = function(text) { return text.split(/[\n\r]/); @@ -20,14 +24,12 @@ ace.TextDocument.prototype.getSelection = function() { return this.selection; }; -ace.TextDocument.prototype.addChangeListener = function(listener) { - this.listeners.push(listener); -}; - ace.TextDocument.prototype.fireChangeEvent = function(firstRow, lastRow) { - for ( var i = 0; i < this.listeners.length; i++) { - this.listeners[i](firstRow, lastRow); + var data = { + firstRow: firstRow, + lastRow: lastRow }; + this.$dispatchEvent("change", { data: data}); }; ace.TextDocument.prototype.getWidth = function() { From a2f125ee4b763b2f59629d9431b2674651d7bfac Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 15 Apr 2010 16:07:44 +0200 Subject: [PATCH 072/392] The selection lead is now the same as the cursor --- src/Selection.js | 89 +++++++++++++++++------------------------------- 1 file changed, 31 insertions(+), 58 deletions(-) diff --git a/src/Selection.js b/src/Selection.js index dc9102c7..69a5aa4e 100644 --- a/src/Selection.js +++ b/src/Selection.js @@ -6,7 +6,7 @@ ace.Selection = function(doc) { this.$initEvents(); this.clearSelection(); - this.cursor = { + this.selectionLead = { row: 0, column: 0 }; @@ -22,7 +22,7 @@ ace.Selection.prototype.updateSelection = function() { }; ace.Selection.prototype.isEmpty = function() { - return (this.selectionLead == null); + return (this.selectionAnchor == null); }; ace.Selection.prototype.isMultiLine = function() { @@ -35,47 +35,30 @@ ace.Selection.prototype.isMultiLine = function() { }; ace.Selection.prototype.getCursor = function() { - return this.cursor; + return this.selectionLead; }; ace.Selection.prototype.setSelectionAnchor = function(row, column) { this.clearSelection(); this.selectionAnchor = this._clipPositionToDocument(row, column); - this.selectionLead = null; }; ace.Selection.prototype.getSelectionAnchor = function() { if (this.selectionAnchor) { - return { - row: this.selectionAnchor.row, - column: this.selectionAnchor.column - }; + return this._clone(this.selectionAnchor); } else { - return { - row: this.cursor.row, - column: this.cursor.column - }; + return this._clone(this.selectionLead); } }; ace.Selection.prototype.getSelectionLead = function() { - if (this.selectionLead) { - return { - row: this.selectionLead.row, - column: this.selectionLead.column - }; - } else { - return { - row: this.cursor.row, - column: this.cursor.column - }; - } + return this._clone(this.selectionLead); }; ace.Selection.prototype.shiftSelection = function(columns) { if (this.isEmpty()) { - this.moveCursorTo(this.cursor.row, this.cursor.column + columns); + this.moveCursorTo(this.selectionLead.row, this.selectionLead.column + columns); return; }; @@ -89,8 +72,8 @@ ace.Selection.prototype.shiftSelection = function(columns) { }; ace.Selection.prototype.getRange = function() { - var anchor = this.selectionAnchor || this.cursor; - var lead = this.selectionLead || this.cursor; + var anchor = this.selectionAnchor || this.selectionLead; + var lead = this.selectionLead; if (anchor.row > lead.row || (anchor.row == lead.row && anchor.column > lead.column)) { @@ -108,7 +91,6 @@ ace.Selection.prototype.getRange = function() { }; ace.Selection.prototype.clearSelection = function() { - this.selectionLead = null; this.selectionAnchor = null; this.updateSelection(); }; @@ -125,19 +107,10 @@ ace.Selection.prototype.selectAll = function() { ace.Selection.prototype._moveSelection = function(mover) { if (!this.selectionAnchor) { - this.selectionAnchor = { - row : this.cursor.row, - column : this.cursor.column - }; + this.selectionAnchor = this._clone(this.selectionLead); } mover.call(this); - - this.selectionLead = { - row : this.cursor.row, - column : this.cursor.column - }; - this.updateSelection(); }; @@ -171,7 +144,7 @@ ace.Selection.prototype.selectPageDown = function() { this.scrollPageDown(); this._moveSelection(function() { - this.moveCursorTo(row, this.cursor.column); + this.moveCursorTo(row, this.selectionLead.column); }); }; @@ -182,7 +155,7 @@ ace.Selection.prototype.selectPageUp = function() { this.scrollPageUp(); this._moveSelection(function() { - this.moveCursorTo(row, this.cursor.column); + this.moveCursorTo(row, this.selectionLead.column); }); }; @@ -206,7 +179,7 @@ ace.Selection.prototype.selectWordLeft = function() { }; ace.Selection.prototype.selectWord = function() { - var cursor = this.cursor; + var cursor = this.selectionLead; var line = this.doc.getLine(cursor.row); var column = cursor.column; @@ -243,9 +216,9 @@ ace.Selection.prototype.selectWord = function() { }; ace.Selection.prototype.selectLine = function() { - this.setSelectionAnchor(this.cursor.row, 0); + this.setSelectionAnchor(this.selectionLead.row, 0); this._moveSelection(function() { - this.moveCursorTo(this.cursor.row + 1, 0); + this.moveCursorTo(this.selectionLead.row + 1, 0); }); }; @@ -258,10 +231,10 @@ ace.Selection.prototype.moveCursorDown = function() { }; ace.Selection.prototype.moveCursorLeft = function() { - if (this.cursor.column == 0) { - if (this.cursor.row > 0) { - this.moveCursorTo(this.cursor.row - 1, this.doc - .getLine(this.cursor.row - 1).length); + if (this.selectionLead.column == 0) { + if (this.selectionLead.row > 0) { + this.moveCursorTo(this.selectionLead.row - 1, this.doc + .getLine(this.selectionLead.row - 1).length); } } else { @@ -270,9 +243,9 @@ ace.Selection.prototype.moveCursorLeft = function() { }; ace.Selection.prototype.moveCursorRight = function() { - if (this.cursor.column == this.doc.getLine(this.cursor.row).length) { - if (this.cursor.row < this.doc.getLength() - 1) { - this.moveCursorTo(this.cursor.row + 1, 0); + if (this.selectionLead.column == this.doc.getLine(this.selectionLead.row).length) { + if (this.selectionLead.row < this.doc.getLength() - 1) { + this.moveCursorTo(this.selectionLead.row + 1, 0); } } else { @@ -281,12 +254,12 @@ ace.Selection.prototype.moveCursorRight = function() { }; ace.Selection.prototype.moveCursorLineStart = function() { - this.moveCursorTo(this.cursor.row, 0); + this.moveCursorTo(this.selectionLead.row, 0); }; ace.Selection.prototype.moveCursorLineEnd = function() { - this.moveCursorTo(this.cursor.row, - this.doc.getLine(this.cursor.row).length); + this.moveCursorTo(this.selectionLead.row, + this.doc.getLine(this.selectionLead.row).length); }; ace.Selection.prototype.moveCursorFileEnd = function() { @@ -300,8 +273,8 @@ ace.Selection.prototype.moveCursorFileStart = function() { }; ace.Selection.prototype.moveCursorWordRight = function() { - var row = this.cursor.row; - var column = this.cursor.column; + var row = this.selectionLead.row; + var column = this.selectionLead.column; var line = this.doc.getLine(row); var rightOfCursor = line.substring(column); @@ -326,8 +299,8 @@ ace.Selection.prototype.moveCursorWordRight = function() { }; ace.Selection.prototype.moveCursorWordLeft = function() { - var row = this.cursor.row; - var column = this.cursor.column; + var row = this.selectionLead.row; + var column = this.selectionLead.column; var line = this.doc.getLine(row); var leftOfCursor = ace.stringReverse(line.substring(0, column)); @@ -352,7 +325,7 @@ ace.Selection.prototype.moveCursorWordLeft = function() { }; ace.Selection.prototype.moveCursorBy = function(rows, chars) { - this.moveCursorTo(this.cursor.row + rows, this.cursor.column + chars); + this.moveCursorTo(this.selectionLead.row + rows, this.selectionLead.column + chars); }; @@ -361,7 +334,7 @@ ace.Selection.prototype.moveCursorToPosition = function(position) { }; ace.Selection.prototype.moveCursorTo = function(row, column) { - this.cursor = this._clipPositionToDocument(row, column); + this.selectionLead = this._clipPositionToDocument(row, column); this.updateCursor(); }; From 6a11af1a44d579b1fa70b67b157cef5df7633a85 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 15 Apr 2010 16:19:42 +0200 Subject: [PATCH 073/392] some cleanups --- src/Editor.js | 20 ++++++++------------ src/Selection.js | 6 ++++++ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Editor.js b/src/Editor.js index ed8a6e87..901bdf34 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -10,13 +10,10 @@ ace.Editor = function(renderer, doc, mode) { this.textInput = new ace.TextInput(container, this); new ace.KeyBinding(container, this); - ace.addListener(container, "mousedown", ace - .bind(this.onMouseDown, this)); - ace.addListener(container, "dblclick", ace - .bind(this.onMouseDoubleClick, this)); + ace.addListener(container, "mousedown", ace.bind(this.onMouseDown, this)); + ace.addListener(container, "dblclick", ace.bind(this.onMouseDoubleClick, this)); + ace.addTripleClickListener(container, ace.bind(this.onMouseTripleClick, this)); ace.addMouseWheelListener(container, ace.bind(this.onMouseWheel, this)); - ace.addTripleClickListener(container, ace.bind(this.selection.selectLine, - this.selection)); this.selectionMarker = null; this._blockScrolling = false; @@ -176,9 +173,7 @@ ace.Editor.prototype.onMouseDown = function(e) { selectionLead = _self.renderer.screenToTextCoordinates(mousePageX, mousePageY); - _self.selection._moveSelection(function() { - _self.moveCursorToPosition(selectionLead); - }); + _self.selection.selectToPosition(selectionLead); _self.renderer.scrollCursorIntoView(); }; @@ -188,13 +183,14 @@ ace.Editor.prototype.onMouseDown = function(e) { return ace.preventDefault(e); }; -ace.Editor.prototype.tokenRe = /^[\w\d]+/g; -ace.Editor.prototype.nonTokenRe = /^[^\w\d]+/g; - ace.Editor.prototype.onMouseDoubleClick = function(e) { this.selection.selectWord(); }; +ace.Editor.prototype.onMouseTripleClick = function(e) { + this.selection.selectLine(); +}; + ace.Editor.prototype.onMouseWheel = function(e) { var delta = e.wheel; this.renderer.scrollToY(this.renderer.getScrollTop() - (delta * 15)); diff --git a/src/Selection.js b/src/Selection.js index 69a5aa4e..20f0f84d 100644 --- a/src/Selection.js +++ b/src/Selection.js @@ -114,6 +114,12 @@ ace.Selection.prototype._moveSelection = function(mover) { this.updateSelection(); }; +ace.Selection.prototype.selectToPosition = function(pos) { + this._moveSelection(function() { + this.moveCursorToPosition(pos); + }); +}; + ace.Selection.prototype.selectUp = function() { this._moveSelection(this.moveCursorUp); }; From 3e8639596a8a3c74b9a364878819eb54d3754f41 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 15 Apr 2010 16:42:07 +0200 Subject: [PATCH 074/392] move tab support from editor to the document --- src/Editor.js | 47 ++++++++----------------------- src/TextDocument.js | 56 ++++++++++++++++++++----------------- src/TextLayer.js | 12 ++++++-- src/VirtualRenderer.js | 1 + test/TextEditTest.js | 8 +++--- test/mode/JavaScriptTest.js | 4 +-- 6 files changed, 58 insertions(+), 70 deletions(-) diff --git a/src/Editor.js b/src/Editor.js index 901bdf34..cb9deb7e 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -34,6 +34,11 @@ ace.Editor.prototype.setDocument = function(doc) { doc.addEventListener("change", ace.bind(this.onDocumentChange, this)); this.renderer.setDocument(doc); + var self = this; + doc.addEventListener("changeTabSize", function() { + self.renderer.draw(); + }); + this.selection = doc.getSelection(); var onCursorChange = ace.bind(this.onCursorChange, this); @@ -42,6 +47,7 @@ ace.Editor.prototype.setDocument = function(doc) { var onSelectionChange = ace.bind(this.onSelectionChange, this); this.selection.addEventListener("changeSelection", onSelectionChange); + this.bgTokenizer.setLines(this.doc.lines); }; @@ -216,8 +222,8 @@ ace.Editor.prototype.onCut = function() { ace.Editor.prototype.onTextInput = function(text) { var cursor = this.getCursorPosition(); - if (this.getUseSoftTabs()) { - text = text.replace(/\t/g, this.getTabString()); + if (this.doc.getUseSoftTabs()) { + text = text.replace(/\t/g, this.doc.getTabString()); } if (!this.selection.isEmpty()) { @@ -233,7 +239,7 @@ ace.Editor.prototype.onTextInput = function(text) { if (row !== end.row) { var line = this.doc.getLine(row); var lineState = this.bgTokenizer.getState(row); - var indent = this.mode.getNextLineIndent(line, lineState, this.getTabString()); + var indent = this.mode.getNextLineIndent(line, lineState, this.doc.getTabString()); if (indent) { var indentRange = { start: { @@ -250,37 +256,6 @@ ace.Editor.prototype.onTextInput = function(text) { this.renderer.scrollCursorIntoView(); }; - -ace.Editor.prototype.getTabString = function() { - if (this.getUseSoftTabs()) { - return new Array(this.getTabSize()+1).join(" "); - } - return "\t"; -}; - -ace.Editor.prototype._useSoftTabs = true; -ace.Editor.prototype.setUseSoftTabs = function(useSoftTabs) { - if (this._useSoftTabs === useSoftTabs) return; - - this._useSoftTabs = useSoftTabs; -}; - -ace.Editor.prototype.getUseSoftTabs = function() { - return this._useSoftTabs; -}; - -ace.Editor.prototype._tabSize = 4; -ace.Editor.prototype.setTabSize = function(tabSize) { - if (this._tabSize === tabSize) return; - - this._tabSize = tabSize; - this.renderer.draw(); -}; - -ace.Editor.prototype.getTabSize = function() { - return this._tabSize; -}; - ace.Editor.prototype.removeRight = function() { if (this.selection.isEmpty()) { this.selection.selectRight(); @@ -309,14 +284,14 @@ ace.Editor.prototype.removeLine = function() { }; ace.Editor.prototype.blockIndent = function(indentString) { - var indentString = indentString || this.getTabString(); + var indentString = indentString || this.doc.getTabString(); var addedColumns = this.doc.indentRows(this.getSelectionRange(), indentString); this.selection.shiftSelection(addedColumns); }; ace.Editor.prototype.blockOutdent = function(indentString) { - var indentString = indentString || this.getTabString(); + var indentString = indentString || this.doc.getTabString(); var addedColumns = this.doc.outdentRows(this.getSelectionRange(), indentString); this.selection.shiftSelection(addedColumns); diff --git a/src/TextDocument.js b/src/TextDocument.js index 0f741463..5a5e408b 100644 --- a/src/TextDocument.js +++ b/src/TextDocument.js @@ -32,6 +32,36 @@ ace.TextDocument.prototype.fireChangeEvent = function(firstRow, lastRow) { this.$dispatchEvent("change", { data: data}); }; +ace.TextDocument.prototype.getTabString = function() { + if (this.getUseSoftTabs()) { + return new Array(this.getTabSize()+1).join(" "); + } + return "\t"; +}; + +ace.TextDocument.prototype._useSoftTabs = true; +ace.TextDocument.prototype.setUseSoftTabs = function(useSoftTabs) { + if (this._useSoftTabs === useSoftTabs) return; + + this._useSoftTabs = useSoftTabs; +}; + +ace.TextDocument.prototype.getUseSoftTabs = function() { + return this._useSoftTabs; +}; + +ace.TextDocument.prototype._tabSize = 4; +ace.TextDocument.prototype.setTabSize = function(tabSize) { + if (this._tabSize === tabSize) return; + + this._tabSize = tabSize; + this.$dispatchEvent("changeTabSize"); +}; + +ace.TextDocument.prototype.getTabSize = function() { + return this._tabSize; +}; + ace.TextDocument.prototype.getWidth = function() { if (this.modified) { this.modified = false; @@ -50,32 +80,6 @@ ace.TextDocument.prototype.getLine = function(row) { return this.lines[row] || ""; }; -ace.TextDocument.prototype.keywords = { - "break" : 1, - "case" : 1, - "catch" : 1, - "continue" : 1, - "default" : 1, - "delete" : 1, - "do" : 1, - "else" : 1, - "finally" : 1, - "for" : 1, - "function" : 1, - "if" : 1, - "in" : 1, - "instanceof" : 1, - "new" : 1, - "return" : 1, - "switch" : 1, - "throw" : 1, - "try" : 1, - "typeof" : 1, - "var" : 1, - "while" : 1, - "with" : 1 -}; - ace.TextDocument.prototype.getLength = function() { return this.lines.length; }; diff --git a/src/TextLayer.js b/src/TextLayer.js index d3cc4ed7..6af0eb58 100644 --- a/src/TextLayer.js +++ b/src/TextLayer.js @@ -6,6 +6,7 @@ ace.TextLayer = function(parentEl) { parentEl.appendChild(this.element); this._measureSizes(); + this._tabString = " "; }; ace.TextLayer.prototype.setTokenizer = function(tokenizer) { @@ -41,6 +42,10 @@ ace.TextLayer.prototype._measureSizes = function() { this.element.removeChild(measureNode); }; +ace.TextLayer.prototype.setTabSize = function(tabSize) { + this._tabString = new Array(tabSize+1).join(" "); +}; + ace.TextLayer.prototype.updateLines = function(layerConfig, firstRow, lastRow) { var first = Math.max(firstRow, layerConfig.firstRow); var last = Math.min(lastRow, layerConfig.lastRow); @@ -73,8 +78,11 @@ ace.TextLayer.prototype.renderLine = function(stringBuilder, row) { for ( var i = 0; i < tokens.length; i++) { var token = tokens[i]; - var output = token.value.replace(/&/g, "&").replace(/", output, diff --git a/src/VirtualRenderer.js b/src/VirtualRenderer.js index 001adbe5..bff44bc9 100644 --- a/src/VirtualRenderer.js +++ b/src/VirtualRenderer.js @@ -37,6 +37,7 @@ ace.VirtualRenderer.prototype.setDocument = function(doc) { this.lines = doc.lines; this.doc = doc; this.markerLayer.setDocument(doc); + this.textLayer.setTabSize(doc.getTabSize()); }; ace.VirtualRenderer.prototype.setTokenizer = function(tokenizer) { diff --git a/test/TextEditTest.js b/test/TextEditTest.js index 32af630a..77f78751 100644 --- a/test/TextEditTest.js +++ b/test/TextEditTest.js @@ -194,13 +194,13 @@ var TextEditTest = TestCase("TextEditTest", var doc = new ace.TextDocument(""); var editor = new ace.Editor(new MockRenderer(), doc); - editor.setTabSize(2); - editor.setUseSoftTabs(true); + doc.setTabSize(2); + doc.setUseSoftTabs(true); editor.onTextInput("\t"); assertEquals(" ", doc.toString()); - editor.setTabSize(5); + doc.setTabSize(5); editor.onTextInput("\t"); assertEquals(" ", doc.toString()); }, @@ -209,7 +209,7 @@ var TextEditTest = TestCase("TextEditTest", var doc = new ace.TextDocument(""); var editor = new ace.Editor(new MockRenderer(), doc); - editor.setUseSoftTabs(false); + doc.setUseSoftTabs(false); editor.onTextInput("\t"); assertEquals("\t", doc.toString()); diff --git a/test/mode/JavaScriptTest.js b/test/mode/JavaScriptTest.js index 3e6183d4..3ce04472 100644 --- a/test/mode/JavaScriptTest.js +++ b/test/mode/JavaScriptTest.js @@ -56,7 +56,7 @@ var JavaScriptTest = new TestCase("mode.JavaScriptTest", { editor.navigateLineEnd(); editor.onTextInput("\n"); - assertEquals(["if () {", editor.getTabString()].join("\n"), doc.toString()); + assertEquals(["if () {", doc.getTabString()].join("\n"), doc.toString()); }, "test: no auto indent after opening brace in multi line comment" : function() { @@ -76,6 +76,6 @@ var JavaScriptTest = new TestCase("mode.JavaScriptTest", { editor.navigateLineEnd(); editor.onTextInput("\n"); - assertEquals([" if () {", " " + editor.getTabString()].join("\n"), doc.toString()); + assertEquals([" if () {", " " + doc.getTabString()].join("\n"), doc.toString()); } }); \ No newline at end of file From e37af546d5ecb428f9d5444f8a818aa6a827edb6 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 15 Apr 2010 17:09:11 +0200 Subject: [PATCH 075/392] support dynamic change of selection style --- demo/editor.html | 34 ++++++++++++++++++++++++---------- src/Editor.js | 22 ++++++++++++++++++++-- src/MarkerLayer.js | 2 +- src/VirtualRenderer.js | 4 ++-- 4 files changed, 47 insertions(+), 15 deletions(-) diff --git a/demo/editor.html b/demo/editor.html index fee837f5..56629d02 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -55,16 +55,20 @@ - - - + + + +
- - -
+ + + + + +
@@ -87,6 +91,16 @@ function getMode() { return modes[modeEl.value]; } +var selectEl = document.getElementById("select_style"); +selectEl.onchange = function() { + if (selectEl.checked) { + editor.setSelectionStyle("line"); + } else { + editor.setSelectionStyle("text"); + } +}; + + var container = document.getElementById("container"); var editor = new ace.Editor( new ace.VirtualRenderer(container), diff --git a/src/Editor.js b/src/Editor.js index cb9deb7e..7a3436bb 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -21,6 +21,8 @@ ace.Editor = function(renderer, doc, mode) { this.renderer.draw(); this.onCursorChange(); this.onSelectionChange(); + + this._initialized = true; }; ace.Editor.prototype.setDocument = function(doc) { @@ -69,6 +71,10 @@ ace.Editor.prototype.setMode = function(mode) { } this.renderer.setTokenizer(this.bgTokenizer); + + if (this._initialized) { + this.renderer.draw(); + } }; @@ -79,7 +85,6 @@ ace.Editor.prototype.resize = function() }; ace.Editor.prototype._highlightBrackets = function() { - if (this._bracketHighlight) { this.renderer.removeMarker(this._bracketHighlight); this._bracketHighlight = null; @@ -146,7 +151,8 @@ ace.Editor.prototype.onSelectionChange = function() { if (!this.selection.isEmpty()) { var range = this.selection.getRange(); - this.selectionMarker = this.renderer.addMarker(range, "selection", "text"); + var style = this.getSelectionStyle(); + this.selectionMarker = this.renderer.addMarker(range, "selection", style); } this.onCursorChange(); @@ -256,6 +262,18 @@ ace.Editor.prototype.onTextInput = function(text) { this.renderer.scrollCursorIntoView(); }; +ace.Editor.prototype._selectionStyle = "line"; +ace.Editor.prototype.setSelectionStyle = function(style) { + if (this._selectionStyle == style) return; + + this._selectionStyle = style; + this.onSelectionChange(); +}; + +ace.Editor.prototype.getSelectionStyle = function() { + return this._selectionStyle; +}; + ace.Editor.prototype.removeRight = function() { if (this.selection.isEmpty()) { this.selection.selectRight(); diff --git a/src/MarkerLayer.js b/src/MarkerLayer.js index cfa019e2..90d28b65 100644 --- a/src/MarkerLayer.js +++ b/src/MarkerLayer.js @@ -67,7 +67,7 @@ ace.MarkerLayer.prototype.update = function(config) { } if (range.start.row !== range.end.row) { - if (marker.type == "line") { + if (marker.type == "text") { this.drawTextMarker(html, range, marker.clazz, config); } else { this.drawMultiLineMarker(html, range, marker.clazz, config); diff --git a/src/VirtualRenderer.js b/src/VirtualRenderer.js index bff44bc9..7f9b6cd9 100644 --- a/src/VirtualRenderer.js +++ b/src/VirtualRenderer.js @@ -111,8 +111,8 @@ ace.VirtualRenderer.prototype.draw = function() { this.gutterLayer.update(layerConfig); }; -ace.VirtualRenderer.prototype.addMarker = function(range, clazz) { - return this.markerLayer.addMarker(range, clazz); +ace.VirtualRenderer.prototype.addMarker = function(range, clazz, type) { + return this.markerLayer.addMarker(range, clazz, type); }; ace.VirtualRenderer.prototype.removeMarker = function(markerId) { From 299894795ec640031ef84bdf1ceb2f16b39e2f39 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 16 Apr 2010 10:46:47 +0200 Subject: [PATCH 076/392] add copy lines down/up functionality and fix "delete line" --- src/Editor.js | 58 ++++++++++++++++++++++------------ src/KeyBinding.js | 12 +++++-- src/Selection.js | 6 ++++ src/TextDocument.js | 44 +++++++++++++++++--------- test/TextDocumentTest.js | 21 +++++++++++++ test/TextEditTest.js | 67 +++++++++++++++++++++++++++++++++++++--- 6 files changed, 166 insertions(+), 42 deletions(-) diff --git a/src/Editor.js b/src/Editor.js index 7a3436bb..55ca87de 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -290,17 +290,6 @@ ace.Editor.prototype.removeLeft = function() { this.clearSelection(); }; -ace.Editor.prototype.removeLine = function() { - this.selection.selectLine(); - this.moveCursorToPosition(this.doc.remove(this.getSelectionRange())); - this.clearSelection(); - - if (this.getCursorPosition().row == this.doc.getLength() - 1) { - this.removeLeft(); - this.selection.moveCursorLineStart(); - } -}; - ace.Editor.prototype.blockIndent = function(indentString) { var indentString = indentString || this.doc.getTabString(); var addedColumns = this.doc.indentRows(this.getSelectionRange(), indentString); @@ -324,6 +313,15 @@ ace.Editor.prototype.toggleCommentLines = function() { this.selection.shiftSelection(addedColumns); }; +ace.Editor.prototype.removeLines = function() { + var rows = this._getSelectedRows(); + this.selection.setSelectionAnchor(rows.last+1, 0); + this.selection.selectTo(rows.first, 0); + + this.doc.remove(this.getSelectionRange()); + this.clearSelection(); +}; + ace.Editor.prototype.moveLinesDown = function() { this._moveLines(function(firstRow, lastRow) { return this.doc.moveLinesDown(firstRow, lastRow); @@ -336,7 +334,33 @@ ace.Editor.prototype.moveLinesUp = function() { }); }; +ace.Editor.prototype.copyLinesUp = function() { + this._moveLines(function(firstRow, lastRow) { + this.doc.duplicateLines(firstRow, lastRow); + return 0; + }); +}; + +ace.Editor.prototype.copyLinesDown = function() { + this._moveLines(function(firstRow, lastRow) { + return this.doc.duplicateLines(firstRow, lastRow); + }); +}; + + ace.Editor.prototype._moveLines = function(mover) { + var rows = this._getSelectedRows(); + + var linesMoved = mover.call(this, rows.first, rows.last); + + var selection = this.selection; + selection.setSelectionAnchor(rows.last+linesMoved+1, 0); + selection._moveSelection(function() { + selection.moveCursorTo(rows.first+linesMoved, 0); + }); +}; + +ace.Editor.prototype._getSelectedRows = function() { var range = this.getSelectionRange(); var firstRow = range.start.row; var lastRow = range.end.row; @@ -344,16 +368,12 @@ ace.Editor.prototype._moveLines = function(mover) { lastRow -= 1; } - var linesMoved = mover.call(this, firstRow, lastRow); - - var selection = this.selection; - selection.setSelectionAnchor(lastRow+linesMoved+1, 0); - selection._moveSelection(function() { - selection.moveCursorTo(firstRow+linesMoved, 0); - }); + return { + first: firstRow, + last: lastRow + }; }; - ace.Editor.prototype.onCompositionStart = function() { this.renderer.showComposition(this.getCursorPosition()); this.onTextInput(" "); diff --git a/src/KeyBinding.js b/src/KeyBinding.js index 48417b97..e2ef51fd 100644 --- a/src/KeyBinding.js +++ b/src/KeyBinding.js @@ -35,7 +35,7 @@ ace.KeyBinding = function(element, editor) { case keys.D: if (e.metaKey) { - editor.removeLine(); + editor.removeLines(); return ace.stopEvent(e); } break; @@ -58,7 +58,10 @@ ace.KeyBinding = function(element, editor) { break; case keys.UP: - if (e.altKey) { + if (e.altKey && e.metaKey ) { + editor.copyLinesUp(); + } + else if (e.altKey) { editor.moveLinesUp(); } else if (e.metaKey && e.shiftKey) { @@ -76,7 +79,10 @@ ace.KeyBinding = function(element, editor) { return ace.stopEvent(e); case keys.DOWN: - if (e.altKey) { + if (e.altKey && e.metaKey ) { + editor.copyLinesDown(); + } + else if (e.altKey) { editor.moveLinesDown(); } else if (e.metaKey && e.shiftKey) { diff --git a/src/Selection.js b/src/Selection.js index 20f0f84d..af2078f2 100644 --- a/src/Selection.js +++ b/src/Selection.js @@ -114,6 +114,12 @@ ace.Selection.prototype._moveSelection = function(mover) { this.updateSelection(); }; +ace.Selection.prototype.selectTo = function(row, column) { + this._moveSelection(function() { + this.moveCursorTo(row, column); + }); +}; + ace.Selection.prototype.selectToPosition = function(pos) { this._moveSelection(function() { this.moveCursorToPosition(pos); diff --git a/src/TextDocument.js b/src/TextDocument.js index 5a5e408b..17c6400c 100644 --- a/src/TextDocument.js +++ b/src/TextDocument.js @@ -92,10 +92,8 @@ ace.TextDocument.prototype.getTextRange = function(range) { else { 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)); + lines.push.apply(lines, this.getLines(range.start.row+1, range.end.row-1)); lines.push(this.lines[range.end.row].substring(0, range.end.column)); - return lines.join("\n"); } }; @@ -203,6 +201,12 @@ ace.TextDocument.prototype.insert = function(position, text) { return end; }; +ace.TextDocument.prototype._insertLines = function(row, lines) { + var args = [row, 0]; + args.push.apply(args, lines); + this.lines.splice.apply(this.lines, args); +}, + ace.TextDocument.prototype._insert = function(position, text) { this.modified = true; @@ -237,9 +241,7 @@ ace.TextDocument.prototype._insert = function(position, text) { + line.substring(position.column); if (newLines.length > 2) { - var args = [ position.row + 1, 0 ]; - args.push.apply(args, newLines.slice(1, -1)); - this.lines.splice.apply(this.lines, args); + this._insertLines(position.row + 1, newLines.slice(1, -1)); } return { @@ -318,10 +320,7 @@ ace.TextDocument.prototype.moveLinesUp = function(firstRow, lastRow) { if (firstRow <= 0) return 0; var removed = this.lines.splice(firstRow, lastRow-firstRow+1); - - var args = [firstRow - 1, 0]; - args.push.apply(args, removed); - this.lines.splice.apply(this.lines, args); + this._insertLines(firstRow-1, removed); this.fireChangeEvent(firstRow-1, lastRow); return -1; @@ -331,11 +330,26 @@ ace.TextDocument.prototype.moveLinesDown = function(firstRow, lastRow) { if (lastRow >= this.lines.length-1) return 0; var removed = this.lines.splice(firstRow, lastRow-firstRow+1); - - var args = [firstRow + 1, 0]; - args.push.apply(args, removed); - this.lines.splice.apply(this.lines, args); + this._insertLines(firstRow+1, removed); this.fireChangeEvent(firstRow, lastRow+1); return 1; -}; \ No newline at end of file +}; + +ace.TextDocument.prototype.duplicateLines = function(firstRow, lastRow) { + var firstRow = this._clipRowToDocument(firstRow); + var lastRow = this._clipRowToDocument(lastRow); + + var lines = this.getLines(firstRow, lastRow); + this._insertLines(firstRow, lines); + + var addedRows = lastRow - firstRow + 1; + this.fireChangeEvent(firstRow, lastRow+addedRows); + + return addedRows; +}; + +ace.TextDocument.prototype._clipRowToDocument = function(row) { + return Math.max(0, Math.min(row, this.lines.length-1)); +}; + diff --git a/test/TextDocumentTest.js b/test/TextDocumentTest.js index 6a169841..58539654 100644 --- a/test/TextDocumentTest.js +++ b/test/TextDocumentTest.js @@ -63,5 +63,26 @@ var TextDocumentTest = new TestCase("TextDocumentTest", { doc.moveLinesUp(2, 2); assertEquals(["3", "1", "4", "2"].join("\n"), doc.toString()); + }, + + "test: duplicate lines" : function() { + var doc = new ace.TextDocument(["1", "2", "3", "4"].join("\n")); + + doc.duplicateLines(1, 2); + assertEquals(["1", "2", "3", "2", "3", "4"].join("\n"), doc.toString()); + }, + + "test: duplicate last line" : function() { + var doc = new ace.TextDocument(["1", "2", "3"].join("\n")); + + doc.duplicateLines(2, 2); + assertEquals(["1", "2", "3", "3"].join("\n"), doc.toString()); + }, + + "test: duplicate first line" : function() { + var doc = new ace.TextDocument(["1", "2", "3"].join("\n")); + + doc.duplicateLines(0, 0); + assertEquals(["1", "1", "2", "3"].join("\n"), doc.toString()); } }); \ No newline at end of file diff --git a/test/TextEditTest.js b/test/TextEditTest.js index 77f78751..cb635f35 100644 --- a/test/TextEditTest.js +++ b/test/TextEditTest.js @@ -5,17 +5,44 @@ var TextEditTest = TestCase("TextEditTest", var editor = new ace.Editor(new MockRenderer(), doc); editor.moveCursorTo(1, 1); - editor.removeLine(); + editor.removeLines(); assertEquals("a\nc\nd", doc.toString()); assertPosition(1, 0, editor.getCursorPosition()); + + editor.removeLines(); + + assertEquals("a\nd", doc.toString()); + assertPosition(1, 0, editor.getCursorPosition()); + + editor.removeLines(); + + assertEquals("a\n", doc.toString()); + assertPosition(1, 0, editor.getCursorPosition()); + + editor.removeLines(); + + assertEquals("a\n", doc.toString()); + assertPosition(1, 0, editor.getCursorPosition()); + }, + + "test: delete multiple selected lines" : function() { + var doc = new ace.TextDocument(["a", "b", "c", "d"].join("\n")); + var editor = new ace.Editor(new MockRenderer(), doc); + + editor.moveCursorTo(1, 1); + editor.getSelection().selectDown(); + + editor.removeLines(); + assertEquals("a\nd", doc.toString()); + assertPosition(1, 0, editor.getCursorPosition()); }, "test: delete first line" : function() { var doc = new ace.TextDocument(["a", "b", "c"].join("\n")); var editor = new ace.Editor(new MockRenderer(), doc); - editor.removeLine(); + editor.removeLines(); assertEquals("b\nc", doc.toString()); assertPosition(0, 0, editor.getCursorPosition()); @@ -26,10 +53,10 @@ var TextEditTest = TestCase("TextEditTest", var editor = new ace.Editor(new MockRenderer(), doc); editor.moveCursorTo(2, 1); - editor.removeLine(); + editor.removeLines(); - assertEquals("a\nb", doc.toString()); - assertPosition(1, 0, editor.getCursorPosition()); + assertEquals("a\nb\n", doc.toString()); + assertPosition(2, 0, editor.getCursorPosition()); }, "test: indent block" : function() { @@ -190,6 +217,36 @@ var TextEditTest = TestCase("TextEditTest", assertPosition(1, 0, editor.getCursorPosition()); }, + "test: copy lines down should select lines and place cursor at the selection start" : function() { + var doc = new ace.TextDocument(["11", "22", "33", "44"].join("\n")); + var editor = new ace.Editor(new MockRenderer(), doc); + + editor.moveCursorTo(1, 1); + editor.getSelection().selectDown(); + + editor.copyLinesDown(); + assertEquals(["11", "22", "33", "22", "33", "44"].join("\n"), doc.toString()); + + assertPosition(3, 0, editor.getCursorPosition()); + assertPosition(5, 0, editor.getSelection().getSelectionAnchor()); + assertPosition(3, 0, editor.getSelection().getSelectionLead()); + }, + + "test: copy lines up should select lines and place cursor at the selection start" : function() { + var doc = new ace.TextDocument(["11", "22", "33", "44"].join("\n")); + var editor = new ace.Editor(new MockRenderer(), doc); + + editor.moveCursorTo(1, 1); + editor.getSelection().selectDown(); + + editor.copyLinesUp(); + assertEquals(["11", "22", "33", "22", "33", "44"].join("\n"), doc.toString()); + + assertPosition(1, 0, editor.getCursorPosition()); + assertPosition(3, 0, editor.getSelection().getSelectionAnchor()); + assertPosition(1, 0, editor.getSelection().getSelectionLead()); + }, + "test: input a tab with soft tab should convert it to spaces" : function() { var doc = new ace.TextDocument(""); var editor = new ace.Editor(new MockRenderer(), doc); From 80f6cd772633338ebd309329d92253ed48091a22 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 16 Apr 2010 11:14:12 +0200 Subject: [PATCH 077/392] add option to highlight the active line --- css/editor.css | 16 +++++++++++++++- demo/editor.html | 8 ++++++++ src/Editor.js | 46 +++++++++++++++++++++++++++++++++++++++++----- src/MarkerLayer.js | 4 ++-- 4 files changed, 66 insertions(+), 8 deletions(-) diff --git a/css/editor.css b/css/editor.css index 5000cb73..32cd9d98 100644 --- a/css/editor.css +++ b/css/editor.css @@ -53,12 +53,14 @@ } .text-layer { + z-index: 2; font-family: Monaco, "Courier New", monospace; font-size: 12px; cursor: text; } .cursor { + z-index: 3; position: absolute; width: 2px; background: black; @@ -89,13 +91,25 @@ color: rgb(104, 104, 91); } +.marker-layer { + z-index: 1; +} + .marker-layer .selection { position: absolute; - background: rgba(77, 151, 255, 0.33); + z-index: 2; + background: rgb(181, 213, 255); } .marker-layer .bracket { position: absolute; + z-index: 3; margin: -1px 0 0 -1px; border: 1px solid rgb(192, 192, 192); +} + +.marker-layer .active_line { + position: absolute; + z-index: 1; + background: rgb(232, 242, 254); } \ No newline at end of file diff --git a/demo/editor.html b/demo/editor.html index 56629d02..79248fff 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -68,6 +68,10 @@ + + + + @@ -100,6 +104,10 @@ selectEl.onchange = function() { } }; +var selectEl = document.getElementById("highlight_active"); +selectEl.onchange = function() { + editor.setHighlightActiveLine(!!selectEl.checked); +}; var container = document.getElementById("container"); var editor = new ace.Editor( diff --git a/src/Editor.js b/src/Editor.js index 55ca87de..216f4a43 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -15,7 +15,8 @@ ace.Editor = function(renderer, doc, mode) { ace.addTripleClickListener(container, ace.bind(this.onMouseTripleClick, this)); ace.addMouseWheelListener(container, ace.bind(this.onMouseWheel, this)); - this.selectionMarker = null; + this._selectionMarker = null; + this._highlightLineMarker = null; this._blockScrolling = false; this.renderer.draw(); @@ -141,18 +142,41 @@ ace.Editor.prototype.onCursorChange = function() { if (!this._blockScrolling) { this.renderer.scrollCursorIntoView(); } + this._updateHighlightActiveLine(); +}; + +ace.Editor.prototype._updateHighlightActiveLine = function() { + if (this._highlightLineMarker) { + this.renderer.removeMarker(this._highlightLineMarker); + } + this._highlightLineMarker = null; + + if (this.getHighlightActiveLine() && !this.selection.isMultiLine()) { + var cursor = this.getCursorPosition(); + var range = { + start: { + row: cursor.row, + column: 0 + }, + end: { + row: cursor.row+1, + column: 0 + } + }; + this._highlightLineMarker = this.renderer.addMarker(range, "active_line", "line"); + } }; ace.Editor.prototype.onSelectionChange = function() { - if (this.selectionMarker) { - this.renderer.removeMarker(this.selectionMarker); + if (this._selectionMarker) { + this.renderer.removeMarker(this._selectionMarker); } - this.selectionMarker = null; + this._selectionMarker = null; if (!this.selection.isEmpty()) { var range = this.selection.getRange(); var style = this.getSelectionStyle(); - this.selectionMarker = this.renderer.addMarker(range, "selection", style); + this._selectionMarker = this.renderer.addMarker(range, "selection", style); } this.onCursorChange(); @@ -274,6 +298,18 @@ ace.Editor.prototype.getSelectionStyle = function() { return this._selectionStyle; }; +ace.Editor.prototype._highlightActiveLine = true; +ace.Editor.prototype.setHighlightActiveLine = function(shouldHighlight) { + if (this._highlightActiveLine == shouldHighlight) return; + + this._highlightActiveLine = shouldHighlight; + this._updateHighlightActiveLine(); +}; + +ace.Editor.prototype.getHighlightActiveLine = function() { + return this._highlightActiveLine; +}; + ace.Editor.prototype.removeRight = function() { if (this.selection.isEmpty()) { this.selection.selectRight(); diff --git a/src/MarkerLayer.js b/src/MarkerLayer.js index 90d28b65..9e2ed0dc 100644 --- a/src/MarkerLayer.js +++ b/src/MarkerLayer.js @@ -54,8 +54,8 @@ ace.MarkerLayer.prototype.update = function(config) { if (range.end.row > config.lastRow) { range.end = { - row: config.lastRow, - column: this.doc.getLine(config.lastRow).length + row: config.lastRow+1, + column: 0 }; } From 257d80f3791cef35b7e929c0324484252050ccfd Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 16 Apr 2010 12:08:03 +0200 Subject: [PATCH 078/392] Add support for doc doc comments: - special highlighting - highlight jsdoc tags - special auto indent mode --- css/editor.css | 8 +++++++ src/KeyBinding.js | 2 +- src/Tokenizer.js | 31 +++++++++++++++++++++------- src/mode/JavaScript.js | 19 ++++++++++------- src/mode/JavaScriptHighlightRules.js | 28 ++++++++++++++++++++----- test/mode/JavaScriptTest.js | 31 ++++++++++------------------ test/mode/JavaScriptTokenizerTest.js | 27 +++++++++++++++++++++--- 7 files changed, 103 insertions(+), 43 deletions(-) diff --git a/css/editor.css b/css/editor.css index 32cd9d98..d30aac52 100644 --- a/css/editor.css +++ b/css/editor.css @@ -80,9 +80,17 @@ .line .comment { font-style: italic; + color: rgb(76, 136, 107); +} + +.line .doc-comment { color: rgb(0, 102, 255); } +.line .doc-comment-tag { + color: rgb(128, 159, 191); +} + .line .number { color: rgb(0, 0, 205); } diff --git a/src/KeyBinding.js b/src/KeyBinding.js index e2ef51fd..cbc1af6b 100644 --- a/src/KeyBinding.js +++ b/src/KeyBinding.js @@ -188,7 +188,7 @@ ace.KeyBinding = function(element, editor) { case keys.TAB: if (e.shiftKey) { editor.blockOutdent(); - } else if (selection.isMultiLineSelection()) { + } else if (selection.isMultiLine()) { editor.blockIndent(); } else { editor.onTextInput("\t"); diff --git a/src/Tokenizer.js b/src/Tokenizer.js index fb50fc74..3881f8ff 100644 --- a/src/Tokenizer.js +++ b/src/Tokenizer.js @@ -27,11 +27,14 @@ ace.Tokenizer.prototype.getLineTokens = function(line, startState) { var lastIndex = 0; + var token = { + type: null, + value: "" + }; + while (match = re.exec(line)) { - var token = { - type : "text", - value : match[0] - }; + var type = "text"; + var value = match[0]; if (re.lastIndex == lastIndex) { throw new Error("tokenizer error"); } lastIndex = re.lastIndex; @@ -41,10 +44,10 @@ ace.Tokenizer.prototype.getLineTokens = function(line, startState) { for ( var i = 0; i < state.length; i++) { if (match[i + 1]) { if (typeof state[i].token == "function") { - token.type = state[i].token(match[0]); + type = state[i].token(match[0]); } else { - token.type = state[i].token; + type = state[i].token; } if (state[i].next && state[i].next !== currentState) { @@ -59,9 +62,23 @@ ace.Tokenizer.prototype.getLineTokens = function(line, startState) { } }; - tokens.push(token); + if (token.type !== type) { + if (token.type) { + tokens.push(token); + } + token = { + type: type, + value: value + }; + } else { + token.value += value; + } }; + if (token.type) { + tokens.push(token); + } + // window.LOG && console.log(tokens, currentState); return { diff --git a/src/mode/JavaScript.js b/src/mode/JavaScript.js index c4f25fd6..1c428866 100644 --- a/src/mode/JavaScript.js +++ b/src/mode/JavaScript.js @@ -13,18 +13,23 @@ ace.mode.JavaScript.prototype.toggleCommentLines = function(doc, range) { return addedRows; }; -ace.mode.JavaScript.prototype.increaseIndentPatterns = { - "start" : /^(\s*).*[\{\(\[]\s*$/ -}; - ace.mode.JavaScript.prototype.getNextLineIndent = function(line, state, tab) { - var re = this.increaseIndentPatterns[state]; - - if (re) { + if (state == "start") { + var re = /^(\s*).*[\{\(\[]\s*$/; var match = line.match(re); if (match) { return (match[1] || "") + tab; } + } else if (state == "doc-comment") { + var re = /^(\s*)(\/?)\*.*$/; + var match = line.match(re); + if (match) { + var indent = match[1]; + if (match[2]) { + indent += " "; + } + return indent + "* "; + } } var match = line.match(/^(\s+).*$/); diff --git a/src/mode/JavaScriptHighlightRules.js b/src/mode/JavaScriptHighlightRules.js index 767a436c..661dd168 100644 --- a/src/mode/JavaScriptHighlightRules.js +++ b/src/mode/JavaScriptHighlightRules.js @@ -32,15 +32,16 @@ ace.mode.JavaScriptHighlightRules = function() { // regexps are ordered -> the first match is used this._rules = { - start : [ { + "start" : [ { token : "comment", regex : "\\/\\/.*$" }, { - token : "comment", // multi line comment in one line - regex : "\\/\\*.*?\\*\\/" + token : "doc-comment", // doc comment + regex : "\\/\\*\\*", + next : "doc-comment" }, { - token : "comment", // multi line comment start - regex : "\\/\\*.*$", + token : "comment", // multi line comment + regex : "\\/\\*", next : "comment" }, { token : "string", // single line @@ -82,6 +83,23 @@ ace.mode.JavaScriptHighlightRules = function() { token : "text", regex : "\\s+" } ], + "doc-comment" : [ { + token : "doc-comment", // closing comment + regex : "\\*\\/", + next : "start" + }, { + token : "doc-comment-tag", + regex : "@[\\w\\d_]+" + }, { + token : "doc-comment", + regex : "\s+" + }, { + token : "doc-comment", + regex : "[^@\\*]+" + }, { + token : "doc-comment", + regex : "." + }], "comment" : [ { token : "comment", // closing comment regex : ".*?\\*\\/", diff --git a/test/mode/JavaScriptTest.js b/test/mode/JavaScriptTest.js index 3ce04472..77309b0f 100644 --- a/test/mode/JavaScriptTest.js +++ b/test/mode/JavaScriptTest.js @@ -50,32 +50,23 @@ var JavaScriptTest = new TestCase("mode.JavaScriptTest", { }, "test: auto indent after opening brace" : function() { - var doc = new ace.TextDocument(["if () {"].join("\n")); - var editor = new ace.Editor(new MockRenderer(), doc, this.mode); - - editor.navigateLineEnd(); - editor.onTextInput("\n"); - - assertEquals(["if () {", doc.getTabString()].join("\n"), doc.toString()); + assertEquals(" ", this.mode.getNextLineIndent("if () {", "start", " ")); }, "test: no auto indent after opening brace in multi line comment" : function() { - var doc = new ace.TextDocument(["/*if () {"].join("\n")); - var editor = new ace.Editor(new MockRenderer(), doc, this.mode); - - editor.navigateLineEnd(); - editor.onTextInput("\n"); - - assertEquals(["/*if () {", ""].join("\n"), doc.toString()); + assertEquals("", this.mode.getNextLineIndent("/*if () {", " ")); }, "test: no auto indent should add to existing indent" : function() { - var doc = new ace.TextDocument([" if () {"].join("\n")); - var editor = new ace.Editor(new MockRenderer(), doc, this.mode); + assertEquals(" ", this.mode.getNextLineIndent(" if () {", "start", " ")); + assertEquals(" ", this.mode.getNextLineIndent(" cde", "start", " ")); + }, - editor.navigateLineEnd(); - editor.onTextInput("\n"); - - assertEquals([" if () {", " " + doc.getTabString()].join("\n"), doc.toString()); + "test: special indent in doc comments" : function() { + assertEquals(" * ", this.mode.getNextLineIndent("/**", "doc-comment", " ")); + assertEquals(" * ", this.mode.getNextLineIndent(" /**", "doc-comment", " ")); + assertEquals(" * ", this.mode.getNextLineIndent(" *", "doc-comment", " ")); + assertEquals(" * ", this.mode.getNextLineIndent(" *", "doc-comment", " ")); + assertEquals(" ", this.mode.getNextLineIndent(" abc", "doc-comment", " ")); } }); \ No newline at end of file diff --git a/test/mode/JavaScriptTokenizerTest.js b/test/mode/JavaScriptTokenizerTest.js index 0e5e019e..9c0cd8b3 100644 --- a/test/mode/JavaScriptTokenizerTest.js +++ b/test/mode/JavaScriptTokenizerTest.js @@ -9,12 +9,33 @@ var JavaScriptTokenizerTest = new TestCase("mode.JavaScriptTokenizerTest", { var tokens = this.tokenizer.getLineTokens(line, "start").tokens; + assertEquals(3, tokens.length); + assertEquals("identifier", tokens[0].type); + assertEquals("text", tokens[1].type); + assertEquals("keyword", tokens[2].type); + }, + + "test: tokenize doc comment" : function() { + var line = "abc /** de */ fg"; + + var tokens = this.tokenizer.getLineTokens(line, "start").tokens; + assertEquals(5, tokens.length); assertEquals("identifier", tokens[0].type); assertEquals("text", tokens[1].type); - assertEquals("text", tokens[2].type); + assertEquals("doc-comment", tokens[2].type); assertEquals("text", tokens[3].type); - assertEquals("keyword", tokens[4].type); - } + assertEquals("identifier", tokens[4].type); + }, + "test: tokenize doc comment with tag" : function() { + var line = "/** @param {} */"; + + var tokens = this.tokenizer.getLineTokens(line, "start").tokens; + + assertEquals(3, tokens.length); + assertEquals("doc-comment", tokens[0].type); + assertEquals("doc-comment-tag", tokens[1].type); + assertEquals("doc-comment", tokens[2].type); + } }); \ No newline at end of file From 980fee2e99006d5b81b84664835b5b8a4a57b54a Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 16 Apr 2010 15:35:58 +0200 Subject: [PATCH 079/392] add html mode with mixed XML and JS highlighting --- demo/editor.html | 6 +- src/Editor.js | 3 +- src/Tokenizer.js | 4 +- src/mode/Html.js | 26 +++++++ src/mode/HtmlHighlightRules.js | 119 +++++++++++++++++++++++++++++++++ src/mode/JavaScript.js | 2 +- src/mode/Text.js | 2 +- test/mode/HtmlTokenizerTest.js | 25 +++++++ test/mode/JavaScriptTest.js | 6 +- test/mode/XmlTest.js | 2 +- test/mode/XmlTokenizerTest.js | 6 +- 11 files changed, 187 insertions(+), 14 deletions(-) create mode 100644 src/mode/Html.js create mode 100644 src/mode/HtmlHighlightRules.js create mode 100644 test/mode/HtmlTokenizerTest.js diff --git a/demo/editor.html b/demo/editor.html index 79248fff..9f1e9a7f 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -36,6 +36,8 @@ + + @@ -61,7 +63,8 @@ @@ -88,6 +91,7 @@ modeEl.onchange = function() { var modes = { text: new ace.mode.Text(), xml: new ace.mode.Xml(), + html: new ace.mode.Html(), javascript: new ace.mode.JavaScript() }; diff --git a/src/Editor.js b/src/Editor.js index 216f4a43..abee186f 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -344,7 +344,8 @@ ace.Editor.prototype.toggleCommentLines = function() { if (this.selection.isEmpty()) return; var range = this.getSelectionRange(); - var addedColumns = this.mode.toggleCommentLines(this.doc, range); + var state = this.bgTokenizer.getState(this.getCursorPosition().row); + var addedColumns = this.mode.toggleCommentLines(this.doc, range, state); this.selection.shiftSelection(addedColumns); }; diff --git a/src/Tokenizer.js b/src/Tokenizer.js index 3881f8ff..8313e900 100644 --- a/src/Tokenizer.js +++ b/src/Tokenizer.js @@ -39,7 +39,7 @@ ace.Tokenizer.prototype.getLineTokens = function(line, startState) { if (re.lastIndex == lastIndex) { throw new Error("tokenizer error"); } lastIndex = re.lastIndex; -// window.LOG && console.log(match); +// window.LOG && jstestdriver.console.log(currentState, match); for ( var i = 0; i < state.length; i++) { if (match[i + 1]) { @@ -79,7 +79,7 @@ ace.Tokenizer.prototype.getLineTokens = function(line, startState) { tokens.push(token); } -// window.LOG && console.log(tokens, currentState); +// window.LOG && jstestdriver.console.log(tokens, currentState); return { tokens : tokens, diff --git a/src/mode/Html.js b/src/mode/Html.js new file mode 100644 index 00000000..036e0656 --- /dev/null +++ b/src/mode/Html.js @@ -0,0 +1,26 @@ +ace.provide("ace.mode.Html"); + +ace.mode.Html = function() { + this.$tokenizer = new ace.Tokenizer(new ace.mode.HtmlHighlightRules().getRules()); + + this._js = new ace.mode.JavaScript(); +}; +ace.inherits(ace.mode.Html, ace.mode.Text); + +ace.mode.Html.prototype.toggleCommentLines = function(doc, range, state) { + var split = state.split("js-"); + if (!split[0] && split[1]) { + return this._js.toggleCommentLines(doc, range, state); + } + + return 0; +}; + +ace.mode.Html.prototype.getNextLineIndent = function(line, state, tab) { + var split = state.split("js-"); + if (!split[0] && split[1]) { + return this._js.getNextLineIndent(line, split[1], tab); + } + + return ""; +}; \ No newline at end of file diff --git a/src/mode/HtmlHighlightRules.js b/src/mode/HtmlHighlightRules.js new file mode 100644 index 00000000..2da964e7 --- /dev/null +++ b/src/mode/HtmlHighlightRules.js @@ -0,0 +1,119 @@ +ace.provide("ace.mode.HtmlHighlightRules"); + +ace.mode.HtmlHighlightRules = function() { + + // regexp must not have capturing parentheses + // regexps are ordered -> the first match is used + + this._rules = { + start : [ { + token : "text", + regex : "<\\!\\[CDATA\\[", + next : "cdata" + }, { + token : "xml_pe", + regex : "<\\?.*?\\?>" + }, { + token : "comment", + regex : "<\\!--", + next : "comment" + }, { + token : "text", + regex : "<(?=\s*script)", + next : "script" + }, { + token : "text", // opening tag + regex : "<\\/?", + next : "tag" + }, { + token : "text", + regex : "\\s+" + }, { + token : "text", + regex : "[^<]+" + } ], + + script : [ { + token : "text", + regex : ">", + next : "js-start" + }, { + token : "keyword", + regex : "[-_a-zA-Z0-9:]+" + }, { + token : "text", + regex : "\\s+" + }, { + token : "string", + regex : '".*?"' + }, { + token : "string", + regex : "'.*?'" + } ], + + tag : [ { + token : "text", + regex : ">", + next : "start" + }, { + token : "keyword", + regex : "[-_a-zA-Z0-9:]+" + }, { + token : "text", + regex : "\\s+" + }, { + token : "string", + regex : '".*?"' + }, { + token : "string", + regex : "'.*?'" + } ], + + cdata : [ { + token : "text", + regex : "\\]\\]>", + next : "start" + }, { + token : "text", + regex : "\\s+" + }, { + token : "text", + regex : ".+" + } ], + + comment : [ { + token : "comment", + regex : ".*?-->", + next : "start" + }, { + token : "comment", + regex : ".+" + } ] + }; + + var jsRules = new ace.mode.JavaScriptHighlightRules().getRules(); + this._addRules(jsRules, "js-"); + this._rules["js-start"].unshift({ + token: "text", + regex: "<\\/(?=script)", + next: "tag" + }); +}; + + +ace.mode.HtmlHighlightRules.prototype._addRules = function(rules, prefix) { + for (var key in rules) { + var state = rules[key]; + for (var i=0; i Date: Fri, 16 Apr 2010 15:36:21 +0200 Subject: [PATCH 080/392] Use events for the backgroundtokenizer --- src/BackgroundTokenizer.js | 23 ++++++++++++++--------- src/Editor.js | 15 +++++++++------ 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/BackgroundTokenizer.js b/src/BackgroundTokenizer.js index f2481584..ff681786 100644 --- a/src/BackgroundTokenizer.js +++ b/src/BackgroundTokenizer.js @@ -1,17 +1,12 @@ ace.provide("ace.BackgroundTokenizer"); -ace.BackgroundTokenizer = function(tokenizer, onUpdate, onComplete) { +ace.BackgroundTokenizer = function(tokenizer) { this.running = false; this.textLines = []; this.lines = []; this.currentLine = 0; this.tokenizer = tokenizer; - this.onUpdate = onUpdate || function(firstLine, lastLine) { - }; - this.onComplete = onComplete || function() { - }; - var self = this; this._worker = function() { if (!self.running) { return; } @@ -33,7 +28,7 @@ ace.BackgroundTokenizer = function(tokenizer, onUpdate, onComplete) { // only check every 30 lines processedLines += 1; if ((processedLines % 30 == 0) && (new Date() - workerStart) > 20) { - self.onUpdate(startLine, self.currentLine); + self.fireUpdateEvent(startLine, self.currentLine); return setTimeout(self._worker, 10); } @@ -42,10 +37,12 @@ ace.BackgroundTokenizer = function(tokenizer, onUpdate, onComplete) { self.running = false; - self.onUpdate(startLine, textLines.length - 1); - self.onComplete(); + self.fireUpdateEvent(startLine, textLines.length - 1); }; + + this.$initEvents(); }; +ace.mixin(ace.BackgroundTokenizer.prototype, ace.MEventEmitter); ace.BackgroundTokenizer.prototype.setTokenizer = function(tokenizer) { this.tokenizer = tokenizer; @@ -61,6 +58,14 @@ ace.BackgroundTokenizer.prototype.setLines = function(textLines) { this.stop(); }; +ace.BackgroundTokenizer.prototype.fireUpdateEvent = function(firstRow, lastRow) { + var data = { + first: firstRow, + last: lastRow + }; + this.$dispatchEvent("update", {data: data}); +}; + ace.BackgroundTokenizer.prototype.start = function(startRow) { this.currentLine = Math.min(startRow || 0, this.currentLine, this.textLines.length); diff --git a/src/Editor.js b/src/Editor.js index abee186f..72ef38a3 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -66,7 +66,8 @@ ace.Editor.prototype.setMode = function(mode) { if (!this.bgTokenizer) { var onUpdate = ace.bind(this.onTokenizerUpdate, this); - this.bgTokenizer = new ace.BackgroundTokenizer(tokenizer, onUpdate); + this.bgTokenizer = new ace.BackgroundTokenizer(tokenizer); + this.bgTokenizer.addEventListener("update", onUpdate); } else { this.bgTokenizer.setTokenizer(tokenizer); } @@ -126,13 +127,15 @@ ace.Editor.prototype.onBlur = function() { this.renderer.visualizeBlur(); }; -ace.Editor.prototype.onDocumentChange = function(startRow, endRow) { - this.bgTokenizer.start(startRow); - this.renderer.updateLines(startRow, endRow); +ace.Editor.prototype.onDocumentChange = function(e) { + var data = e.data; + this.bgTokenizer.start(data.startRow); + this.renderer.updateLines(data.startRow, data.endRow); }; -ace.Editor.prototype.onTokenizerUpdate = function(startRow, endRow) { - this.renderer.updateLines(startRow, endRow); +ace.Editor.prototype.onTokenizerUpdate = function(e) { + var rows = e.data; + this.renderer.updateLines(rows.first, rows.last); }; ace.Editor.prototype.onCursorChange = function() { From b1a902b196f230e4ae45bcbda2b24273f6b3b570 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 16 Apr 2010 15:46:19 +0200 Subject: [PATCH 081/392] fix background tokenizer --- src/BackgroundTokenizer.js | 10 +++++----- src/Editor.js | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/BackgroundTokenizer.js b/src/BackgroundTokenizer.js index ff681786..d9cfe1f6 100644 --- a/src/BackgroundTokenizer.js +++ b/src/BackgroundTokenizer.js @@ -22,8 +22,7 @@ ace.BackgroundTokenizer = function(tokenizer) { var state = self.currentLine == 0 ? "start" : self.lines[self.currentLine - 1].state; - self.lines[self.currentLine] = self.tokenizer.getLineTokens(line, - state); + self.lines[self.currentLine] = self.tokenizer.getLineTokens(line, state); // only check every 30 lines processedLines += 1; @@ -69,11 +68,12 @@ ace.BackgroundTokenizer.prototype.fireUpdateEvent = function(firstRow, lastRow) ace.BackgroundTokenizer.prototype.start = function(startRow) { this.currentLine = Math.min(startRow || 0, this.currentLine, this.textLines.length); - this.lines.splice(startRow, this.lines.length); + + this.lines.splice(this.currentLine, this.lines.length); if (!this.running) { - this.running = true; - setTimeout(this._worker, 50); + clearTimeout(this.running); + this.running = setTimeout(this._worker, 50); } }; diff --git a/src/Editor.js b/src/Editor.js index 72ef38a3..b286d150 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -129,8 +129,8 @@ ace.Editor.prototype.onBlur = function() { ace.Editor.prototype.onDocumentChange = function(e) { var data = e.data; - this.bgTokenizer.start(data.startRow); - this.renderer.updateLines(data.startRow, data.endRow); + this.bgTokenizer.start(data.firstRow); + this.renderer.updateLines(data.firstRow, data.endRow); }; ace.Editor.prototype.onTokenizerUpdate = function(e) { From 8b110a3e70db1603d2194b653e90f0de7c048c64 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 16 Apr 2010 17:22:22 +0200 Subject: [PATCH 082/392] first CSS tokenizer --- css/editor.css | 14 ++- demo/editor.html | 12 ++- jsTestDriver.conf | 1 + src/Tokenizer.js | 4 +- src/mode/Css.js | 6 ++ src/mode/CssHighlightRules.js | 174 ++++++++++++++++++++++++++++++++++ test/mode/CssTokenizerTest.js | 28 ++++++ 7 files changed, 233 insertions(+), 6 deletions(-) create mode 100644 src/mode/Css.js create mode 100644 src/mode/CssHighlightRules.js create mode 100644 test/mode/CssTokenizerTest.js diff --git a/css/editor.css b/css/editor.css index d30aac52..79d7ac30 100644 --- a/css/editor.css +++ b/css/editor.css @@ -33,7 +33,7 @@ font-size: 12px; -webkit-box-sizing: border-box; - box-sizing: border-box; + box-sizing: border-box; } .layer { @@ -74,6 +74,18 @@ color: blue; } +.line .buildin-constant { + color: rgb(88, 72, 246); +} + +.line .library-constant { + color: rgb(6, 150, 14); +} + +.line .buildin-function { + color: rgb(60, 76, 114); +} + .line .string { color: rgb(3, 106, 7); } diff --git a/demo/editor.html b/demo/editor.html index 9f1e9a7f..7b5e060e 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -38,6 +38,8 @@ + + @@ -65,6 +67,7 @@ + @@ -92,6 +95,7 @@ var modes = { text: new ace.mode.Text(), xml: new ace.mode.Xml(), html: new ace.mode.Html(), + css: new ace.mode.Css(), javascript: new ace.mode.JavaScript() }; @@ -138,15 +142,17 @@ ace.addListener(container, "drop", function(e) { if (window.FileReader) { var reader = new FileReader(); reader.onload = function(e) { - editor.clearSelection(); - editor.moveCursorTo(0, 0); - editor.selectFileEnd(); + editor.getSelection().selectAll(); var mode = "text"; if (/^.*\.js$/i.test(file.name)) { mode = "javascript"; } else if (/^.*\.xml$/i.test(file.name)) { mode = "xml"; + } else if (/^.*\.html$/i.test(file.name)) { + mode = "html"; + } else if (/^.*\.css$/i.test(file.name)) { + mode = "css"; } editor.onTextInput(reader.result); diff --git a/jsTestDriver.conf b/jsTestDriver.conf index 48f4c221..507fb0c7 100644 --- a/jsTestDriver.conf +++ b/jsTestDriver.conf @@ -3,6 +3,7 @@ server: http://localhost:4224 load: - src/ace.js + - src/MEventEmitter.js - src/mode/Text.js - src/mode/*.js - src/*.js diff --git a/src/Tokenizer.js b/src/Tokenizer.js index 8313e900..194ce446 100644 --- a/src/Tokenizer.js +++ b/src/Tokenizer.js @@ -39,7 +39,7 @@ ace.Tokenizer.prototype.getLineTokens = function(line, startState) { if (re.lastIndex == lastIndex) { throw new Error("tokenizer error"); } lastIndex = re.lastIndex; -// window.LOG && jstestdriver.console.log(currentState, match); + window.LOG && jstestdriver.console.log(currentState, match); for ( var i = 0; i < state.length; i++) { if (match[i + 1]) { @@ -79,7 +79,7 @@ ace.Tokenizer.prototype.getLineTokens = function(line, startState) { tokens.push(token); } -// window.LOG && jstestdriver.console.log(tokens, currentState); + window.LOG && jstestdriver.console.log(tokens, currentState); return { tokens : tokens, diff --git a/src/mode/Css.js b/src/mode/Css.js new file mode 100644 index 00000000..d4683c6b --- /dev/null +++ b/src/mode/Css.js @@ -0,0 +1,6 @@ +ace.provide("ace.mode.Css"); + +ace.mode.Css = function() { + this.$tokenizer = new ace.Tokenizer(new ace.mode.CssHighlightRules().getRules()); +}; +ace.inherits(ace.mode.Css, ace.mode.Text); diff --git a/src/mode/CssHighlightRules.js b/src/mode/CssHighlightRules.js new file mode 100644 index 00000000..2044974f --- /dev/null +++ b/src/mode/CssHighlightRules.js @@ -0,0 +1,174 @@ +ace.provide("ace.mode.CssHighlightRules"); + +ace.mode.CssHighlightRules = function() { + + var properties = { + "width": 1, + "height": 1, + "top": 1, + "left": 1, + "right": 1, + "bottom": 1, + "overflow": 1, + "overflow-x": 1, + "overflow-y": 1, + "background": 1, + "font": 1, + "font-style": 1, + "font-family": 1, + "font-size": 1, + "text-align": 1, + "white-space": 1, + "color": 1, + "z-index": 1, + "position": 1, + "cursor": 1, + "box-sizing": 1, + "-webkit-box-sizing": 1, + "-moz-box-sizing": 1, + "margin": 1, + "padding": 1, + "padding-top": 1, + "padding-right": 1, + "padding-bottom": 1, + "padding-left": 1, + "border": 1, + "border-top": 1, + "border-right": 1, + "border-left": 1, + "border-bottom": 1 + }; + + var functions = { + "rgb": 1, + "rgba": 1 + }; + + var constants = { + "absolute": 1, + "relative": 1, + "fixed": 1, + "solid": 1, + "hidden": 1, + "scroll": 1, + "no-wrap": 1 + }; + + // regexp must not have capturing parentheses. Use (?:) instead. + // regexps are ordered -> the first match is used + + var numRe = "\\-?(?:(?:[0-9]+)|(?:[0-9]*\\.[0-9]+))"; + + function ic(str) { + var re = []; + var chars = str.split(""); + for (var i=0; i Date: Fri, 16 Apr 2010 17:30:08 +0200 Subject: [PATCH 083/392] syntax highlighting for CSS embedding in HTML --- src/mode/HtmlHighlightRules.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/mode/HtmlHighlightRules.js b/src/mode/HtmlHighlightRules.js index 2da964e7..5a0902b3 100644 --- a/src/mode/HtmlHighlightRules.js +++ b/src/mode/HtmlHighlightRules.js @@ -21,6 +21,10 @@ ace.mode.HtmlHighlightRules = function() { token : "text", regex : "<(?=\s*script)", next : "script" + }, { + token : "text", + regex : "<(?=\s*style)", + next : "css" }, { token : "text", // opening tag regex : "<\\/?", @@ -51,6 +55,24 @@ ace.mode.HtmlHighlightRules = function() { regex : "'.*?'" } ], + css : [ { + token : "text", + regex : ">", + next : "css-start" + }, { + token : "keyword", + regex : "[-_a-zA-Z0-9:]+" + }, { + token : "text", + regex : "\\s+" + }, { + token : "string", + regex : '".*?"' + }, { + token : "string", + regex : "'.*?'" + } ], + tag : [ { token : "text", regex : ">", @@ -98,6 +120,14 @@ ace.mode.HtmlHighlightRules = function() { regex: "<\\/(?=script)", next: "tag" }); + + var cssRules = new ace.mode.CssHighlightRules().getRules(); + this._addRules(cssRules, "css-"); + this._rules["css-start"].unshift({ + token: "text", + regex: "<\\/(?=style)", + next: "tag" + }); }; From 7869795a1d814cc1ec6ff2cc7679a98116c1f98f Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 19 Apr 2010 13:08:07 +0200 Subject: [PATCH 084/392] IE mouse event and mouse capture fixes --- experiments/capture.html | 32 +++-------------- src/Editor.js | 2 ++ src/TextDocument.js | 8 +++-- src/ace.js | 78 +++++++++++++++++++++++++++++++--------- 4 files changed, 74 insertions(+), 46 deletions(-) diff --git a/experiments/capture.html b/experiments/capture.html index eed0f2f7..9df5e3cd 100644 --- a/experiments/capture.html +++ b/experiments/capture.html @@ -21,16 +21,17 @@
+ diff --git a/src/Editor.js b/src/Editor.js index b286d150..044293b9 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -2,6 +2,7 @@ ace.provide("ace.Editor"); ace.Editor = function(renderer, doc, mode) { var container = renderer.getContainerElement(); + this.container = container; this.renderer = renderer; this.setMode(mode || new ace.mode.Text()); @@ -26,6 +27,7 @@ ace.Editor = function(renderer, doc, mode) { this._initialized = true; }; + ace.Editor.prototype.setDocument = function(doc) { // TODO: document change is not yet supported if (this.doc) { diff --git a/src/TextDocument.js b/src/TextDocument.js index 17c6400c..1c66c0e5 100644 --- a/src/TextDocument.js +++ b/src/TextDocument.js @@ -13,7 +13,7 @@ ace.mixin(ace.TextDocument.prototype, ace.MEventEmitter); ace.TextDocument.prototype._split = function(text) { - return text.split(/[\n\r]/); + return text.split(/\r\n|\r|\n/); }; ace.TextDocument.prototype.toString = function() { @@ -212,7 +212,7 @@ ace.TextDocument.prototype._insert = function(position, text) { var newLines = this._split(text); - if (text == "\n") { + if (this._isNewLine(text)) { var line = this.lines[position.row] || ""; this.lines[position.row] = line.substring(0, position.column); this.lines.splice(position.row + 1, 0, line.substring(position.column)); @@ -251,6 +251,10 @@ ace.TextDocument.prototype._insert = function(position, text) { } }; +ace.TextDocument.prototype._isNewLine = function(text) { + return (text == "\r\n" || text == "\r" || text == "\n"); +}; + ace.TextDocument.prototype.remove = function(range) { this._remove(range); diff --git a/src/ace.js b/src/ace.js index a49b2805..080725f4 100644 --- a/src/ace.js +++ b/src/ace.js @@ -139,26 +139,70 @@ ace.bind = function(fcn, context) { }; }; -ace.capture = function(el, eventHandler, releaseCaptureHandler) { - function onMouseMove(e) { - eventHandler(e); - e.stopPropagation(); +ace.getDocumentX = function(event) { + if (event.clientX) { + var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft; + return event.clientX + scrollLeft; + } else { + return event.pageX; } - - function onMouseUp(e) { - eventHandler && eventHandler(e); - releaseCaptureHandler && releaseCaptureHandler(); - - document.removeEventListener("mousemove", onMouseMove, true); - document.removeEventListener("mouseup", onMouseUp, true); - - e.stopPropagation(); - } - - document.addEventListener("mousemove", onMouseMove, true); - document.addEventListener("mouseup", onMouseUp, true); }; +ace.getDocumentY = function(event) { + if (event.clientY) { + var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; + return event.clientY + scrollTop; + } else { + return event.pageX; + } +}; + +if (document.documentElement.setCapture) { + ace.capture = function(el, eventHandler, releaseCaptureHandler) { + function onMouseMove(e) { + eventHandler(e); + return ace.stopPropagation(e); + } + + function onReleaseCapture(e) { + eventHandler && eventHandler(e); + releaseCaptureHandler && releaseCaptureHandler(); + + ace.removeListener(el, "mousemove", eventHandler); + ace.removeListener(el, "mouseup", onReleaseCapture); + ace.removeListener(el, "losecapture", onReleaseCapture); + + el.releaseCapture(); + } + + ace.addListener(el, "mousemove", eventHandler); + ace.addListener(el, "mouseup", onReleaseCapture); + ace.addListener(el, "losecapture", onReleaseCapture); + el.setCapture(); + }; +} +else { + ace.capture = function(el, eventHandler, releaseCaptureHandler) { + function onMouseMove(e) { + eventHandler(e); + e.stopPropagation(); + } + + function onMouseUp(e) { + eventHandler && eventHandler(e); + releaseCaptureHandler && releaseCaptureHandler(); + + document.removeEventListener("mousemove", onMouseMove, true); + document.removeEventListener("mouseup", onMouseUp, true); + + e.stopPropagation(); + } + + document.addEventListener("mousemove", onMouseMove, true); + document.addEventListener("mouseup", onMouseUp, true); + }; +} + ace.addMouseWheelListener = function(el, callback) { var listener = function(e) { e.wheel = (e.wheelDelta) ? e.wheelDelta / 120 From a9e2986121b827784ac398a1f3219b174556ce2a Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 19 Apr 2010 15:42:10 +0200 Subject: [PATCH 085/392] clean up CSS and make it more IE6 friendly --- css/editor.css | 83 ++-------------------------------------- css/tm.css | 86 ++++++++++++++++++++++++++++++++++++++++++ demo/editor.html | 18 +++++++-- src/Editor.js | 15 ++++---- src/VirtualRenderer.js | 18 +++++++++ 5 files changed, 130 insertions(+), 90 deletions(-) create mode 100644 css/tm.css diff --git a/css/editor.css b/css/editor.css index 79d7ac30..3676c327 100644 --- a/css/editor.css +++ b/css/editor.css @@ -1,116 +1,45 @@ .editor { position: absolute; - border: 2px solid rgb(159, 159, 159); overflow: hidden; } -.editor.focus { - border: 2px solid #327fbd;; -} - .scroller { position: absolute; - left: 50px; - right: 0; - bottom: 0; - top: 0; overflow-x: scroll; overflow-y: hidden; } .gutter { position: absolute; - width: 50px; - top: 0px; - bottom: 0px; overflow-x: scroll; overflow-y: hidden; - - background: rgb(227, 227, 227); - border-right: 1px solid rgb(159, 159, 159); - color: rgb(136, 136, 136); - font-family: Monaco, "Courier New"; - font-size: 12px; - - -webkit-box-sizing: border-box; - box-sizing: border-box; } .layer { position: absolute; overflow: hidden; white-space: nowrap; - -webkit-box-sizing: border-box; - box-sizing: border-box; -} - -.gutter-layer { - right: 0; - text-align: right; - padding-right: 10px; - -webkit-box-sizing: border-box; - box-sizing: border-box; } .text-layer { z-index: 2; font-family: Monaco, "Courier New", monospace; - font-size: 12px; cursor: text; } +.cursor-layer { + z-index: 3; +} + .cursor { z-index: 3; position: absolute; - width: 2px; - background: black; } .line { white-space: nowrap; } -.line .keyword { - color: blue; -} - -.line .buildin-constant { - color: rgb(88, 72, 246); -} - -.line .library-constant { - color: rgb(6, 150, 14); -} - -.line .buildin-function { - color: rgb(60, 76, 114); -} - -.line .string { - color: rgb(3, 106, 7); -} - -.line .comment { - font-style: italic; - color: rgb(76, 136, 107); -} - -.line .doc-comment { - color: rgb(0, 102, 255); -} - -.line .doc-comment-tag { - color: rgb(128, 159, 191); -} - -.line .number { - color: rgb(0, 0, 205); -} - -.line .xml_pe { - color: rgb(104, 104, 91); -} - .marker-layer { z-index: 1; } @@ -118,18 +47,14 @@ .marker-layer .selection { position: absolute; z-index: 2; - background: rgb(181, 213, 255); } .marker-layer .bracket { position: absolute; z-index: 3; - margin: -1px 0 0 -1px; - border: 1px solid rgb(192, 192, 192); } .marker-layer .active_line { position: absolute; z-index: 1; - background: rgb(232, 242, 254); } \ No newline at end of file diff --git a/css/tm.css b/css/tm.css new file mode 100644 index 00000000..b9d8dfd2 --- /dev/null +++ b/css/tm.css @@ -0,0 +1,86 @@ +.editor { + border: 2px solid rgb(159, 159, 159); +} + +.editor.focus { + border: 2px solid #327fbd;; +} + +.gutter { + width: 40px; + background: rgb(227, 227, 227); + border-right: 1px solid rgb(159, 159, 159); + color: rgb(136, 136, 136); + font-family: Monaco, "Courier New"; + font-size: 12px; +} + +.gutter-layer { + right: 10px; + text-align: right; +} + +.text-layer { + font-family: Monaco, "Courier New", monospace; + font-size: 12px; + cursor: text; +} + +.cursor { + width: 2px; + background: black; +} + +.line .keyword { + color: blue; +} + +.line .buildin-constant { + color: rgb(88, 72, 246); +} + +.line .library-constant { + color: rgb(6, 150, 14); +} + +.line .buildin-function { + color: rgb(60, 76, 114); +} + +.line .string { + color: rgb(3, 106, 7); +} + +.line .comment { + font-style: italic; + color: rgb(76, 136, 107); +} + +.line .doc-comment { + color: rgb(0, 102, 255); +} + +.line .doc-comment-tag { + color: rgb(128, 159, 191); +} + +.line .number { + color: rgb(0, 0, 205); +} + +.line .xml_pe { + color: rgb(104, 104, 91); +} + +.marker-layer .selection { + background: rgb(181, 213, 255); +} + +.marker-layer .bracket { + margin: -1px 0 0 -1px; + border: 1px solid rgb(192, 192, 192); +} + +.marker-layer .active_line { + background: rgb(232, 242, 254); +} \ No newline at end of file diff --git a/demo/editor.html b/demo/editor.html index 7b5e060e..0a05bf0f 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -9,16 +9,20 @@ + @@ -124,10 +129,15 @@ var editor = new ace.Editor( getMode() ); -window.onresize = function() { - editor.resize(); +function onResize() { + container.style.width = (document.documentElement.clientWidth - 4) + "px"; + container.style.height = (document.documentElement.clientHeight - 30 - 4) + "px"; + editor.resize(); }; +window.onresize = onResize; +onResize(); + ace.addListener(container, "dragover", function(e) { return ace.preventDefault(e); }); diff --git a/src/Editor.js b/src/Editor.js index 044293b9..496375c3 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -82,10 +82,8 @@ ace.Editor.prototype.setMode = function(mode) { }; -ace.Editor.prototype.resize = function() -{ - this.renderer.scrollToY(this.renderer.getScrollTop()); - this.renderer.draw(); +ace.Editor.prototype.resize = function() { + this.renderer.onResize(); }; ace.Editor.prototype._highlightBrackets = function() { @@ -190,7 +188,10 @@ ace.Editor.prototype.onSelectionChange = function() { ace.Editor.prototype.onMouseDown = function(e) { this.textInput.focus(); - var pos = this.renderer.screenToTextCoordinates(e.pageX, e.pageY); + var pageX = ace.getDocumentX(e); + var pageY = ace.getDocumentY(e); + + var pos = this.renderer.screenToTextCoordinates(pageX, pageY); this.moveCursorToPosition(pos); this.selection.setSelectionAnchor(pos.row, pos.column); this.renderer.scrollCursorIntoView(); @@ -199,8 +200,8 @@ ace.Editor.prototype.onMouseDown = function(e) { var mousePageX, mousePageY; var onMouseSelection = function(e) { - mousePageX = e.pageX; - mousePageY = e.pageY; + mousePageX = ace.getDocumentX(e); + mousePageY = ace.getDocumentY(e); }; var onMouseSelectionEnd = function() { diff --git a/src/VirtualRenderer.js b/src/VirtualRenderer.js index 7f9b6cd9..0c421b1c 100644 --- a/src/VirtualRenderer.js +++ b/src/VirtualRenderer.js @@ -31,6 +31,8 @@ ace.VirtualRenderer = function(container) { row : 0, column : 0 }; + + this.onResize(); }; ace.VirtualRenderer.prototype.setDocument = function(doc) { @@ -56,6 +58,22 @@ ace.VirtualRenderer.prototype.getLastVisibleRow = function() { return this.layerConfig.lastRow || 0; }; +ace.VirtualRenderer.prototype.onResize = function() +{ + var height = ace.getInnerHeight(this.container); + this.gutter.style.height = height + "px"; + this.scroller.style.height = height + "px"; + + var width = ace.getInnerWidth(this.container); + var gutterWidth = this.gutter.offsetWidth; + this.scroller.style.left = gutterWidth + "px"; + this.scroller.style.width = Math.max(0, width - gutterWidth) + "px"; + + if (this.doc) { + this.scrollToY(this.getScrollTop()); + } +}; + ace.VirtualRenderer.prototype.updateLines = function(firstRow, lastRow) { var layerConfig = this.layerConfig; From cb8d415f87086daf13732b5e9853b6e18741d1ac Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 19 Apr 2010 17:01:25 +0200 Subject: [PATCH 086/392] fix performance issues --- src/BackgroundTokenizer.js | 2 +- src/CursorLayer.js | 10 ++++++++++ src/Editor.js | 2 +- src/VirtualRenderer.js | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/BackgroundTokenizer.js b/src/BackgroundTokenizer.js index d9cfe1f6..560d7a8b 100644 --- a/src/BackgroundTokenizer.js +++ b/src/BackgroundTokenizer.js @@ -73,7 +73,7 @@ ace.BackgroundTokenizer.prototype.start = function(startRow) { if (!this.running) { clearTimeout(this.running); - this.running = setTimeout(this._worker, 50); + this.running = setTimeout(this._worker, 200); } }; diff --git a/src/CursorLayer.js b/src/CursorLayer.js index dcf4c2a7..b47f6af4 100644 --- a/src/CursorLayer.js +++ b/src/CursorLayer.js @@ -32,7 +32,16 @@ ace.CursorLayer.prototype.showCursor = function() { var cursor = this.cursor; cursor.style.visibility = "visible"; + this.restartTimer(); +}; +ace.CursorLayer.prototype.restartTimer = function() { + clearInterval(this.blinkId); + if (!this.isVisible) { + return; + } + + var cursor = this.cursor; this.blinkId = setInterval(function() { cursor.style.visibility = "hidden"; setTimeout(function() { @@ -68,4 +77,5 @@ ace.CursorLayer.prototype.update = function(config) { if (this.isVisible) { this.element.appendChild(this.cursor); } + this.restartTimer(); }; \ No newline at end of file diff --git a/src/Editor.js b/src/Editor.js index 496375c3..70b4ee10 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -130,7 +130,7 @@ ace.Editor.prototype.onBlur = function() { ace.Editor.prototype.onDocumentChange = function(e) { var data = e.data; this.bgTokenizer.start(data.firstRow); - this.renderer.updateLines(data.firstRow, data.endRow); + this.renderer.updateLines(data.firstRow, data.lastRow); }; ace.Editor.prototype.onTokenizerUpdate = function(e) { diff --git a/src/VirtualRenderer.js b/src/VirtualRenderer.js index 0c421b1c..8c92c961 100644 --- a/src/VirtualRenderer.js +++ b/src/VirtualRenderer.js @@ -77,8 +77,8 @@ ace.VirtualRenderer.prototype.onResize = function() ace.VirtualRenderer.prototype.updateLines = function(firstRow, lastRow) { var layerConfig = this.layerConfig; - // if the first row is below the viewport -> ignore it if (firstRow > layerConfig.lastRow + 1) { return; } + if (lastRow < layerConfig.firstRow) { return; } // if the last row is unknow -> redraw everything if (lastRow === undefined) { From 935859b60e00f704b68d102a2218295d90bad10d Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 19 Apr 2010 17:47:23 +0200 Subject: [PATCH 087/392] line numbers are 1-based --- src/Editor.js | 4 ++-- src/GutterLayer.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Editor.js b/src/Editor.js index 70b4ee10..558dd48f 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -496,11 +496,11 @@ ace.Editor.prototype.moveCursorToPosition = function(pos) { ace.Editor.prototype.gotoLine = function(lineNumber) { this._blockScrolling = true; - this.moveCursorTo(lineNumber, 0); + this.moveCursorTo(lineNumber-1, 0); this._blockScrolling = false; if (!this.isRowVisible(this.getCursorPosition().row)) { - this.scrollToRow(lineNumber - Math.floor(this.getVisibleRowCount() / 2)); + this.scrollToRow(lineNumber - 1 - Math.floor(this.getVisibleRowCount() / 2)); } }, diff --git a/src/GutterLayer.js b/src/GutterLayer.js index 4256a4e9..b2499f6d 100644 --- a/src/GutterLayer.js +++ b/src/GutterLayer.js @@ -10,7 +10,7 @@ ace.GutterLayer.prototype.update = function(config) { var html = []; for ( var i = config.firstRow; i <= config.lastRow; i++) { html.push("
", i, "
"); + + "px;'>", (i+1), ""); html.push(""); } From 07f2f8081b6c877a6e76a314e03a21b9a3bdd715 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 19 Apr 2010 17:49:28 +0200 Subject: [PATCH 088/392] remove unused markup from generated HTML --- src/TextLayer.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/TextLayer.js b/src/TextLayer.js index 6af0eb58..e0d5cc1d 100644 --- a/src/TextLayer.js +++ b/src/TextLayer.js @@ -64,8 +64,7 @@ ace.TextLayer.prototype.updateLines = function(layerConfig, firstRow, lastRow) { ace.TextLayer.prototype.update = function(config) { var html = []; for ( var i = config.firstRow; i <= config.lastRow; i++) { - html.push("
"); this.renderLine(html, i), html.push("
"); } From 40299a28e3df28d10db8b08f35e0a131bfea006c Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 19 Apr 2010 17:51:37 +0200 Subject: [PATCH 089/392] minor --- src/BackgroundTokenizer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/BackgroundTokenizer.js b/src/BackgroundTokenizer.js index 560d7a8b..06ed59ac 100644 --- a/src/BackgroundTokenizer.js +++ b/src/BackgroundTokenizer.js @@ -73,6 +73,7 @@ ace.BackgroundTokenizer.prototype.start = function(startRow) { if (!this.running) { clearTimeout(this.running); + // pretty long delay to prevent the tokenizer from interfering with the user this.running = setTimeout(this._worker, 200); } }; From 9f2ecbdb049adbc3ac6fa06c0d5d38b2c1f36d02 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 19 Apr 2010 18:28:24 +0200 Subject: [PATCH 090/392] fix text layer --- src/TextLayer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TextLayer.js b/src/TextLayer.js index e0d5cc1d..c9700d38 100644 --- a/src/TextLayer.js +++ b/src/TextLayer.js @@ -64,7 +64,7 @@ ace.TextLayer.prototype.updateLines = function(layerConfig, firstRow, lastRow) { ace.TextLayer.prototype.update = function(config) { var html = []; for ( var i = config.firstRow; i <= config.lastRow; i++) { - html.push("
"); this.renderLine(html, i), html.push("
"); } From ba025f3e8726942b3b682f482071e1ac392b3287 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 20 Apr 2010 12:06:47 +0200 Subject: [PATCH 091/392] improve CSS and HTML auto indent --- src/mode/Css.js | 17 ++++++++++++++ src/mode/CssHighlightRules.js | 6 +++++ src/mode/Html.js | 11 ++++++++++ src/mode/JavaScript.js | 22 +++++++------------ src/mode/JavaScriptHighlightRules.js | 10 ++++----- src/mode/Text.js | 9 ++++++++ test/mode/CssTest.js | 33 ++++++++++++++++++++++++++++ test/mode/CssTokenizerTest.js | 9 ++++++++ test/mode/JavaScriptTokenizerTest.js | 11 ++++++++++ test/mode/TextTest.js | 23 +++++++++++++++++++ 10 files changed, 132 insertions(+), 19 deletions(-) create mode 100644 test/mode/CssTest.js create mode 100644 test/mode/TextTest.js diff --git a/src/mode/Css.js b/src/mode/Css.js index d4683c6b..b8c06461 100644 --- a/src/mode/Css.js +++ b/src/mode/Css.js @@ -4,3 +4,20 @@ ace.mode.Css = function() { this.$tokenizer = new ace.Tokenizer(new ace.mode.CssHighlightRules().getRules()); }; ace.inherits(ace.mode.Css, ace.mode.Text); + +ace.mode.Css.prototype.getNextLineIndent = function(line, state, tab) { + var indent = this.$getIndent(line); + + // ignore braces in comments + var tokens = this.$tokenizer.getLineTokens(line, state).tokens; + if (tokens.length && tokens[tokens.length-1].type == "comment") { + return indent; + } + + var match = line.match(/^.*\{\s*$/); + if (match) { + indent += tab; + } + + return indent; +}; \ No newline at end of file diff --git a/src/mode/CssHighlightRules.js b/src/mode/CssHighlightRules.js index 2044974f..35376d1e 100644 --- a/src/mode/CssHighlightRules.js +++ b/src/mode/CssHighlightRules.js @@ -141,6 +141,12 @@ ace.mode.CssHighlightRules = function() { }, { token : "number", // hex3 color regex : "#[a-fA-F0-9]{3}" + }, { + token : "lparen", + regex : "\{" + }, { + token : "rparen", + regex : "\}" }, { token : function(value) { if (properties[value.toLowerCase()]) { diff --git a/src/mode/Html.js b/src/mode/Html.js index 036e0656..631aa038 100644 --- a/src/mode/Html.js +++ b/src/mode/Html.js @@ -4,6 +4,7 @@ ace.mode.Html = function() { this.$tokenizer = new ace.Tokenizer(new ace.mode.HtmlHighlightRules().getRules()); this._js = new ace.mode.JavaScript(); + this._css = new ace.mode.Css(); }; ace.inherits(ace.mode.Html, ace.mode.Text); @@ -13,6 +14,11 @@ ace.mode.Html.prototype.toggleCommentLines = function(doc, range, state) { return this._js.toggleCommentLines(doc, range, state); } + var split = state.split("css-"); + if (!split[0] && split[1]) { + return this._css.toggleCommentLines(doc, range, state); + } + return 0; }; @@ -22,5 +28,10 @@ ace.mode.Html.prototype.getNextLineIndent = function(line, state, tab) { return this._js.getNextLineIndent(line, split[1], tab); } + var split = state.split("css-"); + if (!split[0] && split[1]) { + return this._css.getNextLineIndent(line, split[1], tab); + } + return ""; }; \ No newline at end of file diff --git a/src/mode/JavaScript.js b/src/mode/JavaScript.js index 71c1a2f6..017570aa 100644 --- a/src/mode/JavaScript.js +++ b/src/mode/JavaScript.js @@ -14,28 +14,22 @@ ace.mode.JavaScript.prototype.toggleCommentLines = function(doc, range, state) { }; ace.mode.JavaScript.prototype.getNextLineIndent = function(line, state, tab) { + var indent = this.$getIndent(line); + if (state == "start") { - var re = /^(\s*).*[\{\(\[]\s*$/; - var match = line.match(re); + var match = line.match(/^.*[\{\(\[]\s*$/); if (match) { - return (match[1] || "") + tab; + indent += tab; } } else if (state == "doc-comment") { - var re = /^(\s*)(\/?)\*.*$/; - var match = line.match(re); + var match = line.match(/^\s*(\/?)\*/); if (match) { - var indent = match[1]; - if (match[2]) { + if (match[1]) { indent += " "; } - return indent + "* "; + indent += "* "; } } - var match = line.match(/^(\s+).*$/); - if (match) { - return match[1]; - } - - return ""; + return indent; }; \ No newline at end of file diff --git a/src/mode/JavaScriptHighlightRules.js b/src/mode/JavaScriptHighlightRules.js index 661dd168..61819ac1 100644 --- a/src/mode/JavaScriptHighlightRules.js +++ b/src/mode/JavaScriptHighlightRules.js @@ -74,11 +74,11 @@ ace.mode.JavaScriptHighlightRules = function() { }, regex : "[a-zA-Z_][a-zA-Z0-9_]*\\b" }, { - token : function(value) { - // return parens[value]; - return "text"; - }, - regex : "[\\[\\]\\(\\)\\{\\}]" + token : "lparen", + regex : "[\\[\\(\\{]" + }, { + token : "rparen", + regex : "[\\]\\)\\}]" }, { token : "text", regex : "\\s+" diff --git a/src/mode/Text.js b/src/mode/Text.js index 99d0f537..5baf0de8 100644 --- a/src/mode/Text.js +++ b/src/mode/Text.js @@ -19,5 +19,14 @@ ace.mode.Text.prototype.toggleCommentLines = function(doc, range, state) { }; ace.mode.Text.prototype.getNextLineIndent = function(line, state, tab) { + return ""; +}; + +ace.mode.Text.prototype.$getIndent = function(line) { + var match = line.match(/^(\s+)/); + if (match) { + return match[1]; + } + return ""; }; \ No newline at end of file diff --git a/test/mode/CssTest.js b/test/mode/CssTest.js new file mode 100644 index 00000000..c7abdc0c --- /dev/null +++ b/test/mode/CssTest.js @@ -0,0 +1,33 @@ +var CssTest = new TestCase("mode.CssTest", { + + setUp : function() { + this.mode = new ace.mode.Css(); + }, + + "test: toggle comment lines should not do anything" : function() { + var doc = new ace.TextDocument([" abc", "cde", "fg"].join("\n")); + + var range = { + start: {row: 0, column: 3}, + end: {row: 1, column: 1} + }; + + var comment = this.mode.toggleCommentLines(doc, range, "start"); + assertEquals([" abc", "cde", "fg"].join("\n"), doc.toString()); + }, + + + "test: lines should keep indentation" : function() { + assertEquals(" ", this.mode.getNextLineIndent(" abc", "start", " ")); + assertEquals("\t", this.mode.getNextLineIndent("\tabc", "start", " ")); + }, + + "test: new line after { should increase indent" : function() { + assertEquals(" ", this.mode.getNextLineIndent(" abc{", "start", " ")); + assertEquals("\t ", this.mode.getNextLineIndent("\tabc { ", "start", " ")); + }, + + "test: no indent increase after { in a comment" : function() { + assertEquals(" ", this.mode.getNextLineIndent(" /*{", "start", " ")); + } +}); \ No newline at end of file diff --git a/test/mode/CssTokenizerTest.js b/test/mode/CssTokenizerTest.js index 3d84af00..56d1ce0f 100644 --- a/test/mode/CssTokenizerTest.js +++ b/test/mode/CssTokenizerTest.js @@ -24,5 +24,14 @@ var CssTest = new TestCase("mode.CssTest", { assertEquals(1, tokens.length); assertEquals("number", tokens[0].type); + }, + + "test: tokenize parens" : function() { + var tokens = this.tokenizer.getLineTokens("{()}", "start").tokens; + + assertEquals(3, tokens.length); + assertEquals("lparen", tokens[0].type); + assertEquals("text", tokens[1].type); + assertEquals("rparen", tokens[2].type); } }); \ No newline at end of file diff --git a/test/mode/JavaScriptTokenizerTest.js b/test/mode/JavaScriptTokenizerTest.js index 9c0cd8b3..60f628e4 100644 --- a/test/mode/JavaScriptTokenizerTest.js +++ b/test/mode/JavaScriptTokenizerTest.js @@ -37,5 +37,16 @@ var JavaScriptTokenizerTest = new TestCase("mode.JavaScriptTokenizerTest", { assertEquals("doc-comment", tokens[0].type); assertEquals("doc-comment-tag", tokens[1].type); assertEquals("doc-comment", tokens[2].type); + }, + + "test: tokenize parens" : function() { + var line = "[{( )}]"; + + var tokens = this.tokenizer.getLineTokens(line, "start").tokens; + + assertEquals(3, tokens.length); + assertEquals("lparen", tokens[0].type); + assertEquals("text", tokens[1].type); + assertEquals("rparen", tokens[2].type); } }); \ No newline at end of file diff --git a/test/mode/TextTest.js b/test/mode/TextTest.js new file mode 100644 index 00000000..63418d7e --- /dev/null +++ b/test/mode/TextTest.js @@ -0,0 +1,23 @@ +var TextTest = new TestCase("mode.TextTest", { + + setUp : function() { + this.mode = new ace.mode.Text(); + }, + + "test: toggle comment lines should not do anything" : function() { + var doc = new ace.TextDocument([" abc", "cde", "fg"].join("\n")); + + var range = { + start: {row: 0, column: 3}, + end: {row: 1, column: 1} + }; + + var comment = this.mode.toggleCommentLines(doc, range, "start"); + assertEquals([" abc", "cde", "fg"].join("\n"), doc.toString()); + }, + + + "text: lines should not be indented" : function() { + assertEquals("", this.mode.getNextLineIndent(" abc", " ")); + } +}); \ No newline at end of file From 328dfacf4792babccde9a85cc9f5bf6a29a78bc9 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 20 Apr 2010 12:06:56 +0200 Subject: [PATCH 092/392] goto line is 1 based --- test/NavigationTest.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/NavigationTest.js b/test/NavigationTest.js index 993c4177..f6baab0a 100644 --- a/test/NavigationTest.js +++ b/test/NavigationTest.js @@ -31,32 +31,32 @@ var NavigationTest = TestCase("NavigationTest", var editor = new ace.Editor(new MockRenderer(), this.createTextDocument(200, 5)); editor.navigateTo(0, 0); - editor.gotoLine(100); + editor.gotoLine(101); assertPosition(100, 0, editor.getSelection().getCursor()); assertEquals(90, editor.getFirstVisibleRow()); editor.navigateTo(100, 0); - editor.gotoLine(10); + editor.gotoLine(11); assertPosition(10, 0, editor.getSelection().getCursor()); assertEquals(0, editor.getFirstVisibleRow()); editor.navigateTo(100, 0); - editor.gotoLine(5); + editor.gotoLine(6); assertPosition(5, 0, editor.getSelection().getCursor()); assertEquals(0, editor.getFirstVisibleRow()); editor.navigateTo(100, 0); - editor.gotoLine(0); + editor.gotoLine(1); assertPosition(0, 0, editor.getSelection().getCursor()); assertEquals(0, editor.getFirstVisibleRow()); editor.navigateTo(0, 0); - editor.gotoLine(190); + editor.gotoLine(191); assertPosition(190, 0, editor.getSelection().getCursor()); assertEquals(180, editor.getFirstVisibleRow()); editor.navigateTo(0, 0); - editor.gotoLine(195); + editor.gotoLine(196); assertPosition(195, 0, editor.getSelection().getCursor()); assertEquals(180, editor.getFirstVisibleRow()); }, @@ -65,12 +65,12 @@ var NavigationTest = TestCase("NavigationTest", var editor = new ace.Editor(new MockRenderer(), this.createTextDocument(200, 5)); editor.navigateTo(0, 0); - editor.gotoLine(11); + editor.gotoLine(12); assertPosition(11, 0, editor.getSelection().getCursor()); assertEquals(0, editor.getFirstVisibleRow()); editor.navigateTo(30, 0); - editor.gotoLine(32); + editor.gotoLine(33); assertPosition(32, 0, editor.getSelection().getCursor()); assertEquals(30, editor.getFirstVisibleRow()); } From aecaea3ef17c70c2d1c88673f3baa8cb50f7a7b0 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 20 Apr 2010 14:38:01 +0200 Subject: [PATCH 093/392] changing the document is now possible --- demo/editor.html | 85 +++++++++++++++++++++++++---- src/Editor.js | 98 ++++++++++++++++++--------------- src/MEventEmitter.js | 15 +++++- src/TextDocument.js | 26 +++++++-- test/ChangeDocumentTest.js | 107 +++++++++++++++++++++++++++++++++++++ test/TextEditTest.js | 8 +-- 6 files changed, 276 insertions(+), 63 deletions(-) create mode 100644 test/ChangeDocumentTest.js diff --git a/demo/editor.html b/demo/editor.html index 0a05bf0f..b7b0d7a2 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -65,6 +65,14 @@ + +
+ + + + +
-
+
- + @@ -140,11 +140,11 @@ - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BackgroundTokenizer.js b/src/ace/BackgroundTokenizer.js similarity index 100% rename from src/BackgroundTokenizer.js rename to src/ace/BackgroundTokenizer.js diff --git a/src/Document.js b/src/ace/Document.js similarity index 100% rename from src/Document.js rename to src/ace/Document.js diff --git a/src/Editor.js b/src/ace/Editor.js similarity index 100% rename from src/Editor.js rename to src/ace/Editor.js diff --git a/src/KeyBinding.js b/src/ace/KeyBinding.js similarity index 100% rename from src/KeyBinding.js rename to src/ace/KeyBinding.js diff --git a/src/MEventEmitter.js b/src/ace/MEventEmitter.js similarity index 100% rename from src/MEventEmitter.js rename to src/ace/MEventEmitter.js diff --git a/src/ScrollBar.js b/src/ace/ScrollBar.js similarity index 100% rename from src/ScrollBar.js rename to src/ace/ScrollBar.js diff --git a/src/Selection.js b/src/ace/Selection.js similarity index 100% rename from src/Selection.js rename to src/ace/Selection.js diff --git a/src/TextInput.js b/src/ace/TextInput.js similarity index 100% rename from src/TextInput.js rename to src/ace/TextInput.js diff --git a/src/Tokenizer.js b/src/ace/Tokenizer.js similarity index 100% rename from src/Tokenizer.js rename to src/ace/Tokenizer.js diff --git a/src/VirtualRenderer.js b/src/ace/VirtualRenderer.js similarity index 100% rename from src/VirtualRenderer.js rename to src/ace/VirtualRenderer.js diff --git a/src/ace.js b/src/ace/ace.js similarity index 100% rename from src/ace.js rename to src/ace/ace.js diff --git a/src/layer/Cursor.js b/src/ace/layer/Cursor.js similarity index 100% rename from src/layer/Cursor.js rename to src/ace/layer/Cursor.js diff --git a/src/layer/Gutter.js b/src/ace/layer/Gutter.js similarity index 100% rename from src/layer/Gutter.js rename to src/ace/layer/Gutter.js diff --git a/src/layer/Marker.js b/src/ace/layer/Marker.js similarity index 100% rename from src/layer/Marker.js rename to src/ace/layer/Marker.js diff --git a/src/layer/Text.js b/src/ace/layer/Text.js similarity index 100% rename from src/layer/Text.js rename to src/ace/layer/Text.js diff --git a/src/mode/Css.js b/src/ace/mode/Css.js similarity index 100% rename from src/mode/Css.js rename to src/ace/mode/Css.js diff --git a/src/mode/CssHighlightRules.js b/src/ace/mode/CssHighlightRules.js similarity index 100% rename from src/mode/CssHighlightRules.js rename to src/ace/mode/CssHighlightRules.js diff --git a/src/mode/Html.js b/src/ace/mode/Html.js similarity index 100% rename from src/mode/Html.js rename to src/ace/mode/Html.js diff --git a/src/mode/HtmlHighlightRules.js b/src/ace/mode/HtmlHighlightRules.js similarity index 100% rename from src/mode/HtmlHighlightRules.js rename to src/ace/mode/HtmlHighlightRules.js diff --git a/src/mode/JavaScript.js b/src/ace/mode/JavaScript.js similarity index 100% rename from src/mode/JavaScript.js rename to src/ace/mode/JavaScript.js diff --git a/src/mode/JavaScriptHighlightRules.js b/src/ace/mode/JavaScriptHighlightRules.js similarity index 100% rename from src/mode/JavaScriptHighlightRules.js rename to src/ace/mode/JavaScriptHighlightRules.js diff --git a/src/mode/Text.js b/src/ace/mode/Text.js similarity index 100% rename from src/mode/Text.js rename to src/ace/mode/Text.js diff --git a/src/mode/Xml.js b/src/ace/mode/Xml.js similarity index 100% rename from src/mode/Xml.js rename to src/ace/mode/Xml.js diff --git a/src/mode/XmlHighlightRules.js b/src/ace/mode/XmlHighlightRules.js similarity index 100% rename from src/mode/XmlHighlightRules.js rename to src/ace/mode/XmlHighlightRules.js From a13bb1db4acb0f7e8821a7e110e2c3a70867959f Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 26 Apr 2010 12:04:17 +0200 Subject: [PATCH 118/392] move unit tests into the source folder --- jsTestDriver.conf | 16 ++++++++-------- {test => src/test}/ChangeDocumentTest.js | 0 {test => src/test}/DocumentTest.js | 0 {test => src/test}/EventEmitterTest.js | 0 {test => src/test}/MockRenderer.js | 0 {test => src/test}/NavigationTest.js | 0 {test => src/test}/SelectionTest.js | 0 {test => src/test}/TextEditTest.js | 0 {test => src/test}/VirtualRendererTest.js | 0 {test => src/test}/assertions.js | 0 {test => src/test}/mode/CssTest.js | 0 {test => src/test}/mode/CssTokenizerTest.js | 0 {test => src/test}/mode/HtmlTokenizerTest.js | 0 {test => src/test}/mode/JavaScriptTest.js | 0 .../test}/mode/JavaScriptTokenizerTest.js | 0 {test => src/test}/mode/TextTest.js | 0 {test => src/test}/mode/XmlTest.js | 0 {test => src/test}/mode/XmlTokenizerTest.js | 0 18 files changed, 8 insertions(+), 8 deletions(-) rename {test => src/test}/ChangeDocumentTest.js (100%) rename {test => src/test}/DocumentTest.js (100%) rename {test => src/test}/EventEmitterTest.js (100%) rename {test => src/test}/MockRenderer.js (100%) rename {test => src/test}/NavigationTest.js (100%) rename {test => src/test}/SelectionTest.js (100%) rename {test => src/test}/TextEditTest.js (100%) rename {test => src/test}/VirtualRendererTest.js (100%) rename {test => src/test}/assertions.js (100%) rename {test => src/test}/mode/CssTest.js (100%) rename {test => src/test}/mode/CssTokenizerTest.js (100%) rename {test => src/test}/mode/HtmlTokenizerTest.js (100%) rename {test => src/test}/mode/JavaScriptTest.js (100%) rename {test => src/test}/mode/JavaScriptTokenizerTest.js (100%) rename {test => src/test}/mode/TextTest.js (100%) rename {test => src/test}/mode/XmlTest.js (100%) rename {test => src/test}/mode/XmlTokenizerTest.js (100%) diff --git a/jsTestDriver.conf b/jsTestDriver.conf index 48db8236..7d00e89d 100644 --- a/jsTestDriver.conf +++ b/jsTestDriver.conf @@ -2,12 +2,12 @@ server: http://localhost:4224 load: - - src/ace.js - - src/MEventEmitter.js - - src/mode/Text.js - - src/mode/*.js - - src/layer/*.js - - src/*.js + - src/ace/ace.js + - src/ace/MEventEmitter.js + - src/ace/mode/Text.js + - src/ace/mode/*.js + - src/ace/layer/*.js + - src/ace/*.js - - test/*.js - - test/mode/*.js \ No newline at end of file + - src/test/*.js + - src/test/mode/*.js \ No newline at end of file diff --git a/test/ChangeDocumentTest.js b/src/test/ChangeDocumentTest.js similarity index 100% rename from test/ChangeDocumentTest.js rename to src/test/ChangeDocumentTest.js diff --git a/test/DocumentTest.js b/src/test/DocumentTest.js similarity index 100% rename from test/DocumentTest.js rename to src/test/DocumentTest.js diff --git a/test/EventEmitterTest.js b/src/test/EventEmitterTest.js similarity index 100% rename from test/EventEmitterTest.js rename to src/test/EventEmitterTest.js diff --git a/test/MockRenderer.js b/src/test/MockRenderer.js similarity index 100% rename from test/MockRenderer.js rename to src/test/MockRenderer.js diff --git a/test/NavigationTest.js b/src/test/NavigationTest.js similarity index 100% rename from test/NavigationTest.js rename to src/test/NavigationTest.js diff --git a/test/SelectionTest.js b/src/test/SelectionTest.js similarity index 100% rename from test/SelectionTest.js rename to src/test/SelectionTest.js diff --git a/test/TextEditTest.js b/src/test/TextEditTest.js similarity index 100% rename from test/TextEditTest.js rename to src/test/TextEditTest.js diff --git a/test/VirtualRendererTest.js b/src/test/VirtualRendererTest.js similarity index 100% rename from test/VirtualRendererTest.js rename to src/test/VirtualRendererTest.js diff --git a/test/assertions.js b/src/test/assertions.js similarity index 100% rename from test/assertions.js rename to src/test/assertions.js diff --git a/test/mode/CssTest.js b/src/test/mode/CssTest.js similarity index 100% rename from test/mode/CssTest.js rename to src/test/mode/CssTest.js diff --git a/test/mode/CssTokenizerTest.js b/src/test/mode/CssTokenizerTest.js similarity index 100% rename from test/mode/CssTokenizerTest.js rename to src/test/mode/CssTokenizerTest.js diff --git a/test/mode/HtmlTokenizerTest.js b/src/test/mode/HtmlTokenizerTest.js similarity index 100% rename from test/mode/HtmlTokenizerTest.js rename to src/test/mode/HtmlTokenizerTest.js diff --git a/test/mode/JavaScriptTest.js b/src/test/mode/JavaScriptTest.js similarity index 100% rename from test/mode/JavaScriptTest.js rename to src/test/mode/JavaScriptTest.js diff --git a/test/mode/JavaScriptTokenizerTest.js b/src/test/mode/JavaScriptTokenizerTest.js similarity index 100% rename from test/mode/JavaScriptTokenizerTest.js rename to src/test/mode/JavaScriptTokenizerTest.js diff --git a/test/mode/TextTest.js b/src/test/mode/TextTest.js similarity index 100% rename from test/mode/TextTest.js rename to src/test/mode/TextTest.js diff --git a/test/mode/XmlTest.js b/src/test/mode/XmlTest.js similarity index 100% rename from test/mode/XmlTest.js rename to src/test/mode/XmlTest.js diff --git a/test/mode/XmlTokenizerTest.js b/src/test/mode/XmlTokenizerTest.js similarity index 100% rename from test/mode/XmlTokenizerTest.js rename to src/test/mode/XmlTokenizerTest.js From 7212563e3a4974a5ab9dc079e2df828b083cac76 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 26 Apr 2010 13:44:38 +0200 Subject: [PATCH 119/392] fix repeated key events for firefox --- src/ace/KeyBinding.js | 2 +- src/ace/ace.js | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/ace/KeyBinding.js b/src/ace/KeyBinding.js index f105cb18..fe7e9e8d 100644 --- a/src/ace/KeyBinding.js +++ b/src/ace/KeyBinding.js @@ -4,7 +4,7 @@ ace.KeyBinding = function(element, editor) { var keys = this.keys; - ace.addListener(element, "keydown", function(e) { + ace.addKeyListener(element, function(e) { var key = e.keyCode; var selection = editor.getSelection(); diff --git a/src/ace/ace.js b/src/ace/ace.js index ba1ad530..f05f9416 100644 --- a/src/ace/ace.js +++ b/src/ace/ace.js @@ -272,4 +272,22 @@ ace.addTripleClickListener = function(el, callback) { ace.autoremoveListener(el, "mousedown", callback, 300); }, 300); }); +}; + +ace.addKeyListener = function(el, callback) { + var lastDown = null; + + ace.addListener(el, "keydown", function(e) { + lastDown = e.keyCode; + return callback(e); + }); + + ace.addListener(el, "keypress", function(e) { + var keyId = e.keyCode; + if (lastDown !== keyId) { + return callback(e); + } else { + lastDown = null; + } + }); }; \ No newline at end of file From ff9485f617e3ffe3f78291948378b7ca793f8fcc Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 26 Apr 2010 15:09:55 +0200 Subject: [PATCH 120/392] move page down selection back into the editor because it requires knowledge about the visible viewport --- src/ace/Editor.js | 22 ++++++++++++++++++++++ src/ace/Selection.js | 21 --------------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/ace/Editor.js b/src/ace/Editor.js index 40ee1bcb..671ff918 100644 --- a/src/ace/Editor.js +++ b/src/ace/Editor.js @@ -547,6 +547,28 @@ ace.Editor = function(renderer, doc) { return firstRow - (lastRow - firstRow) + 1; }; + this.selectPageDown = function() { + var row = this.getPageDownRow() + Math.floor(this.getVisibleRowCount() / 2); + + this.scrollPageDown(); + + var selection = this.getSelection(); + selection.$moveSelection(function() { + selection.moveCursorTo(row, selection.getSelectionLead().column); + }); + }; + + this.selectPageUp = function() { + var visibleRows = this.getLastVisibleRow() - this.getFirstVisibleRow(); + var row = this.getPageUpRow() + Math.round(visibleRows / 2); + + this.scrollPageUp(); + + var selection = this.getSelection(); + selection.$moveSelection(function() { + selection.moveCursorTo(row, selection.getSelectionLead().column); + }); + }; this.scrollPageDown = function() { this.scrollToRow(this.getPageDownRow()); diff --git a/src/ace/Selection.js b/src/ace/Selection.js index f1048851..e209748f 100644 --- a/src/ace/Selection.js +++ b/src/ace/Selection.js @@ -157,27 +157,6 @@ ace.Selection = function(doc) { this.$moveSelection(this.moveCursorLineEnd); }; - this.selectPageDown = function() { - var row = this.getPageDownRow() + Math.floor(this.getVisibleRowCount() / 2); - - this.scrollPageDown(); - - this.$moveSelection(function() { - this.moveCursorTo(row, this.selectionLead.column); - }); - }; - - this.selectPageUp = function() { - var visibleRows = this.getLastVisibleRow() - this.getFirstVisibleRow(); - var row = this.getPageUpRow() + Math.round(visibleRows / 2); - - this.scrollPageUp(); - - this.$moveSelection(function() { - this.moveCursorTo(row, this.selectionLead.column); - }); - }; - this.selectFileEnd = function() { this.$moveSelection(this.moveCursorFileEnd); }; From 214eba97617d5f4363dcbbead166a828a2ae61db Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 26 Apr 2010 15:10:05 +0200 Subject: [PATCH 121/392] refactor key bindings --- src/ace/KeyBinding.js | 389 ++++++++++++++++++++++-------------------- 1 file changed, 202 insertions(+), 187 deletions(-) diff --git a/src/ace/KeyBinding.js b/src/ace/KeyBinding.js index fe7e9e8d..482d0541 100644 --- a/src/ace/KeyBinding.js +++ b/src/ace/KeyBinding.js @@ -2,201 +2,216 @@ ace.provide("ace.KeyBinding"); ace.KeyBinding = function(element, editor) { + this.editor = editor; var keys = this.keys; + var self = this; ace.addKeyListener(element, function(e) { - var key = e.keyCode; - var selection = editor.getSelection(); + var key = []; + if (e.ctrlKey || e.metaKey) { + key.push("Control"); + } + if (e.altKey) { + key.push("Alt"); + } + if (e.shiftKey) { + key.push("Shift"); + } + key.push(keys[e.keyCode] || String.fromCharCode(e.keyCode)); - switch (key) { - case keys.A: - if (e.metaKey) { - selection.selectAll(); - return ace.stopEvent(e); - } - break; - - case keys.D: - if (e.metaKey) { - editor.removeLines(); - return ace.stopEvent(e); - } - break; - - case keys.L: - if (e.metaKey) { - var line = parseInt(prompt("Enter line number:")); - if (!isNaN(line)) { - editor.gotoLine(line); - return ace.stopEvent(e); - } - } - break; - - case keys["7"]: - if (e.metaKey) { - editor.toggleCommentLines(); - return ace.stopEvent(e); - }; - break; - - case keys.UP: - if (e.altKey && e.metaKey ) { - editor.copyLinesUp(); - } - else if (e.altKey) { - editor.moveLinesUp(); - } - else if (e.metaKey && e.shiftKey) { - selection.selectFileStart(); - } - else if (e.metaKey) { - editor.navigateFileStart(); - } - else if (e.shiftKey) { - selection.selectUp(); - } - else { - editor.navigateUp(); - } - return ace.stopEvent(e); - - case keys.DOWN: - if (e.altKey && e.metaKey ) { - editor.copyLinesDown(); - } - else if (e.altKey) { - editor.moveLinesDown(); - } - else if (e.metaKey && e.shiftKey) { - selection.selectFileEnd(); - } - else if (e.metaKey) { - editor.navigateFileEnd(); - } - else if (e.shiftKey) { - selection.selectDown(); - } - else { - editor.navigateDown(); - } - return ace.stopEvent(e); - - case keys.LEFT: - if (e.altKey && e.shiftKey) { - selection.selectWordLeft(); - } - else if (e.altKey) { - editor.navigateWordLeft(); - } - else if (e.metaKey && e.shiftKey) { - selection.selectLineStart(); - } - else if (e.metaKey) { - editor.navigateLineStart(); - } - else if (e.shiftKey) { - selection.selectLeft(); - } - else { - editor.navigateLeft(); - } - return ace.stopEvent(e); - - case keys.RIGHT: - if (e.altKey && e.shiftKey) { - selection.selectWordRight(); - } - else if (e.altKey) { - editor.navigateWordRight(); - } - else if (e.metaKey && e.shiftKey) { - selection.selectLineEnd(); - } - else if (e.metaKey) { - editor.navigateLineEnd(); - } - else if (e.shiftKey) { - selection.selectRight(); - } - else { - editor.navigateRight(); - } - return ace.stopEvent(e); - - case keys.PAGEDOWN: - if (e.shiftKey) { - selection.selectPageDown(); - } - else { - editor.scrollPageDown(); - } - return ace.stopEvent(e); - - case keys.PAGEUP: - if (e.shiftKey) { - selection.selectPageUp(); - } - else { - editor.scrollPageUp(); - } - return ace.stopEvent(e); - - case keys.POS1: - if (e.shiftKey) { - selection.selectLineStart(); - } - else { - editor.navigateLineStart(); - } - return ace.stopEvent(e); - - case keys.END: - if (e.shiftKey) { - selection.selectLineEnd(); - } - else { - editor.navigateLineEnd(); - } - return ace.stopEvent(e); - - case keys.DELETE: - editor.removeRight(); - return ace.stopEvent(e); - - case keys.BACKSPACE: - editor.removeLeft(); - return ace.stopEvent(e); - - case keys.TAB: - if (e.shiftKey) { - editor.blockOutdent(); - } else if (selection.isMultiLine()) { - editor.blockIndent(); - } else { - editor.onTextInput("\t"); - } - return ace.stopEvent(e); + var command = self[key.join("-")]; + if (command) { + self.selection = editor.getSelection(); + command.call(self); + return ace.stopEvent(e); } }); }; (function() { this.keys = { - UP : 38, - RIGHT : 39, - DOWN : 40, - LEFT : 37, - PAGEUP : 33, - PAGEDOWN : 34, - POS1 : 36, - END : 35, - DELETE : 46, - BACKSPACE : 8, - TAB : 9, - A : 65, - D: 68, - L: 76, - "7": 55 + 8: "Backspace", + 9: "Tab", + 16: "Shift", + 17: "Control", + 18: "Alt", + 33: "PageUp", + 34: "PageDown", + 35: "End", + 36: "Home", + 37: "Left", + 38: "Up", + 39: "Right", + 40: "Down", + 46: "Delete", + 91: "Meta" }; -}).call(ace.KeyBinding.prototype); + + this["Control-A"] = function() { + this.selection.selectAll(); + }; + + this["Control-D"] = function() { + this.editor.removeLines(); + }; + + this["Control-L"] = function() { + var line = parseInt(prompt("Enter line number:")); + if (!isNaN(line)) { + this.editor.gotoLine(line); + } + }; + + this["Control-7"] = function() { + this.editor.toggleCommentLines(); + }; + + this["Control-Alt-Up"] = function() { + this.editor.copyLinesUp(); + }; + + this["Alt-Up"] = function() { + this.editor.moveLinesUp(); + }; + + this["Control-Shift-Up"] = function() { + this.selection.selectFileStart(); + }; + + this["Control-Up"] = function() { + this.editor.navigateFileStart(); + }; + + this["Shift-Up"] = function() { + this.selection.selectUp(); + }; + + this["Up"] = function() { + this.editor.navigateUp(); + }; + + this["Control-Alt-Down"] = function() { + this.editor.copyLinesDown(); + }; + + this["Alt-Down"] = function() { + this.editor.moveLinesDown(); + }; + + this["Control-Shift-Down"] = function() { + this.selection.selectFileEnd(); + }; + + this["Control-Down"] = function() { + this.editor.navigateFileEnd(); + }; + + this["Shift-Down"] = function() { + this.selection.selectDown(); + }; + + this["Down"] = function() { + this.editor.navigateDown(); + }; + + this["Alt-Shift-Left"] = function() { + this.selection.selectWordLeft(); + }; + + this["Alt-Left"] = function() { + this.editor.navigateWordLeft(); + }; + + this["Control-Shift-Left"] = function() { + this.selection.selectLineStart(); + }; + + this["Control-Left"] = function() { + this.editor.navigateLineStart(); + }; + + this["Shift-Left"] = function() { + this.selection.selectLeft(); + }; + + this["Left"] = function() { + this.editor.navigateLeft(); + }; + + this["Alt-Shift-Right"] = function() { + this.selection.selectWordRight(); + }; + + this["Alt-Right"] = function() { + this.editor.navigateWordRight(); + }; + + this["Control-Shift-Right"] = function() { + this.selection.selectLineEnd(); + }; + + this["Control-Right"] = function() { + this.editor.navigateLineEnd(); + }; + + this["Shift-Right"] = function() { + this.selection.selectRight(); + }; + + this["Right"] = function() { + this.editor.navigateRight(); + }; + + this["Shift-PageDown"] = function() { + this.editor.selectPageDown(); + }; + + this["PageDown"] = function() { + this.editor.scrollPageDown(); + }; + + this["Shift-PageUp"] = function() { + this.editor.selectPageUp(); + }; + + this["PageUp"] = function() { + this.editor.scrollPageUp(); + }; + + this["Shift-Home"] = function() { + this.selection.selectLineStart(); + }; + + this["Home"] = function() { + this.editor.navigateLineStart(); + }; + + this["Shift-End"] = function() { + this.selection.selectLineEnd(); + }; + + this["End"] = function() { + this.editor.navigateLineEnd(); + }; + + this["Delete"] = function() { + this.editor.removeRight(); + }; + + this["Backspace"] = function() { + this.editor.removeLeft(); + }; + + this["Shift-Tab"] = function() { + this.editor.blockOutdent(); + }; + + this["Tab"] = function() { + if (this.selection.isMultiLine()) { + this.editor.blockIndent(); + } else { + this.editor.onTextInput("\t"); + } + }; + +}).call(ace.KeyBinding.prototype); \ No newline at end of file From c34324fe70862638abd52dcd8b7fc4f0e34fe6fb Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 26 Apr 2010 15:17:11 +0200 Subject: [PATCH 122/392] fix "copy lines up/down" --- src/ace/Document.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ace/Document.js b/src/ace/Document.js index 6192799f..231b8de8 100644 --- a/src/ace/Document.js +++ b/src/ace/Document.js @@ -381,7 +381,7 @@ ace.Document = function(text, mode) { this.$insertLines(firstRow, lines); var addedRows = lastRow - firstRow + 1; - this.fireChangeEvent(firstRow, lastRow+addedRows); + this.fireChangeEvent(firstRow); return addedRows; }; From 10e35ef5074f797060a577509bffa9656cbc61fc Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 26 Apr 2010 15:51:58 +0200 Subject: [PATCH 123/392] add proper new line handling including new line character detection --- src/ace/Document.js | 46 +++++++++++++++++++++++++++++++++++++--- src/test/DocumentTest.js | 39 ++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/src/ace/Document.js b/src/ace/Document.js index 231b8de8..b624176b 100644 --- a/src/ace/Document.js +++ b/src/ace/Document.js @@ -3,7 +3,7 @@ ace.provide("ace.Document"); ace.Document = function(text, mode) { this.$initEvents(); - this.lines = this.$split(text); + this.lines = []; this.modified = true; this.selection = new ace.Selection(this); @@ -11,6 +11,8 @@ ace.Document = function(text, mode) { if (mode) { this.setMode(mode); } + + this.insert({row: 0, column: 0}, text); }; (function() { @@ -22,7 +24,7 @@ ace.Document = function(text, mode) { }; this.toString = function() { - return this.lines.join("\n"); + return this.lines.join(this.$getNewLineCharacter()); }; this.getSelection = function() { @@ -68,6 +70,41 @@ ace.Document = function(text, mode) { return this.$tabSize; }; + this.$detectNewLine = function(text) { + var match = text.match(/^.*?(\r?\n)/m); + console.log(match); + if (match) { + this.$autoNewLine = match[1]; + } else { + this.$autoNewLine = "\n"; + } + }; + + this.$getNewLineCharacter = function() { + switch (this.$newLineMode) { + case "windows": + return "\r\n"; + + case "unix": + return "\n"; + + case "auto": + return this.$autoNewLine; + } + }, + + this.$autoNewLine = "\n"; + this.$newLineMode = "auto"; + this.setNewLineMode = function(newLineMode) { + if (this.$newLineMode === newLineMode) return; + + this.$newLineMode = newLineMode; + }; + + this.getNewLineMode = function() { + return this.$newLineMode; + }; + this.$mode = null; this.setMode = function(mode) { if (this.$mode === mode) return; @@ -127,7 +164,7 @@ ace.Document = function(text, mode) { lines.push(this.lines[range.start.row].substring(range.start.column)); lines.push.apply(lines, this.getLines(range.start.row+1, range.end.row-1)); lines.push(this.lines[range.end.row].substring(0, range.end.column)); - return lines.join("\n"); + return lines.join(this.$getNewLineCharacter()); } }; @@ -242,6 +279,9 @@ ace.Document = function(text, mode) { this.$insert = function(position, text) { this.modified = true; + if (this.lines.length <= 1) { + this.$detectNewLine(text); + } var newLines = this.$split(text); diff --git a/src/test/DocumentTest.js b/src/test/DocumentTest.js index 4a8b8e62..ad66f640 100644 --- a/src/test/DocumentTest.js +++ b/src/test/DocumentTest.js @@ -84,5 +84,44 @@ var TextDocumentTest = new TestCase("TextDocumentTest", { doc.duplicateLines(0, 0); assertEquals(["1", "1", "2", "3"].join("\n"), doc.toString()); + }, + + "test: should handle unix style new lines" : function() { + var doc = new ace.Document(["1", "2", "3"].join("\n")); + assertEquals(["1", "2", "3"].join("\n"), doc.toString()); + }, + + "test: should handle windows style new lines" : function() { + var doc = new ace.Document(["1", "2", "3"].join("\r\n")); + doc.setNewLineMode("unix"); + assertEquals(["1", "2", "3"].join("\n"), doc.toString()); + }, + + "test: set new line mode to 'windows' should use '\r\n' as new lines": function() { + var doc = new ace.Document(["1", "2", "3"].join("\n")); + doc.setNewLineMode("windows"); + assertEquals(["1", "2", "3"].join("\r\n"), doc.toString()); + }, + + "test: set new line mode to 'unix' should use '\n' as new lines": function() { + var doc = new ace.Document(["1", "2", "3"].join("\r\n")); + doc.setNewLineMode("unix"); + assertEquals(["1", "2", "3"].join("\n"), doc.toString()); + }, + + "test: set new line mode to 'auto' should use detect the incoming nl type": function() { + var doc = new ace.Document(["1", "2", "3"].join("\n")); + doc.setNewLineMode("auto"); + assertEquals(["1", "2", "3"].join("\n"), doc.toString()); + + var doc = new ace.Document(["1", "2", "3"].join("\r\n")); + doc.setNewLineMode("auto"); + assertEquals(["1", "2", "3"].join("\r\n"), doc.toString()); + + doc.replace({ + start: {row: 0, column: 0}, + end: {row: 2, column: 1} + }, ["4", "5", "6"].join("\n")); + assertEquals(["4", "5", "6"].join("\n"), doc.toString()); } }); \ No newline at end of file From 1a4f0cd0543905b929247a2ea047bd365239db63 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 26 Apr 2010 17:40:40 +0200 Subject: [PATCH 124/392] extract doc comments into a separate mode --- demo/editor.html | 2 ++ jsTestDriver.conf | 3 +- src/ace/Document.js | 1 - src/ace/mode/CssHighlightRules.js | 8 +---- src/ace/mode/DocCommentHighlightRules.js | 38 ++++++++++++++++++++++ src/ace/mode/HtmlHighlightRules.js | 26 ++------------- src/ace/mode/JavaScript.js | 2 +- src/ace/mode/JavaScriptHighlightRules.js | 40 ++++++------------------ src/ace/mode/Text.js | 8 +---- src/ace/mode/TextHighlightRules.js | 37 ++++++++++++++++++++++ src/ace/mode/XmlHighlightRules.js | 9 +----- src/test/mode/JavaScriptTest.js | 12 +++---- 12 files changed, 101 insertions(+), 85 deletions(-) create mode 100644 src/ace/mode/DocCommentHighlightRules.js create mode 100644 src/ace/mode/TextHighlightRules.js diff --git a/demo/editor.html b/demo/editor.html index f8b2f1a1..9ea770af 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -42,7 +42,9 @@ + + diff --git a/jsTestDriver.conf b/jsTestDriver.conf index 7d00e89d..44db80ab 100644 --- a/jsTestDriver.conf +++ b/jsTestDriver.conf @@ -3,8 +3,9 @@ server: http://localhost:4224 load: - src/ace/ace.js - - src/ace/MEventEmitter.js + - src/ace/MEventEmitter.js - src/ace/mode/Text.js + - src/ace/mode/TextHighlightRules.js - src/ace/mode/*.js - src/ace/layer/*.js - src/ace/*.js diff --git a/src/ace/Document.js b/src/ace/Document.js index b624176b..577fd915 100644 --- a/src/ace/Document.js +++ b/src/ace/Document.js @@ -72,7 +72,6 @@ ace.Document = function(text, mode) { this.$detectNewLine = function(text) { var match = text.match(/^.*?(\r?\n)/m); - console.log(match); if (match) { this.$autoNewLine = match[1]; } else { diff --git a/src/ace/mode/CssHighlightRules.js b/src/ace/mode/CssHighlightRules.js index a36871a7..1adcff59 100644 --- a/src/ace/mode/CssHighlightRules.js +++ b/src/ace/mode/CssHighlightRules.js @@ -175,10 +175,4 @@ ace.mode.CssHighlightRules = function() { }; }; -(function() { - - this.getRules = function() { - return this.$rules; - }; - -}).call(ace.mode.CssHighlightRules.prototype); \ No newline at end of file +ace.inherits(ace.mode.CssHighlightRules, ace.mode.TextHighlightRules); \ No newline at end of file diff --git a/src/ace/mode/DocCommentHighlightRules.js b/src/ace/mode/DocCommentHighlightRules.js new file mode 100644 index 00000000..28533ced --- /dev/null +++ b/src/ace/mode/DocCommentHighlightRules.js @@ -0,0 +1,38 @@ +ace.provide("ace.mode.DocCommentHighlightRules"); + +ace.mode.DocCommentHighlightRules = function() { + + this.$rules = { + "start" : [ { + token : "doc-comment", // closing comment + regex : "\\*\\/", + next : "start" + }, { + token : "doc-comment-tag", + regex : "@[\\w\\d_]+" + }, { + token : "doc-comment", + regex : "\s+" + }, { + token : "doc-comment", + regex : "[^@\\*]+" + }, { + token : "doc-comment", + regex : "." + }] + }; +}; + +ace.inherits(ace.mode.DocCommentHighlightRules, ace.mode.TextHighlightRules); + +(function() { + + this.getStartRule = function(start) { + return { + token : "doc-comment", // doc comment + regex : "\\/\\*\\*", + next: start + }; + }; + +}).call(ace.mode.DocCommentHighlightRules.prototype); \ No newline at end of file diff --git a/src/ace/mode/HtmlHighlightRules.js b/src/ace/mode/HtmlHighlightRules.js index 9bd69914..f3daf6c2 100644 --- a/src/ace/mode/HtmlHighlightRules.js +++ b/src/ace/mode/HtmlHighlightRules.js @@ -114,7 +114,7 @@ ace.mode.HtmlHighlightRules = function() { }; var jsRules = new ace.mode.JavaScriptHighlightRules().getRules(); - this.$addRules(jsRules, "js-"); + this.addRules(jsRules, "js-"); this.$rules["js-start"].unshift({ token: "text", regex: "<\\/(?=script)", @@ -122,31 +122,11 @@ ace.mode.HtmlHighlightRules = function() { }); var cssRules = new ace.mode.CssHighlightRules().getRules(); - this.$addRules(cssRules, "css-"); + this.addRules(cssRules, "css-"); this.$rules["css-start"].unshift({ token: "text", regex: "<\\/(?=style)", next: "tag" }); }; - -(function() { - - this.$addRules = function(rules, prefix) { - for (var key in rules) { - var state = rules[key]; - for (var i=0; i the first match is used - this.$rules = { "start" : [ { token : "comment", regex : "\\/\\/.*$" - }, { - token : "doc-comment", // doc comment - regex : "\\/\\*\\*", - next : "doc-comment" - }, { + }, + docComment.getStartRule("doc-start"), + { token : "comment", // multi line comment regex : "\\/\\*", next : "comment" @@ -83,23 +82,6 @@ ace.mode.JavaScriptHighlightRules = function() { token : "text", regex : "\\s+" } ], - "doc-comment" : [ { - token : "doc-comment", // closing comment - regex : "\\*\\/", - next : "start" - }, { - token : "doc-comment-tag", - regex : "@[\\w\\d_]+" - }, { - token : "doc-comment", - regex : "\s+" - }, { - token : "doc-comment", - regex : "[^@\\*]+" - }, { - token : "doc-comment", - regex : "." - }], "comment" : [ { token : "comment", // closing comment regex : ".*?\\*\\/", @@ -125,12 +107,8 @@ ace.mode.JavaScriptHighlightRules = function() { regex : '.+' } ] }; + + this.addRules(docComment.getRules(), "doc-"); + this.$rules["doc-start"][0].next = "start"; }; - -(function() { - - this.getRules = function() { - return this.$rules; - }; - -}).call(ace.mode.JavaScriptHighlightRules.prototype); \ No newline at end of file +ace.inherits(ace.mode.JavaScriptHighlightRules, ace.mode.TextHighlightRules); \ No newline at end of file diff --git a/src/ace/mode/Text.js b/src/ace/mode/Text.js index 874048e9..013d24b5 100644 --- a/src/ace/mode/Text.js +++ b/src/ace/mode/Text.js @@ -1,13 +1,7 @@ ace.provide("ace.mode.Text"); ace.mode.Text = function() { - var rules = { - "start" : [ { - token : "text", - regex : ".+" - } ] - }; - this.$tokenizer = new ace.Tokenizer(rules); + this.$tokenizer = new ace.Tokenizer(new ace.mode.TextHighlightRules().getRules()); }; (function() { diff --git a/src/ace/mode/TextHighlightRules.js b/src/ace/mode/TextHighlightRules.js new file mode 100644 index 00000000..81e0e130 --- /dev/null +++ b/src/ace/mode/TextHighlightRules.js @@ -0,0 +1,37 @@ +ace.provide("ace.mode.TextHighlightRules"); + +ace.mode.TextHighlightRules = function() { + + // regexp must not have capturing parentheses + // regexps are ordered -> the first match is used + + this.$rules = { + "start" : [ { + token : "text", + regex : ".+" + } ] + }; +}; + +(function() { + + this.addRules = function(rules, prefix) { + for (var key in rules) { + var state = rules[key]; + for (var i=0; i Date: Tue, 27 Apr 2010 09:40:41 +0200 Subject: [PATCH 125/392] use soft tabs by default --- src/ace/Document.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ace/Document.js b/src/ace/Document.js index 577fd915..1efce9bd 100644 --- a/src/ace/Document.js +++ b/src/ace/Document.js @@ -47,7 +47,7 @@ ace.Document = function(text, mode) { } }; - this.$useSoftTabs = false; + this.$useSoftTabs = true; this.setUseSoftTabs = function(useSoftTabs) { if (this.$useSoftTabs === useSoftTabs) return; From ac50406a0a5814a92da070ba96184bcdf3f7f81d Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 27 Apr 2010 09:41:18 +0200 Subject: [PATCH 126/392] show middle dots for spaces in "show invisibles" mode --- src/ace/layer/Text.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/ace/layer/Text.js b/src/ace/layer/Text.js index a831a2e8..fdb2dc6b 100644 --- a/src/ace/layer/Text.js +++ b/src/ace/layer/Text.js @@ -10,11 +10,10 @@ ace.layer.Text = function(parentEl) { (function() { -// this.EOF_CHAR = "¶"; this.EOF_CHAR = "¶"; this.EOL_CHAR = "¬"; -// this.TAB_CHAR = "‣"; this.TAB_CHAR = "→"; + this.SPACE_CHAR = "·"; this.setTokenizer = function(tokenizer) { this.tokenizer = tokenizer; @@ -104,13 +103,27 @@ ace.layer.Text = function(parentEl) { this.renderLine = function(stringBuilder, row) { var tokens = this.tokenizer.getTokens(row); + + if (this.$showInvisibles) { + var self = this; + var spaceRe = /[\v\f \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000]+/g; + var spaceReplace = function(space) { + var space = new Array(space.length+1).join(self.SPACE_CHAR); + return ""; + }; + } + else { + var spaceRe = /[\v\f \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000]/g; + var spaceReplace = " "; + } + for ( var i = 0; i < tokens.length; i++) { var token = tokens[i]; var output = token.value .replace(/&/g, "&") .replace(/ Date: Tue, 27 Apr 2010 12:20:00 +0200 Subject: [PATCH 127/392] add support for auto outdent in CSS, JS and HTML modes --- demo/editor.html | 1 + src/ace/Document.js | 14 ++++--- src/ace/Editor.js | 26 +++++++----- src/ace/ace.js | 4 ++ src/ace/mode/Css.js | 11 ++++- src/ace/mode/Html.js | 44 +++++++++++++------- src/ace/mode/JavaScript.js | 13 +++++- src/ace/mode/MatchingBraceOutdent.js | 51 +++++++++++++++++++++++ src/ace/mode/Text.js | 11 ++++- src/test/DocumentTest.js | 18 ++++---- src/test/mode/CssTest.js | 14 +++---- src/test/mode/JavaScriptTest.js | 62 +++++++++++++++++----------- src/test/mode/TextTest.js | 6 +-- src/test/mode/XmlTest.js | 4 +- 14 files changed, 199 insertions(+), 80 deletions(-) create mode 100644 src/ace/mode/MatchingBraceOutdent.js diff --git a/demo/editor.html b/demo/editor.html index 9ea770af..bb947174 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -52,6 +52,7 @@ + diff --git a/src/ace/Document.js b/src/ace/Document.js index 1efce9bd..144ef517 100644 --- a/src/ace/Document.js +++ b/src/ace/Document.js @@ -12,7 +12,11 @@ ace.Document = function(text, mode) { this.setMode(mode); } - this.insert({row: 0, column: 0}, text); + if (ace.isArray(text)) { + this.$insertLines(0, text); + } else { + this.$insert({row: 0, column: 0}, text); + } }; (function() { @@ -149,6 +153,10 @@ ace.Document = function(text, mode) { return this.lines[row] || ""; }; + this.getLines = function(firstRow, lastRow) { + return this.lines.slice(firstRow, lastRow+1); + }; + this.getLength = function() { return this.lines.length; }; @@ -167,10 +175,6 @@ ace.Document = function(text, mode) { } }; - this.getLines = function(firstRow, lastRow) { - return this.lines.slice(firstRow, lastRow+1); - }; - this.findMatchingBracket = function(position) { if (position.column == 0) return null; diff --git a/src/ace/Editor.js b/src/ace/Editor.js index 671ff918..e856688f 100644 --- a/src/ace/Editor.js +++ b/src/ace/Editor.js @@ -285,23 +285,25 @@ ace.Editor = function(renderer, doc) { return; var cursor = this.getCursorPosition(); - text = text.replace("\t", this.doc.getTabString()); if (!this.selection.isEmpty()) { - var end = this.doc.replace(this.getSelectionRange(), text); + var cursor = this.doc.remove(this.getSelectionRange()); this.clearSelection(); } - else { - var end = this.doc.insert(cursor, text); - } + + var lineState = this.bgTokenizer.getState(cursor.row-1); + var shouldOutdent = this.mode.checkOutdent(lineState, this.doc.getLine(cursor.row), text); + + var end = this.doc.insert(cursor, text); + + var row = cursor.row; + var line = this.doc.getLine(row); + var lineState = this.bgTokenizer.getState(row); // multi line insert - var row = cursor.row; if (row !== end.row) { - var line = this.doc.getLine(row); - var lineState = this.bgTokenizer.getState(row); - var indent = this.mode.getNextLineIndent(line, lineState, this.doc.getTabString()); + var indent = this.mode.getNextLineIndent(lineState, line, this.doc.getTabString()); if (indent) { var indentRange = { start: { @@ -312,6 +314,10 @@ ace.Editor = function(renderer, doc) { }; end.column += this.doc.indentRows(indentRange, indent); } + } else { + if (shouldOutdent) { + end.column += this.mode.autoOutdent(lineState, this.doc, row); + } } this.moveCursorToPosition(end); @@ -424,7 +430,7 @@ ace.Editor = function(renderer, doc) { } }; var state = this.bgTokenizer.getState(this.getCursorPosition().row); - var addedColumns = this.mode.toggleCommentLines(this.doc, range, state); + var addedColumns = this.mode.toggleCommentLines(state, this.doc, range); this.selection.shiftSelection(addedColumns); }; diff --git a/src/ace/ace.js b/src/ace/ace.js index f05f9416..214ea027 100644 --- a/src/ace/ace.js +++ b/src/ace/ace.js @@ -171,6 +171,10 @@ else { }; } +ace.isArray = function(value) { + return Object.prototype.toString.call(value) == "[object Array]"; +}; + ace.bind = function(fcn, context) { return function() { return fcn.apply(context, arguments); diff --git a/src/ace/mode/Css.js b/src/ace/mode/Css.js index d07720ee..886f10e2 100644 --- a/src/ace/mode/Css.js +++ b/src/ace/mode/Css.js @@ -2,12 +2,13 @@ ace.provide("ace.mode.Css"); ace.mode.Css = function() { this.$tokenizer = new ace.Tokenizer(new ace.mode.CssHighlightRules().getRules()); + this.$outdent = new ace.mode.MatchingBraceOutdent(); }; ace.inherits(ace.mode.Css, ace.mode.Text); (function() { - this.getNextLineIndent = function(line, state, tab) { + this.getNextLineIndent = function(state, line, tab) { var indent = this.$getIndent(line); // ignore braces in comments @@ -24,4 +25,12 @@ ace.inherits(ace.mode.Css, ace.mode.Text); return indent; }; + this.checkOutdent = function(state, line, input) { + return this.$outdent.checkOutdent(line, input); + }; + + this.autoOutdent = function(state, doc, row) { + return this.$outdent.autoOutdent(doc, row); + }; + }).call(ace.mode.Css.prototype); \ No newline at end of file diff --git a/src/ace/mode/Html.js b/src/ace/mode/Html.js index a1fdc32d..cbac2e2f 100644 --- a/src/ace/mode/Html.js +++ b/src/ace/mode/Html.js @@ -10,32 +10,44 @@ ace.inherits(ace.mode.Html, ace.mode.Text); (function() { - this.toggleCommentLines = function(doc, range, state) { - var split = state.split("js-"); - if (!split[0] && split[1]) { - return this.$js.toggleCommentLines(doc, range, state); - } - - var split = state.split("css-"); - if (!split[0] && split[1]) { - return this.$css.toggleCommentLines(doc, range, state); - } - - return 0; + this.toggleCommentLines = function(state, doc, range) { + return this.$delegate("toggleCommentLines", arguments, function() { + return 0; + }); }; - this.getNextLineIndent = function(line, state, tab) { + this.getNextLineIndent = function(state, line, tab) { + return this.$delegate("getNextLineIndent", arguments, function() { + return ""; + }); + }; + + this.checkOutdent = function(state, line, input) { + return this.$delegate("checkOutdent", arguments, function() { + return false; + }); + }; + + this.autoOutdent = function(state, doc, row) { + return this.$delegate("autoOutdent", arguments); + }; + + this.$delegate = function(method, args, defaultHandler) { + var state = args[0]; var split = state.split("js-"); + if (!split[0] && split[1]) { - return this.$js.getNextLineIndent(line, split[1], tab); + args[0] = split[1]; + return this.$js[method].apply(this.$js, args); } var split = state.split("css-"); if (!split[0] && split[1]) { - return this.$css.getNextLineIndent(line, split[1], tab); + args[0] = split[1]; + return this.$css[method].apply(this.$css, args); } - return ""; + return defaultHandler ? defaultHandler() : undefined; }; }).call(ace.mode.Html.prototype); \ No newline at end of file diff --git a/src/ace/mode/JavaScript.js b/src/ace/mode/JavaScript.js index cb0cee7a..35329f03 100644 --- a/src/ace/mode/JavaScript.js +++ b/src/ace/mode/JavaScript.js @@ -2,12 +2,13 @@ ace.provide("ace.mode.JavaScript"); ace.mode.JavaScript = function() { this.$tokenizer = new ace.Tokenizer(new ace.mode.JavaScriptHighlightRules().getRules()); + this.$outdent = new ace.mode.MatchingBraceOutdent(); }; ace.inherits(ace.mode.JavaScript, ace.mode.Text); (function() { - this.toggleCommentLines = function(doc, range, state) { + this.toggleCommentLines = function(state, doc, range) { var addedRows = doc.outdentRows(range, "//"); if (addedRows == 0) { var addedRows = doc.indentRows(range, "//"); @@ -15,7 +16,7 @@ ace.inherits(ace.mode.JavaScript, ace.mode.Text); return addedRows; }; - this.getNextLineIndent = function(line, state, tab) { + this.getNextLineIndent = function(state, line, tab) { var indent = this.$getIndent(line); var tokenizedLine = this.$tokenizer.getLineTokens(line, state); @@ -47,4 +48,12 @@ ace.inherits(ace.mode.JavaScript, ace.mode.Text); return indent; }; + this.checkOutdent = function(state, line, input) { + return this.$outdent.checkOutdent(line, input); + }; + + this.autoOutdent = function(state, doc, row) { + return this.$outdent.autoOutdent(doc, row); + }; + }).call(ace.mode.JavaScript.prototype); \ No newline at end of file diff --git a/src/ace/mode/MatchingBraceOutdent.js b/src/ace/mode/MatchingBraceOutdent.js new file mode 100644 index 00000000..5c6e5d0b --- /dev/null +++ b/src/ace/mode/MatchingBraceOutdent.js @@ -0,0 +1,51 @@ +ace.provide("ace.mode.MatchingBraceOutdent"); + +ace.mode.MatchingBraceOutdent = function() {}; + +(function() { + + this.checkOutdent = function(line, input) { + if (! /^\s+$/.test(line)) + return false; + + return /^\s*\}/.test(input); + }; + + this.autoOutdent = function(doc, row) { + var line = doc.getLine(row); + var match = line.match(/^(\s*\})/); + + if (!match) return 0; + + var column = match[1].length; + var openBracePos = doc.findMatchingBracket({row: row, column: column}); + + if (!openBracePos || openBracePos.row == row) return 0; + + var indent = this.$getIndent(doc.getLine(openBracePos.row)); + + var range = { + start: { + row: row, + column: 0 + }, + end: { + row: row, + column: column-1 + } + }; + doc.replace(range, indent); + + return indent.length - (column-1); + }; + + this.$getIndent = function(line) { + var match = line.match(/^(\s+)/); + if (match) { + return match[1]; + } + + return ""; + }; + +}).call(ace.mode.MatchingBraceOutdent.prototype); \ No newline at end of file diff --git a/src/ace/mode/Text.js b/src/ace/mode/Text.js index 013d24b5..e7ed42a0 100644 --- a/src/ace/mode/Text.js +++ b/src/ace/mode/Text.js @@ -10,14 +10,21 @@ ace.mode.Text = function() { return this.$tokenizer; }; - this.toggleCommentLines = function(doc, range, state) { + this.toggleCommentLines = function(state, doc, range) { return 0; }; - this.getNextLineIndent = function(line, state, tab) { + this.getNextLineIndent = function(state, line, tab) { return ""; }; + this.checkOutdent = function(state, line, input) { + return false; + }; + + this.autoOutdent = function(state, doc, row) { + }; + this.$getIndent = function(line) { var match = line.match(/^(\s+)/); if (match) { diff --git a/src/test/DocumentTest.js b/src/test/DocumentTest.js index ad66f640..d1002ede 100644 --- a/src/test/DocumentTest.js +++ b/src/test/DocumentTest.js @@ -1,7 +1,7 @@ var TextDocumentTest = new TestCase("TextDocumentTest", { "test: find matching opening bracket" : function() { - var doc = new ace.Document(["(()(", "())))"].join("\n")); + var doc = new ace.Document(["(()(", "())))"]); assertPosition(0, 1, doc.findMatchingBracket({row: 0, column: 3})); assertPosition(1, 0, doc.findMatchingBracket({row: 1, column: 2})); @@ -11,7 +11,7 @@ var TextDocumentTest = new TestCase("TextDocumentTest", { }, "test: find matching closing bracket" : function() { - var doc = new ace.Document(["(()(", "())))"].join("\n")); + var doc = new ace.Document(["(()(", "())))"]); assertPosition(1, 1, doc.findMatchingBracket({row: 1, column: 1})); assertPosition(1, 1, doc.findMatchingBracket({row: 1, column: 1})); @@ -22,7 +22,7 @@ var TextDocumentTest = new TestCase("TextDocumentTest", { }, "test: match different bracket types" : function() { - var doc = new ace.Document(["({[", ")]}"].join("\n")); + var doc = new ace.Document(["({[", ")]}"]); assertPosition(1, 0, doc.findMatchingBracket({row: 0, column: 1})); assertPosition(1, 2, doc.findMatchingBracket({row: 0, column: 2})); @@ -34,7 +34,7 @@ var TextDocumentTest = new TestCase("TextDocumentTest", { }, "test: move lines down" : function() { - var doc = new ace.Document(["1", "2", "3", "4"].join("\n")); + var doc = new ace.Document(["1", "2", "3", "4"]); doc.moveLinesDown(0, 1); assertEquals(["3", "1", "2", "4"].join("\n"), doc.toString()); @@ -50,7 +50,7 @@ var TextDocumentTest = new TestCase("TextDocumentTest", { }, "test: move lines up" : function() { - var doc = new ace.Document(["1", "2", "3", "4"].join("\n")); + var doc = new ace.Document(["1", "2", "3", "4"]); doc.moveLinesUp(2, 3); assertEquals(["1", "3", "4", "2"].join("\n"), doc.toString()); @@ -66,28 +66,28 @@ var TextDocumentTest = new TestCase("TextDocumentTest", { }, "test: duplicate lines" : function() { - var doc = new ace.Document(["1", "2", "3", "4"].join("\n")); + var doc = new ace.Document(["1", "2", "3", "4"]); doc.duplicateLines(1, 2); assertEquals(["1", "2", "3", "2", "3", "4"].join("\n"), doc.toString()); }, "test: duplicate last line" : function() { - var doc = new ace.Document(["1", "2", "3"].join("\n")); + var doc = new ace.Document(["1", "2", "3"]); doc.duplicateLines(2, 2); assertEquals(["1", "2", "3", "3"].join("\n"), doc.toString()); }, "test: duplicate first line" : function() { - var doc = new ace.Document(["1", "2", "3"].join("\n")); + var doc = new ace.Document(["1", "2", "3"]); doc.duplicateLines(0, 0); assertEquals(["1", "1", "2", "3"].join("\n"), doc.toString()); }, "test: should handle unix style new lines" : function() { - var doc = new ace.Document(["1", "2", "3"].join("\n")); + var doc = new ace.Document(["1", "2", "3"]); assertEquals(["1", "2", "3"].join("\n"), doc.toString()); }, diff --git a/src/test/mode/CssTest.js b/src/test/mode/CssTest.js index 56eec460..19e8d781 100644 --- a/src/test/mode/CssTest.js +++ b/src/test/mode/CssTest.js @@ -12,23 +12,23 @@ var CssTest = new TestCase("mode.CssTest", { end: {row: 1, column: 1} }; - var comment = this.mode.toggleCommentLines(doc, range, "start"); + var comment = this.mode.toggleCommentLines("start", doc, range); assertEquals([" abc", "cde", "fg"].join("\n"), doc.toString()); }, "test: lines should keep indentation" : function() { - assertEquals(" ", this.mode.getNextLineIndent(" abc", "start", " ")); - assertEquals("\t", this.mode.getNextLineIndent("\tabc", "start", " ")); + assertEquals(" ", this.mode.getNextLineIndent("start", " abc", " ")); + assertEquals("\t", this.mode.getNextLineIndent("start", "\tabc", " ")); }, "test: new line after { should increase indent" : function() { - assertEquals(" ", this.mode.getNextLineIndent(" abc{", "start", " ")); - assertEquals("\t ", this.mode.getNextLineIndent("\tabc { ", "start", " ")); + assertEquals(" ", this.mode.getNextLineIndent("start", " abc{", " ")); + assertEquals("\t ", this.mode.getNextLineIndent("start", "\tabc { ", " ")); }, "test: no indent increase after { in a comment" : function() { - assertEquals(" ", this.mode.getNextLineIndent(" /*{", "start", " ")); - assertEquals(" ", this.mode.getNextLineIndent(" /*{ ", "start", " ")); + assertEquals(" ", this.mode.getNextLineIndent("start", " /*{", " ")); + assertEquals(" ", this.mode.getNextLineIndent("start", " /*{ ", " ")); } }); \ No newline at end of file diff --git a/src/test/mode/JavaScriptTest.js b/src/test/mode/JavaScriptTest.js index e4eb1f94..fb5ad112 100644 --- a/src/test/mode/JavaScriptTest.js +++ b/src/test/mode/JavaScriptTest.js @@ -14,74 +14,90 @@ var JavaScriptTest = new TestCase("mode.JavaScriptTest", { }, "test: toggle comment lines should prepend '//' to each line" : function() { - var doc = new ace.Document([" abc", "cde", "fg"].join("\n")); + var doc = new ace.Document([" abc", "cde", "fg"]); var range = { start: {row: 0, column: 3}, end: {row: 1, column: 1} }; - var comment = this.mode.toggleCommentLines(doc, range, "start"); + var comment = this.mode.toggleCommentLines("start", doc, range); assertEquals(["// abc", "//cde", "fg"].join("\n"), doc.toString()); }, "test: toggle comment on commented lines should remove leading '//' chars" : function() { - var doc = new ace.Document(["// abc", "//cde", "fg"].join("\n")); + var doc = new ace.Document(["// abc", "//cde", "fg"]); var range = { start: {row: 0, column: 3}, end: {row: 1, column: 1} }; - var comment = this.mode.toggleCommentLines(doc, range, "start"); + var comment = this.mode.toggleCommentLines("start", doc, range); assertEquals([" abc", "cde", "fg"].join("\n"), doc.toString()); }, "test: toggle comment on multiple lines with one commented line prepend '//' to each line" : function() { - var doc = new ace.Document(["// abc", "//cde", "fg"].join("\n")); + var doc = new ace.Document(["// abc", "//cde", "fg"]); var range = { start: {row: 0, column: 3}, end: {row: 2, column: 1} }; - var comment = this.mode.toggleCommentLines(doc, range, "start"); + var comment = this.mode.toggleCommentLines("start", doc, range); assertEquals(["//// abc", "////cde", "//fg"].join("\n"), doc.toString()); }, "test: auto indent after opening brace" : function() { - assertEquals(" ", this.mode.getNextLineIndent("if () {", "start", " ")); + assertEquals(" ", this.mode.getNextLineIndent("start", "if () {", " ")); }, "test: no auto indent after opening brace in multi line comment" : function() { - assertEquals("", this.mode.getNextLineIndent("/*if () {", "start", " ")); - assertEquals(" ", this.mode.getNextLineIndent(" abcd", "comment", " ")); + assertEquals("", this.mode.getNextLineIndent("start", "/*if () {", " ")); + assertEquals(" ", this.mode.getNextLineIndent("comment", " abcd", " ")); }, "test: no auto indent after opening brace in single line comment" : function() { - assertEquals("", this.mode.getNextLineIndent("//if () {", "start", " ")); - assertEquals(" ", this.mode.getNextLineIndent(" //if () {", "start", " ")); + assertEquals("", this.mode.getNextLineIndent("start", "//if () {", " ")); + assertEquals(" ", this.mode.getNextLineIndent("start", " //if () {", " ")); }, "test: no auto indent should add to existing indent" : function() { - assertEquals(" ", this.mode.getNextLineIndent(" if () {", "start", " ")); - assertEquals(" ", this.mode.getNextLineIndent(" cde", "start", " ")); + assertEquals(" ", this.mode.getNextLineIndent("start", " if () {", " ")); + assertEquals(" ", this.mode.getNextLineIndent("start", " cde", " ")); }, "test: special indent in doc comments" : function() { - assertEquals(" * ", this.mode.getNextLineIndent("/**", "doc-start", " ")); - assertEquals(" * ", this.mode.getNextLineIndent(" /**", "doc-start", " ")); - assertEquals(" * ", this.mode.getNextLineIndent(" *", "doc-start", " ")); - assertEquals(" * ", this.mode.getNextLineIndent(" *", "doc-start", " ")); - assertEquals(" ", this.mode.getNextLineIndent(" abc", "doc-start", " ")); + assertEquals(" * ", this.mode.getNextLineIndent("doc-start", "/**", " ")); + assertEquals(" * ", this.mode.getNextLineIndent("doc-start", " /**", " ")); + assertEquals(" * ", this.mode.getNextLineIndent("doc-start", " *", " ")); + assertEquals(" * ", this.mode.getNextLineIndent("doc-start", " *", " ")); + assertEquals(" ", this.mode.getNextLineIndent("doc-start", " abc", " ")); }, "test: no indent after doc comments" : function() { - assertEquals("", this.mode.getNextLineIndent(" */", "doc-start", " ")); + assertEquals("", this.mode.getNextLineIndent("doc-start", " */", " ")); + }, + + "test: trigger outdent if line is space and new text starts with closing brace" : function() { + assertTrue(this.mode.checkOutdent("start", " ", " }")); + assertFalse(this.mode.checkOutdent("start", " a ", " }")); + assertFalse(this.mode.checkOutdent("start", "", "}")); + assertFalse(this.mode.checkOutdent("start", " ", "a }")); + assertFalse(this.mode.checkOutdent("start", " }", "}")); + }, + + "test: auto outdent should indent the line with the same indent as the line with the matching opening brace" : function() { + var doc = new ace.Document([" function foo() {", " bla", " }"]); + this.mode.autoOutdent("start", doc, 2); + assertEquals(" }", doc.getLine(2)); + }, + + "test: no auto outdent if no matching brace is found" : function() { + var doc = new ace.Document([" function foo()", " bla", " }"]); + this.mode.autoOutdent("start", doc, 2); + assertEquals(" }", doc.getLine(2)); } -// "test: outdent if first non WS character in line is a closing brace" : function() { -// assertEquals("", this.mode.getNextLineIndent(")", "start", " ")); -// assertEquals(" ", this.mode.getNextLineIndent(" )", "start", " ")); -// } }); \ No newline at end of file diff --git a/src/test/mode/TextTest.js b/src/test/mode/TextTest.js index 2d53207b..4150c796 100644 --- a/src/test/mode/TextTest.js +++ b/src/test/mode/TextTest.js @@ -5,19 +5,19 @@ var TextTest = new TestCase("mode.TextTest", { }, "test: toggle comment lines should not do anything" : function() { - var doc = new ace.Document([" abc", "cde", "fg"].join("\n")); + var doc = new ace.Document([" abc", "cde", "fg"]); var range = { start: {row: 0, column: 3}, end: {row: 1, column: 1} }; - var comment = this.mode.toggleCommentLines(doc, range, "start"); + var comment = this.mode.toggleCommentLines("start", doc, range); assertEquals([" abc", "cde", "fg"].join("\n"), doc.toString()); }, "text: lines should not be indented" : function() { - assertEquals("", this.mode.getNextLineIndent(" abc", " ")); + assertEquals("", this.mode.getNextLineIndent("start", " abc", " ")); } }); \ No newline at end of file diff --git a/src/test/mode/XmlTest.js b/src/test/mode/XmlTest.js index 0b928c98..df938a67 100644 --- a/src/test/mode/XmlTest.js +++ b/src/test/mode/XmlTest.js @@ -14,14 +14,14 @@ var XmlTest = new TestCase("mode.XmlTest", { }, "test: toggle comment lines should not do anything" : function() { - var doc = new ace.Document([" abc", "cde", "fg"].join("\n")); + var doc = new ace.Document([" abc", "cde", "fg"]); var range = { start: {row: 0, column: 3}, end: {row: 1, column: 1} }; - var comment = this.mode.toggleCommentLines(doc, range, "start"); + var comment = this.mode.toggleCommentLines("start", doc, range); assertEquals([" abc", "cde", "fg"].join("\n"), doc.toString()); } }); \ No newline at end of file From 61ac1944722fbb7ef39988360cac65e707f33430 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 27 Apr 2010 12:20:16 +0200 Subject: [PATCH 128/392] fix nasty tokenizer bugs --- src/ace/BackgroundTokenizer.js | 20 ++++++++++---------- src/ace/Editor.js | 1 + 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/ace/BackgroundTokenizer.js b/src/ace/BackgroundTokenizer.js index b024706a..64b124d8 100644 --- a/src/ace/BackgroundTokenizer.js +++ b/src/ace/BackgroundTokenizer.js @@ -18,20 +18,15 @@ ace.BackgroundTokenizer = function(tokenizer) { var processedLines = 0; while (self.currentLine < textLines.length) { - var line = textLines[self.currentLine]; - - var state = self.currentLine == 0 ? "start" - : self.lines[self.currentLine - 1].state; - self.lines[self.currentLine] = self.tokenizer.getLineTokens(line, state); + self.lines[self.currentLine] = self.$tokenizeRow(self.currentLine); + self.currentLine++; // only check every 30 lines processedLines += 1; if ((processedLines % 30 == 0) && (new Date() - workerStart) > 20) { - self.fireUpdateEvent(startLine, self.currentLine); + self.fireUpdateEvent(startLine, self.currentLine-1); return setTimeout(self.$worker, 10); } - - self.currentLine++; } self.running = false; @@ -95,11 +90,16 @@ ace.BackgroundTokenizer = function(tokenizer) { this.$tokenizeRow = function(row) { if (!this.lines[row]) { - var state = "start"; + var state = null; if (row > 0 && this.lines[row - 1]) { state = this.lines[row - 1].state; } - this.lines[row] = this.tokenizer.getLineTokens(this.textLines[row] || "", state); + var tokens = this.tokenizer.getLineTokens(this.textLines[row] || "", state || "start"); + if (state) { + this.lines[row] = tokens; + } else { + return tokens; + } } return this.lines[row]; }; diff --git a/src/ace/Editor.js b/src/ace/Editor.js index e856688f..6e6ef4d9 100644 --- a/src/ace/Editor.js +++ b/src/ace/Editor.js @@ -65,6 +65,7 @@ ace.Editor = function(renderer, doc) { this.onDocumentModeChange(); this.bgTokenizer.setLines(this.doc.lines); + this.bgTokenizer.start(0); this.renderer.draw(); this.onCursorChange(); From 737d2ed9d01efb956bdffa0cae698aa90a04f019 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 27 Apr 2010 18:16:52 +0200 Subject: [PATCH 129/392] add first search support --- demo/editor.html | 1 + src/ace/Editor.js | 33 +++++++++ src/ace/KeyBinding.js | 46 +++++++----- src/ace/Search.js | 156 +++++++++++++++++++++++++++++++++++++++++ src/ace/Selection.js | 5 ++ src/ace/ace.js | 10 +++ src/test/SearchTest.js | 130 ++++++++++++++++++++++++++++++++++ 7 files changed, 365 insertions(+), 16 deletions(-) create mode 100644 src/ace/Search.js create mode 100644 src/test/SearchTest.js diff --git a/demo/editor.html b/demo/editor.html index bb947174..adeb605c 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -54,6 +54,7 @@ + diff --git a/src/ace/Editor.js b/src/ace/Editor.js index 6e6ef4d9..abe0ea57 100644 --- a/src/ace/Editor.js +++ b/src/ace/Editor.js @@ -23,6 +23,10 @@ ace.Editor = function(renderer, doc) { this.$highlightLineMarker = null; this.$blockScrolling = false; + this.$search = new ace.Search().set({ + wrap: true + }); + this.setDocument(doc || new ace.Document("")); }; @@ -687,4 +691,33 @@ ace.Editor = function(renderer, doc) { this.clearSelection(); this.selection.moveCursorWordLeft(); }; + + this.find = function(needle) { + this.clearSelection(); + this.$search.set({needle: needle}); + this.findNext(); + }, + + this.findNext = function() { + this.$find(false); + }; + + this.findPrevious = function() { + this.$find(true); + }; + + this.$find = function(backwards) { + if (!this.selection.isEmpty()) { + this.$search.set({needle: this.doc.getTextRange(this.getSelectionRange())}); + } + + this.$search.set({ + backwards: backwards + }); + + var range = this.$search.find(this.doc); + if (range) + this.selection.setSelectionRange(range); + }; + }).call(ace.Editor.prototype); \ No newline at end of file diff --git a/src/ace/KeyBinding.js b/src/ace/KeyBinding.js index 482d0541..de1ff40f 100644 --- a/src/ace/KeyBinding.js +++ b/src/ace/KeyBinding.js @@ -30,21 +30,21 @@ ace.KeyBinding = function(element, editor) { (function() { this.keys = { - 8: "Backspace", - 9: "Tab", - 16: "Shift", - 17: "Control", - 18: "Alt", - 33: "PageUp", - 34: "PageDown", - 35: "End", - 36: "Home", - 37: "Left", - 38: "Up", - 39: "Right", - 40: "Down", - 46: "Delete", - 91: "Meta" + 8 : "Backspace", + 9 : "Tab", + 16 : "Shift", + 17 : "Control", + 18 : "Alt", + 33 : "PageUp", + 34 : "PageDown", + 35 : "End", + 36 : "Home", + 37 : "Left", + 38 : "Up", + 39 : "Right", + 40 : "Down", + 46 : "Delete", + 91 : "Meta" }; this["Control-A"] = function() { @@ -66,6 +66,19 @@ ace.KeyBinding = function(element, editor) { this.editor.toggleCommentLines(); }; + this["Control-K"] = function() { + this.editor.findNext(); + }; + + this["Control-Shift-K"] = function() { + this.editor.findPrevious(); + }; + + this["Control-F"] = function() { + var needle = prompt("Find:"); + this.editor.find(needle); + }; + this["Control-Alt-Up"] = function() { this.editor.copyLinesUp(); }; @@ -209,7 +222,8 @@ ace.KeyBinding = function(element, editor) { this["Tab"] = function() { if (this.selection.isMultiLine()) { this.editor.blockIndent(); - } else { + } + else { this.editor.onTextInput("\t"); } }; diff --git a/src/ace/Search.js b/src/ace/Search.js new file mode 100644 index 00000000..f753a47e --- /dev/null +++ b/src/ace/Search.js @@ -0,0 +1,156 @@ +ace.provide("ace.Search"); + +ace.Search = function() { + this.$options = { + needle: "", + backwards: false, + wrap: false, + caseSensitive: false, + wholeWord: false + }; +}; + +ace.Search.ALL = 1; +ace.Search.SELECTION = 2; + +(function() { + + this.set = function(options) { + ace.mixin(this.$options, options); + return this; + }; + + this.find = function(doc) { + var needle = this.$options.needle; + if (!this.$options.needle) + return null; + + if (this.$options.backwards) { + return this.$findBackward(doc); + } else { + return this.$findForward(doc); + } + }; + + this.$assembleRegExp = function() { + var needle = ace.escapeRegExp(this.$options.needle); + if (this.$options.wholeWord) { + needle = "\\b" + needle + "\\b"; + } + + var modifier = "g"; + if (this.$options.caseSensitive) { + modifier += "i"; + } + + var re = new RegExp(needle, modifier); + return re; + }; + + this.$findForward = function(doc) { + var start = doc.getSelection().getCursor(); + var row = start.row; + var column = start.column; + + var startRow = row; + + var line = doc.getLine(row); + var wrapped = false; + + var re = this.$assembleRegExp(); + re.lastIndex = column; + + do { + var match = re.exec(line); + if (!match) { + if (row == startRow && wrapped) { + return null; + } + + row++; + + if (row >= doc.getLength()) { + if (this.$options.wrap) { + row = 0; + wrapped = true; + } else { + return null; + } + } + + line = doc.getLine(row); + re.lastIndex = 0; + } + } while(!match); + + var range = { + start: { + row: row, + column: match.index + }, + end: { + row: row, + column: match.index + match[0].length + } + }; + return range; + }; + + this.$findBackward = function(doc) { + var start = doc.getSelection().getRange().start; + var row = start.row; + var column = start.column; + + var startRow = row; + + var line = doc.getLine(row).substring(0, column); + var wrapped = false; + + var re = this.$assembleRegExp(); + + var found = false; + var lastOffset = 0; + var match = ""; + + do { + line.replace(re, function(str, offset) { + match = str; + found = true; + lastOffset = offset; + return str; + }); + + if (!found) { + if (row == startRow && wrapped) { + return null; + } + + row--; + + if (row < 0) { + if (this.$options.wrap) { + row = doc.getLength() - 1; + wrapped = true; + } else { + return null; + } + } + + line = doc.getLine(row); + } + } while(!found); + + var range = { + start: { + row: row, + column: lastOffset + }, + end: { + row: row, + column: lastOffset + match.length + } + }; + return range; + }; + +}).call(ace.Search.prototype); \ No newline at end of file diff --git a/src/ace/Selection.js b/src/ace/Selection.js index e209748f..c6dec599 100644 --- a/src/ace/Selection.js +++ b/src/ace/Selection.js @@ -102,6 +102,11 @@ ace.Selection = function(doc) { }); }; + this.setSelectionRange = function(range) { + this.setSelectionAnchor(range.start.row, range.start.column); + this.selectTo(range.end.row, range.end.column); + }; + this.$moveSelection = function(mover) { var changed = false; diff --git a/src/ace/ace.js b/src/ace/ace.js index 214ea027..8574d653 100644 --- a/src/ace/ace.js +++ b/src/ace/ace.js @@ -21,6 +21,12 @@ ace.inherits = function(ctor, superCtor) { ctor.prototype.constructor = ctor; }; +ace.mixin = function(obj, mixin) { + for (var key in mixin) { + obj[key] = mixin[key]; + } +}; + ace.implement = function(proto, mixin) { mixin.call(proto); }; @@ -175,6 +181,10 @@ ace.isArray = function(value) { return Object.prototype.toString.call(value) == "[object Array]"; }; +ace.escapeRegExp = function(str) { + return str.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1'); +}; + ace.bind = function(fcn, context) { return function() { return fcn.apply(context, arguments); diff --git a/src/test/SearchTest.js b/src/test/SearchTest.js new file mode 100644 index 00000000..9ccd4b81 --- /dev/null +++ b/src/test/SearchTest.js @@ -0,0 +1,130 @@ +var SearchTest = new TestCase("SearchTest", { + + "test: configure the search object" : function() { + var search = new ace.Search(); + search.set({ + needle: "juhu", + scope: ace.Search.ALL + }); + }, + + "test: find simple text in document" : function() { + var doc = new ace.Document(["juhu kinners 123", "456"]); + var search = new ace.Search().set({ + needle: "kinners" + }); + + var range = search.find(doc); + assertPosition(0, 5, range.start); + assertPosition(0, 12, range.end); + }, + + "test: find simple text in next line" : function() { + var doc = new ace.Document(["abc", "juhu kinners 123", "456"]); + var search = new ace.Search().set({ + needle: "kinners" + }); + + var range = search.find(doc); + assertPosition(1, 5, range.start); + assertPosition(1, 12, range.end); + }, + + "test: find text starting at cursor position" : function() { + var doc = new ace.Document(["juhu kinners", "juhu kinners 123"]); + doc.getSelection().moveCursorTo(0, 6); + var search = new ace.Search().set({ + needle: "kinners" + }); + + var range = search.find(doc); + assertPosition(1, 5, range.start); + assertPosition(1, 12, range.end); + }, + + "test: wrap search is off by default" : function() { + var doc = new ace.Document(["abc", "juhu kinners 123", "456"]); + doc.getSelection().moveCursorTo(2, 1); + + var search = new ace.Search().set({ + needle: "kinners" + }); + + assertEquals(null, search.find(doc)); + }, + + "test: wrap search should wrap at file end" : function() { + var doc = new ace.Document(["abc", "juhu kinners 123", "456"]); + doc.getSelection().moveCursorTo(2, 1); + + var search = new ace.Search().set({ + needle: "kinners", + wrap: true + }); + + var range = search.find(doc); + assertPosition(1, 5, range.start); + assertPosition(1, 12, range.end); + }, + + "test: wrap search with no match should return 'null'": function() { + var doc = new ace.Document(["abc", "juhu kinners 123", "456"]); + doc.getSelection().moveCursorTo(2, 1); + + var search = new ace.Search().set({ + needle: "xyz", + wrap: true + }); + + assertEquals(null, search.find(doc)); + }, + + "test: case sensitive is by default off": function() { + var doc = new ace.Document(["abc", "juhu kinners 123", "456"]); + + var search = new ace.Search().set({ + needle: "JUHU" + }); + + assertEquals(null, search.find(doc)); + }, + + "test: case sensitive search": function() { + var doc = new ace.Document(["abc", "juhu kinners 123", "456"]); + + var search = new ace.Search().set({ + needle: "KINNERS", + caseSensitive: true + }); + + var range = search.find(doc); + assertPosition(1, 5, range.start); + assertPosition(1, 12, range.end); + }, + + "test: whole word search should not match inside of words": function() { + var doc = new ace.Document(["juhukinners", "juhu kinners 123", "456"]); + + var search = new ace.Search().set({ + needle: "kinners", + wholeWord: true + }); + + var range = search.find(doc); + assertPosition(1, 5, range.start); + assertPosition(1, 12, range.end); + }, + + "test: find backwards": function() { + var doc = new ace.Document(["juhu juhu juhu juhu"]); + doc.getSelection().moveCursorTo(0, 10); + var search = new ace.Search().set({ + needle: "juhu", + backwards: true + }); + + var range = search.find(doc); + assertPosition(0, 5, range.start); + assertPosition(0, 9, range.end); + } +}); \ No newline at end of file From 623f6823a4d121cde6e0f5f0f46846567f3f0530 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 28 Apr 2010 09:21:48 +0200 Subject: [PATCH 130/392] fix demo --- demo/editor.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/editor.html b/demo/editor.html index adeb605c..f0d054af 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -255,7 +255,7 @@ ace.addListener(container, "drop", function(e) { editor.onTextInput(reader.result); modeEl.value = mode; - editor.setMode(modes[mode]); + editor.getDocument().setMode(modes[mode]); } reader.readAsText(file); } From 06e21af2f4154534efd0f14beecf3e277798e913 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 28 Apr 2010 09:29:50 +0200 Subject: [PATCH 131/392] fix: Horizontal scrollbar breaks selection --- css/editor.css | 4 +--- src/ace/VirtualRenderer.js | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/css/editor.css b/css/editor.css index a28f10f4..e8aa4bbc 100644 --- a/css/editor.css +++ b/css/editor.css @@ -29,20 +29,19 @@ } .layer { + z-index: 0; position: absolute; overflow: hidden; white-space: nowrap; } .text-layer { - z-index: 2; font-family: Monaco, "Courier New", monospace; cursor: text; color: black; } .cursor-layer { - z-index: 3; } .cursor { @@ -55,7 +54,6 @@ } .marker-layer { - z-index: 1; } .marker-layer .selection { diff --git a/src/ace/VirtualRenderer.js b/src/ace/VirtualRenderer.js index 3b87e938..d17c8fa7 100644 --- a/src/ace/VirtualRenderer.js +++ b/src/ace/VirtualRenderer.js @@ -62,7 +62,8 @@ ace.VirtualRenderer = function(container) { }; this.getMouseEventTarget = function() { - return this.scroller; + // return top most layer + return this.cursorLayer.element; }; this.getFirstVisibleRow = function() { From 18d56bec38d7ba0ada3b3cdc553dc4013b1b23c3 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 28 Apr 2010 09:40:23 +0200 Subject: [PATCH 132/392] fix scrollbar in FF --- css/editor.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/css/editor.css b/css/editor.css index e8aa4bbc..148ec7f9 100644 --- a/css/editor.css +++ b/css/editor.css @@ -25,7 +25,7 @@ .editor .sb div { position: absolute; width: 1px; - left: -10px; + left: 0px; } .layer { From f703a386e6a8b08150e7cd720883b04c32a78d88 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 28 Apr 2010 09:40:46 +0200 Subject: [PATCH 133/392] optimize selection rendering. Use at most 3 DIVs --- src/ace/layer/Marker.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/ace/layer/Marker.js b/src/ace/layer/Marker.js index dcb13e30..6c129b1a 100644 --- a/src/ace/layer/Marker.js +++ b/src/ace/layer/Marker.js @@ -112,6 +112,7 @@ ace.layer.Marker = function(parentEl) { this.drawMultiLineMarker = function(stringBuilder, range, clazz, layerConfig) { + // from selection start to the end of the line var height = layerConfig.lineHeight; var width = Math.round(layerConfig.width - (range.start.column * layerConfig.characterWidth)); var top = (range.start.row - layerConfig.firstRow) * layerConfig.lineHeight; @@ -125,6 +126,7 @@ ace.layer.Marker = function(parentEl) { "left:", left, "px;'>
" ); + // from start of the last line to the selection end var top = (range.end.row - layerConfig.firstRow) * layerConfig.lineHeight; var width = Math.round(range.end.column * layerConfig.characterWidth); @@ -135,15 +137,18 @@ ace.layer.Marker = function(parentEl) { "width:", width, "px;'>" ); - for (var row = range.start.row + 1; row < range.end.row; row++) { - var top = (row - layerConfig.firstRow) * layerConfig.lineHeight; - stringBuilder.push( - "
" - ); - } + // all the complete lines + var height = (range.end.row - range.start.row - 1) * layerConfig.lineHeight; + if (height < 0) + return; + var top = (range.start.row + 1 - layerConfig.firstRow) * layerConfig.lineHeight; + + stringBuilder.push( + "
" + ); }; this.drawSingleLineMarker = function(stringBuilder, range, clazz, layerConfig) { From 0f1f6666726fa7427712ffd4b9dd70e9d7fe6cc7 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 28 Apr 2010 11:25:27 +0200 Subject: [PATCH 134/392] remove lab.js since it is not used at the moment --- demo/LAB.js | 2 - demo/fLAB.js | 191 --------------------------------------------------- 2 files changed, 193 deletions(-) delete mode 100755 demo/LAB.js delete mode 100755 demo/fLAB.js diff --git a/demo/LAB.js b/demo/LAB.js deleted file mode 100755 index db515343..00000000 --- a/demo/LAB.js +++ /dev/null @@ -1,2 +0,0 @@ -// LAB.js (LABjs :: Loading And Blocking JavaScript) | v1.0.2rc1 (c) Kyle Simpson | MIT License -(function(j){var p="string",w="head",H="body",Y="script",t="readyState",k="preloaddone",y="loadtrigger",I="srcuri",D="preload",Z="complete",z="done",A="which",J="preserve",E="onreadystatechange",ba="onload",K="hasOwnProperty",bb="script/cache",L="[object ",bv=L+"Function]",bw=L+"Array]",e=null,h=true,i=false,s=j.document,bx=j.location,bc=j.ActiveXObject,B=j.setTimeout,bd=j.clearTimeout,M=function(a){return s.getElementsByTagName(a)},N=Object.prototype.toString,O=function(){},q={},P={},be=/^[^?#]*\//.exec(bx.href)[0],bf=/^\w+\:\/\/\/?[^\/]+/.exec(be)[0],by=M(Y),bg=j.opera&&N.call(j.opera)==L+"Opera]",bh=(function(a){a[a]=a+"";return a[a]!=a+""})(new String("__count__")),u={cache:!(bh||bg),order:bh||bg,xhr:h,dupe:h,base:"",which:w};u[J]=i;u[D]=h;q[w]=M(w);q[H]=M(H);function Q(a){return N.call(a)===bv}function R(a,b){var c=/^\w+\:\/\//,d;if(typeof a!==p)a="";if(typeof b!==p)b="";d=(c.test(a)?"":b)+a;return((c.test(d)?"":(d.charAt(0)==="/"?bf:be))+d)}function bz(a){return(R(a).indexOf(bf)===0)}function bA(a){var b,c=-1;while(b=by[++c]){if(typeof b.src===p&&a===R(b.src)&&b.type!==bb)return h}return i}function F(v,l){v=!(!v);if(l==e)l=u;var bi=i,C=v&&l[D],bj=C&&l.cache,G=C&&l.order,bk=C&&l.xhr,bB=l[J],bC=l.which,bD=l.base,bl=O,S=i,x,r=h,m={},T=[],U=e;C=bj||bk||G;function bm(a,b){if((a[t]&&a[t]!==Z&&a[t]!=="loaded")||b[z]){return i}a[ba]=a[E]=e;return h}function V(a,b,c){c=!(!c);if(!c&&!(bm(a,b)))return;b[z]=h;for(var d in m){if(m[K](d)&&!(m[d][z]))return}bi=h;bl()}function bn(a){if(Q(a[y])){a[y]();a[y]=e}}function bE(a,b){if(!bm(a,b))return;b[k]=h;B(function(){q[b[A]].removeChild(a);bn(b)},0)}function bF(a,b){if(a[t]===4){a[E]=O;b[k]=h;B(function(){bn(b)},0)}}function W(b,c,d,g,f,n){var o=b[A];B(function(){if("item"in q[o]){if(!q[o][0]){B(arguments.callee,25);return}q[o]=q[o][0]}var a=s.createElement(Y);a.type=d;if(typeof g===p)a.charset=g;if(Q(f)){a[ba]=a[E]=function(){f(a,b)};a.src=c}q[o].insertBefore(a,(o===w?q[o].firstChild:e));if(typeof n===p){a.text=n;V(a,b,h)}},0)}function bo(a,b,c,d){P[a[I]]=h;W(a,b,c,d,V)}function bp(a,b,c,d){var g=arguments;if(r&&a[k]==e){a[k]=i;W(a,b,bb,d,bE)}else if(!r&&a[k]!=e&&!a[k]){a[y]=function(){bp.apply(e,g)}}else if(!r){bo.apply(e,g)}}function bq(a,b,c,d){var g=arguments,f;if(r&&a[k]==e){a[k]=i;f=a.xhr=(bc?new bc("Microsoft.XMLHTTP"):new j.XMLHttpRequest());f[E]=function(){bF(f,a)};f.open("GET",b);f.send("")}else if(!r&&a[k]!=e&&!a[k]){a[y]=function(){bq.apply(e,g)}}else if(!r){P[a[I]]=h;W(a,b,c,d,e,a.xhr.responseText);a.xhr=e}}function br(a){if(a.allowDup==e)a.allowDup=l.dupe;var b=a.src,c=a.type,d=a.charset,g=a.allowDup,f=R(b,bD),n,o=bz(f);if(typeof c!==p)c="text/javascript";if(typeof d!==p)d=e;g=!(!g);if(!g&&((P[f]!=e)||(r&&m[f])||bA(f))){if(m[f]!=e&&m[f][k]&&!m[f][z]&&o){V(e,m[f],h)}return}if(m[f]==e)m[f]={};n=m[f];if(n[A]==e)n[A]=bC;n[z]=i;n[I]=f;S=h;if(!G&&bk&&o)bq(n,f,c,d);else if(!G&&bj)bp(n,f,c,d);else bo(n,f,c,d)}function bs(a){T.push(a)}function X(a){if(v&&!G)bs(a);if(!v||C)a()}function bt(a){var b=[],c;for(c=-1;++c Date: Wed, 28 Apr 2010 11:25:47 +0200 Subject: [PATCH 135/392] fix nasty key event bug --- src/ace/ace.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ace/ace.js b/src/ace/ace.js index 8574d653..d0d5a371 100644 --- a/src/ace/ace.js +++ b/src/ace/ace.js @@ -292,12 +292,12 @@ ace.addKeyListener = function(el, callback) { var lastDown = null; ace.addListener(el, "keydown", function(e) { - lastDown = e.keyCode; + lastDown = e.keyIdentifier || e.keyCode; return callback(e); }); ace.addListener(el, "keypress", function(e) { - var keyId = e.keyCode; + var keyId = e.keyIdentifier || e.keyCode; if (lastDown !== keyId) { return callback(e); } else { From f655d16a78d3e9a9173bb263c08ae704fda501c2 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 28 Apr 2010 12:44:38 +0200 Subject: [PATCH 136/392] find in selection and refactor search code --- src/ace/Search.js | 218 +++++++++++++++++++++++++---------------- src/test/SearchTest.js | 47 +++++++++ 2 files changed, 183 insertions(+), 82 deletions(-) diff --git a/src/ace/Search.js b/src/ace/Search.js index f753a47e..b2445e74 100644 --- a/src/ace/Search.js +++ b/src/ace/Search.js @@ -6,7 +6,8 @@ ace.Search = function() { backwards: false, wrap: false, caseSensitive: false, - wholeWord: false + wholeWord: false, + scope: ace.Search.ALL }; }; @@ -32,6 +33,71 @@ ace.Search.SELECTION = 2; } }; + this.$findForward = function(doc) { + var re = this.$assembleRegExp(); + var match = null; + var matchedRow = -1; + + this.$forwardLineIterator(doc).forEach(function(line, startIndex, row) { + re.lastIndex = startIndex; + match = re.exec(line); + + if (match) { + matchedRow = row; + return true; + } + }); + + if (!match) + return null; + + return this.$rangeFromMatch(matchedRow, match.index, match[0].length); + }; + + this.$findBackward = function(doc) { + var re = this.$assembleRegExp(); + + var found = false; + var lastOffset = 0; + var lastRow = -1; + var match = ""; + + this.$backwardLineIterator(doc).forEach(function(line, startIndex, row) { + if (startIndex) { + line = line.substring(startIndex); + } + line.replace(re, function(str, offset) { + match = str; + found = true; + lastOffset = startIndex + offset; + lastRow = row; + return str; + }); + + if (found) return true; + }); + + if (!found) { + return null; + } + + return this.$rangeFromMatch(lastRow, lastOffset, match.length); + }; + + this.$rangeFromMatch = function(row, column, length) { + var range = { + start: { + row: row, + column: column + }, + end: { + row: row, + column: column + length + } + }; + return range; + }; + this.$assembleRegExp = function() { var needle = ace.escapeRegExp(this.$options.needle); if (this.$options.wholeWord) { @@ -47,110 +113,98 @@ ace.Search.SELECTION = 2; return re; }; - this.$findForward = function(doc) { + this.$forwardLineIterator = function(doc) { + var searchSelection = this.$options.scope == ace.Search.SELECTION; + + var range = doc.getSelection().getRange(); var start = doc.getSelection().getCursor(); - var row = start.row; - var column = start.column; - var startRow = row; + var firstRow = searchSelection ? range.start.row : 0; + var firstColumn = searchSelection ? range.start.column : 0; + var lastRow = searchSelection ? range.end.row : doc.getLength() - 1; - var line = doc.getLine(row); - var wrapped = false; + var wrap = this.$options.wrap; - var re = this.$assembleRegExp(); - re.lastIndex = column; - - do { - var match = re.exec(line); - if (!match) { - if (row == startRow && wrapped) { - return null; - } - - row++; - - if (row >= doc.getLength()) { - if (this.$options.wrap) { - row = 0; - wrapped = true; - } else { - return null; - } - } - - line = doc.getLine(row); - re.lastIndex = 0; + function getLine(row) { + var line = doc.getLine(row); + if (searchSelection && row == range.end.row) { + line = line.substring(0, range.end.column); } - } while(!match); + return line; + } - var range = { - start: { - row: row, - column: match.index - }, - end: { - row: row, - column: match.index + match[0].length + return { + forEach: function(callback) { + var row = start.row; + + var line = getLine(row); + startIndex = start.column; + + while (!callback(line, startIndex, row)) { + + row++; + startIndex = 0; + + if (row > lastRow) { + if (wrap) { + row = firstRow; + startIndex = firstColumn; + } else { + return; + } + } + + if (row == start.row) + return; + + var line = getLine(row); + } } }; - return range; }; - this.$findBackward = function(doc) { - var start = doc.getSelection().getRange().start; - var row = start.row; - var column = start.column; + this.$backwardLineIterator = function(doc) { + var searchSelection = this.$options.scope == ace.Search.SELECTION; - var startRow = row; + var range = doc.getSelection().getRange(); + var start = searchSelection ? range.end : range.start; - var line = doc.getLine(row).substring(0, column); - var wrapped = false; + var firstRow = searchSelection ? range.start.row : 0; + var firstColumn = searchSelection ? range.start.column : 0; + var lastRow = searchSelection ? range.end.row : doc.getLength() - 1; - var re = this.$assembleRegExp(); + var wrap = this.$options.wrap; - var found = false; - var lastOffset = 0; - var match = ""; + return { + forEach : function(callback) { + var row = start.row; - do { - line.replace(re, function(str, offset) { - match = str; - found = true; - lastOffset = offset; - return str; - }); + var line = doc.getLine(row).substring(0, start.column); + var startIndex = 0; - if (!found) { - if (row == startRow && wrapped) { - return null; - } + while (!callback(line, startIndex, row)) { - row--; + row--; + var startIndex = 0; - if (row < 0) { - if (this.$options.wrap) { - row = doc.getLength() - 1; - wrapped = true; - } else { + if (row < firstRow) { + if (wrap) { + row = lastRow; + } else { + return null; + } + } + + if (row == start.row) return null; + + line = doc.getLine(row); + if (searchSelection && row == firstRow) { + startIndex = firstColumn; } } - - line = doc.getLine(row); - } - } while(!found); - - var range = { - start: { - row: row, - column: lastOffset - }, - end: { - row: row, - column: lastOffset + match.length } }; - return range; }; }).call(ace.Search.prototype); \ No newline at end of file diff --git a/src/test/SearchTest.js b/src/test/SearchTest.js index 9ccd4b81..30ccce5f 100644 --- a/src/test/SearchTest.js +++ b/src/test/SearchTest.js @@ -26,6 +26,7 @@ var SearchTest = new TestCase("SearchTest", { }); var range = search.find(doc); + console.log(range) assertPosition(1, 5, range.start); assertPosition(1, 12, range.end); }, @@ -126,5 +127,51 @@ var SearchTest = new TestCase("SearchTest", { var range = search.find(doc); assertPosition(0, 5, range.start); assertPosition(0, 9, range.end); + }, + + "test: find in selection": function() { + var doc = new ace.Document(["juhu", "juhu", "juhu", "juhu"]); + doc.getSelection().setSelectionAnchor(1, 0); + doc.getSelection().selectTo(3, 5); + + var search = new ace.Search().set({ + needle: "juhu", + wrap: true, + scope: ace.Search.SELECTION + }); + + var range = search.find(doc); + assertPosition(1, 0, range.start); + assertPosition(1, 4, range.end); + + doc.getSelection().setSelectionAnchor(0, 2); + doc.getSelection().selectTo(3, 2); + + var range = search.find(doc); + assertPosition(1, 0, range.start); + assertPosition(1, 4, range.end); + }, + + "test: find backwards in selection": function() { + var doc = new ace.Document(["juhu", "juhu", "juhu", "juhu"]); + + var search = new ace.Search().set({ + needle: "juhu", + wrap: true, + backwards: true, + scope: ace.Search.SELECTION + }); + + doc.getSelection().setSelectionAnchor(0, 2); + doc.getSelection().selectTo(3, 2); + + var range = search.find(doc); + assertPosition(2, 0, range.start); + assertPosition(2, 4, range.end); + + doc.getSelection().setSelectionAnchor(0, 2); + doc.getSelection().selectTo(1, 2); + + assertEquals(null, search.find(doc)); } }); \ No newline at end of file From 5bc8d8236ebfc384444e2c5d49229e68a22e3d6a Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 28 Apr 2010 16:11:37 +0200 Subject: [PATCH 137/392] add simple replace functionality --- src/ace/Editor.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ace/Editor.js b/src/ace/Editor.js index abe0ea57..086a9e45 100644 --- a/src/ace/Editor.js +++ b/src/ace/Editor.js @@ -692,6 +692,12 @@ ace.Editor = function(renderer, doc) { this.selection.moveCursorWordLeft(); }; + this.replace = function(replacement) { + var range = this.getSelectionRange(); + range.end = this.doc.replace(range, replacement); + this.selection.setSelectionRange(range); + }, + this.find = function(needle) { this.clearSelection(); this.$search.set({needle: needle}); From a62062e57674e3fc6251156dabd6e49fdb192fd1 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 28 Apr 2010 16:41:17 +0200 Subject: [PATCH 138/392] fix search for a couple of edge cases --- src/ace/Search.js | 23 ++++++++++++++++++----- src/test/SearchTest.js | 32 +++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/ace/Search.js b/src/ace/Search.js index b2445e74..860c3ca0 100644 --- a/src/ace/Search.js +++ b/src/ace/Search.js @@ -140,8 +140,14 @@ ace.Search.SELECTION = 2; var line = getLine(row); startIndex = start.column; + var stop = false; + while (!callback(line, startIndex, row)) { + if (stop) { + return; + } + row++; startIndex = 0; @@ -155,7 +161,7 @@ ace.Search.SELECTION = 2; } if (row == start.row) - return; + stop = true; var line = getLine(row); } @@ -181,9 +187,13 @@ ace.Search.SELECTION = 2; var line = doc.getLine(row).substring(0, start.column); var startIndex = 0; + var stop = false; while (!callback(line, startIndex, row)) { + if (stop) + return; + row--; var startIndex = 0; @@ -191,16 +201,19 @@ ace.Search.SELECTION = 2; if (wrap) { row = lastRow; } else { - return null; + return; } } if (row == start.row) - return null; + stop = true; line = doc.getLine(row); - if (searchSelection && row == firstRow) { - startIndex = firstColumn; + if (searchSelection) { + if (row == firstRow) + startIndex = firstColumn; + else if (row == lastRow) + line = line.substring(0, range.end.column); } } } diff --git a/src/test/SearchTest.js b/src/test/SearchTest.js index 30ccce5f..d5b59f21 100644 --- a/src/test/SearchTest.js +++ b/src/test/SearchTest.js @@ -26,7 +26,6 @@ var SearchTest = new TestCase("SearchTest", { }); var range = search.find(doc); - console.log(range) assertPosition(1, 5, range.start); assertPosition(1, 12, range.end); }, @@ -173,5 +172,36 @@ var SearchTest = new TestCase("SearchTest", { doc.getSelection().selectTo(1, 2); assertEquals(null, search.find(doc)); + }, + + "test: edge case - match directly before the cursor" : function() { + var doc = new ace.Document(["123", "123", "juhu"]); + + var search = new ace.Search().set({ + needle: "juhu", + wrap: true + }); + + doc.getSelection().moveCursorTo(2, 5); + + var range = search.find(doc); + assertPosition(2, 0, range.start); + assertPosition(2, 4, range.end); + }, + + "test: edge case - match backwards directly after the cursor" : function() { + var doc = new ace.Document(["123", "123", "juhu"]); + + var search = new ace.Search().set({ + needle: "juhu", + wrap: true, + backwards: true + }); + + doc.getSelection().moveCursorTo(2, 0); + + var range = search.find(doc); + assertPosition(2, 0, range.start); + assertPosition(2, 4, range.end); } }); \ No newline at end of file From 000a81da7fbf94c247d3b13857316b06da47c96f Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 28 Apr 2010 17:37:04 +0200 Subject: [PATCH 139/392] refactor searching and add findAll method --- src/ace/Search.js | 154 +++++++++++++++++++++++++++-------------- src/test/SearchTest.js | 48 +++++++++++++ 2 files changed, 149 insertions(+), 53 deletions(-) diff --git a/src/ace/Search.js b/src/ace/Search.js index 860c3ca0..19f9e248 100644 --- a/src/ace/Search.js +++ b/src/ace/Search.js @@ -27,63 +27,106 @@ ace.Search.SELECTION = 2; return null; if (this.$options.backwards) { - return this.$findBackward(doc); + var iterator = this.$backwardMatchIterator(doc); } else { - return this.$findForward(doc); - } - }; - - this.$findForward = function(doc) { - var re = this.$assembleRegExp(); - var match = null; - var matchedRow = -1; - - this.$forwardLineIterator(doc).forEach(function(line, startIndex, row) { - re.lastIndex = startIndex; - match = re.exec(line); - - if (match) { - matchedRow = row; - return true; - } - }); - - if (!match) - return null; - - return this.$rangeFromMatch(matchedRow, match.index, match[0].length); - }; - - this.$findBackward = function(doc) { - var re = this.$assembleRegExp(); - - var found = false; - var lastOffset = 0; - var lastRow = -1; - var match = ""; - - this.$backwardLineIterator(doc).forEach(function(line, startIndex, row) { - if (startIndex) { - line = line.substring(startIndex); - } - line.replace(re, function(str, offset) { - match = str; - found = true; - lastOffset = startIndex + offset; - lastRow = row; - return str; - }); - - if (found) return true; - }); - - if (!found) { - return null; + var iterator = this.$forwardMatchIterator(doc); } - return this.$rangeFromMatch(lastRow, lastOffset, match.length); + var firstRange = null; + iterator.forEach(function(range) { + firstRange = range; + return true; + }); + + return firstRange; }; + this.findAll = function(doc) { + var needle = this.$options.needle; + if (!this.$options.needle) + return []; + + if (this.$options.backwards) { + var iterator = this.$backwardMatchIterator(doc); + } else { + var iterator = this.$forwardMatchIterator(doc); + } + + var ranges = []; + iterator.forEach(function(range) { + ranges.push(range); + }); + + return ranges; + }; + + this.$forwardMatchIterator = function(doc) { + var re = this.$assembleRegExp(); + var self = this; + + return { + forEach: function(callback) { + self.$forwardLineIterator(doc).forEach(function(line, startIndex, row) { + if (startIndex) { + line = line.substring(startIndex); + } + + var matches = []; + + line.replace(re, function(str, offset) { + matches.push({ + str: str, + offset: startIndex + offset + }); + return str; + }); + + for (var i=0; i= 0; i--) { + var match = matches[i]; + var range = self.$rangeFromMatch(row, match.offset, match.str.length); + if (callback(range)) + return true; + } + }); + } + }; + }; + + + this.$rangeFromMatch = function(row, column, length) { var range = { start: { @@ -99,7 +142,12 @@ ace.Search.SELECTION = 2; }; this.$assembleRegExp = function() { - var needle = ace.escapeRegExp(this.$options.needle); + if (this.$options.regExp) { + var needle = this.$options.needle; + } else { + var needle = ace.escapeRegExp(this.$options.needle); + } + if (this.$options.wholeWord) { needle = "\\b" + needle + "\\b"; } diff --git a/src/test/SearchTest.js b/src/test/SearchTest.js index d5b59f21..e0e34363 100644 --- a/src/test/SearchTest.js +++ b/src/test/SearchTest.js @@ -203,5 +203,53 @@ var SearchTest = new TestCase("SearchTest", { var range = search.find(doc); assertPosition(2, 0, range.start); assertPosition(2, 4, range.end); + }, + + "test: find using a regular expression" : function() { + var doc = new ace.Document(["abc123 123 cd", "abc"]); + + var search = new ace.Search().set({ + needle: "\\d+", + regExp: true + }); + + var range = search.find(doc); + assertPosition(0, 3, range.start); + assertPosition(0, 6, range.end); + }, + + "test: find using a regular expression and whole word" : function() { + var doc = new ace.Document(["abc123 123 cd", "abc"]); + + var search = new ace.Search().set({ + needle: "\\d+\\b", + regExp: true, + wholeWord: true + }); + + var range = search.find(doc); + assertPosition(0, 7, range.start); + assertPosition(0, 10, range.end); + }, + + "test: find all matches in selection" : function() { + var doc = new ace.Document(["juhu", "juhu", "juhu", "juhu"]); + + var search = new ace.Search().set({ + needle: "uh", + wrap: true, + scope: ace.Search.SELECTION + }); + + doc.getSelection().setSelectionAnchor(0, 2); + doc.getSelection().selectTo(3, 2); + + var ranges = search.findAll(doc); + + assertEquals(2, ranges.length); + assertPosition(1, 1, ranges[0].start); + assertPosition(1, 3, ranges[0].end); + assertPosition(2, 1, ranges[1].start); + assertPosition(2, 3, ranges[1].end); } }); \ No newline at end of file From 9a457c8f589baa97331c4563d43b063ea47eb81b Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 28 Apr 2010 17:52:32 +0200 Subject: [PATCH 140/392] add support for replaceAll command --- src/ace/Editor.js | 15 +++++++++++++++ src/ace/Search.js | 2 -- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/ace/Editor.js b/src/ace/Editor.js index 086a9e45..84a98148 100644 --- a/src/ace/Editor.js +++ b/src/ace/Editor.js @@ -698,6 +698,21 @@ ace.Editor = function(renderer, doc) { this.selection.setSelectionRange(range); }, + this.replaceAll = function(replacement) { + this.clearSelection(); + this.selection.moveCursorTo(0, 0); + + var ranges = this.$search.findAll(this.doc); + if (!ranges.length) + return; + + for (var i=0; i Date: Thu, 29 Apr 2010 11:48:38 +0200 Subject: [PATCH 141/392] support regexp replacements --- src/ace/Editor.js | 19 ++++++++++++---- src/ace/Search.js | 20 +++++++++++++++-- src/test/SearchTest.js | 49 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 6 deletions(-) diff --git a/src/ace/Editor.js b/src/ace/Editor.js index 84a98148..1c0b4534 100644 --- a/src/ace/Editor.js +++ b/src/ace/Editor.js @@ -693,9 +693,9 @@ ace.Editor = function(renderer, doc) { }; this.replace = function(replacement) { - var range = this.getSelectionRange(); - range.end = this.doc.replace(range, replacement); - this.selection.setSelectionRange(range); + var range = this.$tryReplace(this.getSelectionRange(), replacement); + if (range !== null) + this.selection.setSelectionRange(range); }, this.replaceAll = function(replacement) { @@ -708,11 +708,22 @@ ace.Editor = function(renderer, doc) { for (var i=0; i Date: Thu, 29 Apr 2010 14:47:48 +0200 Subject: [PATCH 142/392] add support for overwrite/insert mode --- css/tm.css | 8 ++++++-- src/ace/Editor.js | 30 +++++++++++++++++++++++++++++- src/ace/KeyBinding.js | 5 +++++ src/ace/VirtualRenderer.js | 4 ++-- src/ace/layer/Cursor.js | 8 +++++++- 5 files changed, 49 insertions(+), 6 deletions(-) diff --git a/css/tm.css b/css/tm.css index 0bf48fee..1745966c 100644 --- a/css/tm.css +++ b/css/tm.css @@ -27,10 +27,14 @@ } .cursor { - width: 2px; - background: black; + border-left: 2px solid black; } +.cursor.overwrite { + border-left: 0px; + border-bottom: 1px solid black; +} + .line .invisible { color: rgb(191, 191, 191); } diff --git a/src/ace/Editor.js b/src/ace/Editor.js index 1c0b4534..62ee0084 100644 --- a/src/ace/Editor.js +++ b/src/ace/Editor.js @@ -150,7 +150,7 @@ ace.Editor = function(renderer, doc) { this.onCursorChange = function() { this.$highlightBrackets(); - this.renderer.updateCursor(this.getCursorPosition()); + this.renderer.updateCursor(this.getCursorPosition(), this.$overwrite); if (!this.$blockScrolling) { this.renderer.scrollCursorIntoView(); @@ -295,6 +295,15 @@ ace.Editor = function(renderer, doc) { if (!this.selection.isEmpty()) { var cursor = this.doc.remove(this.getSelectionRange()); this.clearSelection(); + } else if (this.$overwrite){ + var range = { + start: cursor, + end: { + row: cursor.row, + column: cursor.column + text.length + } + }; + this.doc.remove(range); } var lineState = this.bgTokenizer.getState(cursor.row-1); @@ -329,6 +338,25 @@ ace.Editor = function(renderer, doc) { this.renderer.scrollCursorIntoView(); }; + this.$overwrite = false; + this.setOverwrite = function(overwrite) { + if (this.$overwrite == overwrite) return; + + this.$overwrite = overwrite; + + this.$blockScrolling = true; + this.onCursorChange(); + this.$blockScrolling = false; + }; + + this.getOverwrite = function() { + return this.$overwrite; + }; + + this.toggleOverwrite = function() { + this.setOverwrite(!this.$overwrite); + }; + this.$selectionStyle = "line"; this.setSelectionStyle = function(style) { if (this.$selectionStyle == style) return; diff --git a/src/ace/KeyBinding.js b/src/ace/KeyBinding.js index de1ff40f..293e026e 100644 --- a/src/ace/KeyBinding.js +++ b/src/ace/KeyBinding.js @@ -43,6 +43,7 @@ ace.KeyBinding = function(element, editor) { 38 : "Up", 39 : "Right", 40 : "Down", + 45 : "Insert", 46 : "Delete", 91 : "Meta" }; @@ -79,6 +80,10 @@ ace.KeyBinding = function(element, editor) { this.editor.find(needle); }; + this["Insert"] = function() { + this.editor.toggleOverwrite(); + }; + this["Control-Alt-Up"] = function() { this.editor.copyLinesUp(); }; diff --git a/src/ace/VirtualRenderer.js b/src/ace/VirtualRenderer.js index d17c8fa7..8e4e374e 100644 --- a/src/ace/VirtualRenderer.js +++ b/src/ace/VirtualRenderer.js @@ -169,8 +169,8 @@ ace.VirtualRenderer = function(container) { this.markerLayer.removeMarker(markerId); }; - this.updateCursor = function(position) { - this.cursorLayer.setCursor(this.$documentToScreenPosition(position)); + this.updateCursor = function(position, overwrite) { + this.cursorLayer.setCursor(this.$documentToScreenPosition(position), overwrite); this.cursorLayer.update(this.layerConfig); }; diff --git a/src/ace/layer/Cursor.js b/src/ace/layer/Cursor.js index e43edc38..e907440c 100644 --- a/src/ace/layer/Cursor.js +++ b/src/ace/layer/Cursor.js @@ -13,11 +13,16 @@ ace.layer.Cursor = function(parentEl) { (function() { - this.setCursor = function(position) { + this.setCursor = function(position, overwrite) { this.position = { row : position.row, column : position.column }; + if (overwrite) { + ace.addCssClass(this.cursor, "overwrite"); + } else { + ace.removeCssClass(this.cursor, "overwrite"); + } }; this.hideCursor = function() { @@ -74,6 +79,7 @@ ace.layer.Cursor = function(parentEl) { this.cursor.style.left = cursorLeft + "px"; this.cursor.style.top = (cursorTop - (config.firstRow * config.lineHeight)) + "px"; + this.cursor.style.width = config.characterWidth + "px"; this.cursor.style.height = config.lineHeight + "px"; if (this.isVisible) { From 11ac2173bf474473eb5ca15318c361e8c07a6bbe Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 29 Apr 2010 14:48:22 +0200 Subject: [PATCH 143/392] fix selection.isEmpty() method --- src/ace/Selection.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ace/Selection.js b/src/ace/Selection.js index c6dec599..b7232955 100644 --- a/src/ace/Selection.js +++ b/src/ace/Selection.js @@ -17,7 +17,9 @@ ace.Selection = function(doc) { ace.implement(this, ace.MEventEmitter); this.isEmpty = function() { - return (this.selectionAnchor == null); + return (this.selectionAnchor == null || + (this.selectionAnchor.row == this.selectionLead.row && + this.selectionAnchor.column == this.selectionLead.column)); }; this.isMultiLine = function() { From 22ddda9b31f18b48ed41d74051a0d9d3ac3ad7c9 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 29 Apr 2010 14:48:33 +0200 Subject: [PATCH 144/392] fix hasCssClass function --- src/ace/ace.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ace/ace.js b/src/ace/ace.js index d0d5a371..c6b14ca0 100644 --- a/src/ace/ace.js +++ b/src/ace/ace.js @@ -84,7 +84,7 @@ ace.preventDefault = function(e) { ace.hasCssClass = function(el, name) { - var classes = el.className.split(/\s*/g); + var classes = el.className.split(/\s+/g); return ace.arrayIndexOf(classes, name) !== -1; }; From ed5d97e9bd52309517bc9c1bfeb1ff8214ac721a Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 3 May 2010 17:23:08 +0200 Subject: [PATCH 145/392] split ace.js into separate files --- demo/editor.html | 6 +- src/ace/ace.js | 307 ------------------------------------------- src/ace/lib/core.js | 18 +++ src/ace/lib/dom.js | 88 +++++++++++++ src/ace/lib/event.js | 161 +++++++++++++++++++++++ src/ace/lib/lang.js | 67 ++++++++++ src/ace/lib/oop.js | 21 +++ 7 files changed, 360 insertions(+), 308 deletions(-) delete mode 100644 src/ace/ace.js create mode 100644 src/ace/lib/core.js create mode 100644 src/ace/lib/dom.js create mode 100644 src/ace/lib/event.js create mode 100644 src/ace/lib/lang.js create mode 100644 src/ace/lib/oop.js diff --git a/demo/editor.html b/demo/editor.html index f0d054af..821536af 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -40,7 +40,11 @@ - + + + + + diff --git a/src/ace/ace.js b/src/ace/ace.js deleted file mode 100644 index c6b14ca0..00000000 --- a/src/ace/ace.js +++ /dev/null @@ -1,307 +0,0 @@ -if (!window.ace) - ace = {}; - -ace.provide = function(namespace) { - var parts = namespace.split("."); - var obj = window; - for (var i=0; i Date: Mon, 3 May 2010 17:24:51 +0200 Subject: [PATCH 146/392] fix mouse cursor --- css/editor.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/css/editor.css b/css/editor.css index 148ec7f9..3cd216ea 100644 --- a/css/editor.css +++ b/css/editor.css @@ -7,6 +7,7 @@ position: absolute; overflow-x: scroll; overflow-y: hidden; + cursor: text; } .gutter { @@ -37,7 +38,6 @@ .text-layer { font-family: Monaco, "Courier New", monospace; - cursor: text; color: black; } From 0cdafa9daa1128a54541e74dd87b4bc97a27e898 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 3 May 2010 17:25:31 +0200 Subject: [PATCH 147/392] first undo manager support --- demo/editor.html | 6 ++ jsTestDriver.conf | 3 +- src/ace/Document.js | 144 +++++++++++++++++++++++++++++++++++++---- src/ace/Editor.js | 8 +++ src/ace/KeyBinding.js | 12 ++++ src/ace/UndoManager.js | 35 ++++++++++ 6 files changed, 194 insertions(+), 14 deletions(-) create mode 100644 src/ace/UndoManager.js diff --git a/demo/editor.html b/demo/editor.html index 821536af..fd5b8c18 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -60,6 +60,7 @@ + @@ -149,10 +150,15 @@ var docs = {} docs.js = new ace.Document(document.getElementById("jstext").innerHTML); docs.js.setMode(new ace.mode.JavaScript()); +docs.js.setUndoManager(new ace.UndoManager()); + docs.css = new ace.Document(document.getElementById("csstext").innerHTML); docs.css.setMode(new ace.mode.Css()); +docs.css.setUndoManager(new ace.UndoManager()); + docs.html = new ace.Document(document.getElementById("htmltext").innerHTML); docs.html.setMode(new ace.mode.Html()); +docs.html.setUndoManager(new ace.UndoManager()); var docEl = document.getElementById("doc"); diff --git a/jsTestDriver.conf b/jsTestDriver.conf index 44db80ab..f047d8c9 100644 --- a/jsTestDriver.conf +++ b/jsTestDriver.conf @@ -2,7 +2,8 @@ server: http://localhost:4224 load: - - src/ace/ace.js + - src/ace/lib/core.js + - src/ace/lib/*.js - src/ace/MEventEmitter.js - src/ace/mode/Text.js - src/ace/mode/TextHighlightRules.js diff --git a/src/ace/Document.js b/src/ace/Document.js index 144ef517..0ed141f1 100644 --- a/src/ace/Document.js +++ b/src/ace/Document.js @@ -23,6 +23,8 @@ ace.Document = function(text, mode) { ace.implement(this, ace.MEventEmitter); + this.$undoManager = null; + this.$split = function(text) { return text.split(/\r\n|\r|\n/); }; @@ -43,6 +45,33 @@ ace.Document = function(text, mode) { this.$dispatchEvent("change", { data: data}); }; + this.setUndoManager = function(undoManager) { + this.$undoManager = undoManager; + this.$deltas = []; + + if (this.$informUndoManager) { + this.$informUndoManager.cancel(); + } + + if (undoManager) { + undoManager.setDocument(this); + var self = this; + this.$informUndoManager = ace.deferredCall(function() { + undoManager.notify(self.$deltas); + self.$deltas = []; + }); + } + }; + + this.$defaultUndoManager = { + undo: function() {}, + redo: function() {} + }; + + this.getUndoManager = function() { + return this.$undoManager || this.$defaultUndoManager; + }, + this.getTabString = function() { if (this.getUseSoftTabs()) { return new Array(this.getTabSize()+1).join(" "); @@ -267,8 +296,8 @@ ace.Document = function(text, mode) { return null; }; - this.insert = function(position, text) { - var end = this.$insert(position, text); + this.insert = function(position, text, fromUndo) { + var end = this.$insert(position, text, fromUndo); this.fireChangeEvent(position.row, position.row == end.row ? position.row : undefined); return end; @@ -278,9 +307,28 @@ ace.Document = function(text, mode) { var args = [row, 0]; args.push.apply(args, lines); this.lines.splice.apply(this.lines, args); + + if (this.$undoManager) { + var nl = this.$getNewLineCharacter(); + this.$deltas.push({ + type: "insert", + range: { + start: { + row: row, + column: 0 + }, + end: { + row: row + lines.length, + column: 0 + } + }, + text: lines.join(nl) + nl + }); + this.$informUndoManager.schedule(); + } }, - this.$insert = function(position, text) { + this.$insert = function(position, text, fromUndo) { this.modified = true; if (this.lines.length <= 1) { this.$detectNewLine(text); @@ -293,7 +341,7 @@ ace.Document = function(text, mode) { this.lines[position.row] = line.substring(0, position.column); this.lines.splice(position.row + 1, 0, line.substring(position.column)); - return { + var end = { row : position.row + 1, column : 0 }; @@ -303,7 +351,7 @@ ace.Document = function(text, mode) { this.lines[position.row] = line.substring(0, position.column) + text + line.substring(position.column); - return { + var end = { row : position.row, column : position.column + text.length }; @@ -320,27 +368,56 @@ ace.Document = function(text, mode) { this.$insertLines(position.row + 1, newLines.slice(1, -1)); } - return { + var end = { row : position.row + newLines.length - 1, column : newLines[newLines.length - 1].length }; } + + if (!fromUndo && this.$undoManager) { + var nl = this.$getNewLineCharacter(); + this.$deltas.push({ + type: "insert", + range: { + start: ace.copyObject(position), + end: ace.copyObject(end) + }, + text: text + }); + this.$informUndoManager.schedule(); + } + + return end; }; this.$isNewLine = function(text) { return (text == "\r\n" || text == "\r" || text == "\n"); }; - this.remove = function(range) { - this.$remove(range); + this.remove = function(range, fromUndo) { + this.$remove(range, fromUndo); this.fireChangeEvent(range.start.row, range.end.row == range.start.row ? range.start.row : undefined); + return range.start; }; - this.$remove = function(range) { + this.$remove = function(range, fromUndo) { + if (!fromUndo && this.$undoManager) { + var nl = this.$getNewLineCharacter(); + this.$deltas.push({ + type: "remove", + range: { + start: ace.copyObject(range.start), + end: ace.copyObject(range.end) + }, + text: this.getTextRange(range) + }); + this.$informUndoManager.schedule(); + } + this.modified = true; var firstRow = range.start.row; @@ -351,9 +428,38 @@ ace.Document = function(text, mode) { this.lines.splice(firstRow, lastRow - firstRow + 1, row); + return range.start; }; + this.undoChanges = function(deltas) { + this.selection.clearSelection(); + for (var i=0; i Date: Mon, 3 May 2010 17:57:56 +0200 Subject: [PATCH 148/392] clear selection after text input --- src/ace/Editor.js | 2 ++ src/ace/Selection.js | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ace/Editor.js b/src/ace/Editor.js index 4dcb2c49..9fabcf60 100644 --- a/src/ace/Editor.js +++ b/src/ace/Editor.js @@ -306,6 +306,8 @@ ace.Editor = function(renderer, doc) { this.doc.remove(range); } + this.clearSelection(); + var lineState = this.bgTokenizer.getState(cursor.row-1); var shouldOutdent = this.mode.checkOutdent(lineState, this.doc.getLine(cursor.row), text); diff --git a/src/ace/Selection.js b/src/ace/Selection.js index b7232955..a3cd5be8 100644 --- a/src/ace/Selection.js +++ b/src/ace/Selection.js @@ -17,7 +17,7 @@ ace.Selection = function(doc) { ace.implement(this, ace.MEventEmitter); this.isEmpty = function() { - return (this.selectionAnchor == null || + return (!this.selectionAnchor || (this.selectionAnchor.row == this.selectionLead.row && this.selectionAnchor.column == this.selectionLead.column)); }; @@ -37,7 +37,6 @@ ace.Selection = function(doc) { this.setSelectionAnchor = function(row, column) { this.clearSelection(); - this.selectionAnchor = this.$clipPositionToDocument(row, column); }; From abd4734eca858b9655b7b79700e6bc2695a14718 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 4 May 2010 09:52:31 +0200 Subject: [PATCH 149/392] fix bug in the document's insert method --- src/ace/Document.js | 8 ++++---- src/ace/lib/lang.js | 5 +++++ src/test/DocumentTest.js | 22 ++++++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/ace/Document.js b/src/ace/Document.js index 0ed141f1..6bc91f7c 100644 --- a/src/ace/Document.js +++ b/src/ace/Document.js @@ -358,11 +358,11 @@ ace.Document = function(text, mode) { } else { var line = this.lines[position.row] || ""; + var firstLine = line.substring(0, position.column) + newLines[0]; + var lastLine = newLines[newLines.length - 1] + line.substring(position.column); - this.lines[position.row] = line.substring(0, position.column) - + newLines[0]; - this.lines[position.row + 1] = newLines[newLines.length - 1] - + line.substring(position.column); + this.lines[position.row] = firstLine; + this.$insertLines(position.row + 1, [lastLine]); if (newLines.length > 2) { this.$insertLines(position.row + 1, newLines.slice(1, -1)); diff --git a/src/ace/lib/lang.js b/src/ace/lib/lang.js index 40be4580..73ea7720 100644 --- a/src/ace/lib/lang.js +++ b/src/ace/lib/lang.js @@ -57,6 +57,11 @@ } }, + call: function() { + this.cancel(); + fcn(); + }, + cancel: function() { clearTimeout(timer); timer = null; diff --git a/src/test/DocumentTest.js b/src/test/DocumentTest.js index d1002ede..08067696 100644 --- a/src/test/DocumentTest.js +++ b/src/test/DocumentTest.js @@ -123,5 +123,27 @@ var TextDocumentTest = new TestCase("TextDocumentTest", { end: {row: 2, column: 1} }, ["4", "5", "6"].join("\n")); assertEquals(["4", "5", "6"].join("\n"), doc.toString()); + }, + + "test: undo/redo for delete line" : function() { + var doc = new ace.Document(["111", "222", "333"]); + var undoManager = new ace.UndoManager(); + doc.setUndoManager(undoManager); + + var initialText = doc.toString(); + + var editor = new ace.Editor(new MockRenderer(), doc); + + editor.removeLines(); + var removedText = doc.toString(); + + // call normally async code now + doc.$informUndoManager.call(); + + undoManager.undo(); + assertEquals(initialText, doc.toString()); + + undoManager.redo(); + assertEquals(removedText, doc.toString()); } }); \ No newline at end of file From cc0c16ca6fb76582825bbc996bb7cd84855c2b0a Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 4 May 2010 11:34:38 +0200 Subject: [PATCH 150/392] Refactor: replace range map with a proper object --- demo/editor.html | 1 + src/ace/Document.js | 37 ++++----------- src/ace/Editor.js | 47 +++--------------- src/ace/Range.js | 65 +++++++++++++++++++++++++ src/ace/Search.js | 12 +---- src/ace/Selection.js | 13 ++--- src/ace/layer/Marker.js | 37 ++------------- src/ace/mode/MatchingBraceOutdent.js | 13 +---- src/test/DocumentTest.js | 30 ++++++++++-- src/test/RangeTest.js | 71 ++++++++++++++++++++++++++++ 10 files changed, 187 insertions(+), 139 deletions(-) create mode 100644 src/ace/Range.js create mode 100644 src/test/RangeTest.js diff --git a/demo/editor.html b/demo/editor.html index fd5b8c18..35dd4d1e 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -59,6 +59,7 @@ + diff --git a/src/ace/Document.js b/src/ace/Document.js index 6bc91f7c..2df8ae7b 100644 --- a/src/ace/Document.js +++ b/src/ace/Document.js @@ -312,16 +312,7 @@ ace.Document = function(text, mode) { var nl = this.$getNewLineCharacter(); this.$deltas.push({ type: "insert", - range: { - start: { - row: row, - column: 0 - }, - end: { - row: row + lines.length, - column: 0 - } - }, + range: new ace.Range(row, 0, row + lines.length, 0), text: lines.join(nl) + nl }); this.$informUndoManager.schedule(); @@ -378,10 +369,7 @@ ace.Document = function(text, mode) { var nl = this.$getNewLineCharacter(); this.$deltas.push({ type: "insert", - range: { - start: ace.copyObject(position), - end: ace.copyObject(end) - }, + range: ace.Range.fromPoints(position, end), text: text }); this.$informUndoManager.schedule(); @@ -398,7 +386,7 @@ ace.Document = function(text, mode) { this.$remove(range, fromUndo); this.fireChangeEvent(range.start.row, - range.end.row == range.start.row ? range.start.row + !range.isMultiLine() ? range.start.row : undefined); return range.start; @@ -409,10 +397,7 @@ ace.Document = function(text, mode) { var nl = this.$getNewLineCharacter(); this.$deltas.push({ type: "remove", - range: { - start: ace.copyObject(range.start), - end: ace.copyObject(range.end) - }, + range: range.clone(), text: this.getTextRange(range) }); this.$informUndoManager.schedule(); @@ -423,8 +408,8 @@ ace.Document = function(text, mode) { var firstRow = range.start.row; var lastRow = range.end.row; - var row = this.lines[firstRow].substring(0, range.start.column) - + this.lines[lastRow].substring(range.end.column); + var row = this.getLine(firstRow).substring(0, range.start.column) + + this.getLine(lastRow).substring(range.end.column); this.lines.splice(firstRow, lastRow - firstRow + 1, row); @@ -438,6 +423,7 @@ ace.Document = function(text, mode) { var delta = deltas[i]; if (delta.type == "insert") { this.remove(delta.range, true); + this.selection.clearSelection(); this.selection.moveCursorToPosition(delta.range.start); } else { this.insert(delta.range.start, delta.text, true); @@ -494,14 +480,7 @@ ace.Document = function(text, mode) { } } - var deleteRange = { - start: { - column: 0 - }, - end: { - column: outdentLength - } - }; + var deleteRange = new ace.Range(0, 0, 0, outdentLength); for (var i=range.start.row; i<= range.end.row; i++) { diff --git a/src/ace/Editor.js b/src/ace/Editor.js index 9fabcf60..49362563 100644 --- a/src/ace/Editor.js +++ b/src/ace/Editor.js @@ -107,13 +107,7 @@ ace.Editor = function(renderer, doc) { var pos = self.doc.findMatchingBracket(self.getCursorPosition()); if (pos) { - range = { - start: pos, - end: { - row: pos.row, - column: pos.column+1 - } - }; + range = new ace.Range(pos.row, pos.column, pos.row, pos.column=1); self.$bracketHighlight = self.renderer.addMarker(range, "bracket"); } }, 10); @@ -166,16 +160,7 @@ ace.Editor = function(renderer, doc) { if (this.getHighlightActiveLine() && !this.selection.isMultiLine()) { var cursor = this.getCursorPosition(); - var range = { - start: { - row: cursor.row, - column: 0 - }, - end: { - row: cursor.row+1, - column: 0 - } - }; + var range = new ace.Range(cursor.row, 0, cursor.row+1, 0); this.$highlightLineMarker = this.renderer.addMarker(range, "active_line", "line"); } }; @@ -296,13 +281,8 @@ ace.Editor = function(renderer, doc) { var cursor = this.doc.remove(this.getSelectionRange()); this.clearSelection(); } else if (this.$overwrite){ - var range = { - start: cursor, - end: { - row: cursor.row, - column: cursor.column + text.length - } - }; + var range = new ace.Range.fromPoints(cursor, cursor); + range.end.column += text.length; this.doc.remove(range); } @@ -321,13 +301,7 @@ ace.Editor = function(renderer, doc) { if (row !== end.row) { var indent = this.mode.getNextLineIndent(lineState, line, this.doc.getTabString()); if (indent) { - var indentRange = { - start: { - row: row+1, - column: 0 - }, - end : end - }; + var indentRange = new ace.Range(row+1, 0, end.row, end.column); end.column += this.doc.indentRows(indentRange, indent); } } else { @@ -454,16 +428,7 @@ ace.Editor = function(renderer, doc) { var rows = this.$getSelectedRows(); - var range = { - start: { - row: rows.first, - column: 0 - }, - end: { - row: rows.last, - column: 0 - } - }; + var range = new ace.Range(rows.first, 0, rows.last, 0); var state = this.bgTokenizer.getState(this.getCursorPosition().row); var addedColumns = this.mode.toggleCommentLines(state, this.doc, range); diff --git a/src/ace/Range.js b/src/ace/Range.js new file mode 100644 index 00000000..43418cc9 --- /dev/null +++ b/src/ace/Range.js @@ -0,0 +1,65 @@ +ace.provide("ace.Range"); + +ace.Range = function(startRow, startColumn, endRow, endColumn) { + this.start = { + row: startRow, + column: startColumn + }; + + this.end = { + row: endRow, + column: endColumn + }; +}; + +(function() { + + this.clipRows = function(firstRow, lastRow) { + if (this.end.row > lastRow) { + this.end = { + row: lastRow+1, + column: 0 + }; + } + + if (this.start.row > lastRow) { + this.start = { + row: lastRow+1, + column: 0 + }; + } + + if (this.start.row < firstRow) { + this.start = { + row: firstRow, + column: 0 + }; + } + + if (this.end.row < firstRow) { + this.end = { + row: firstRow, + column: 0 + }; + } + return this; + }; + + this.isEmpty = function() { + return (this.start.row == this.end.row && this.start.column == this.end.column); + }; + + this.isMultiLine = function() { + return (this.start.row !== this.end.row); + }; + + this.clone = function() { + return ace.Range.fromPoints(this.start, this.end); + }; + +}).call(ace.Range.prototype); + + +ace.Range.fromPoints = function(start, end) { + return new ace.Range(start.row, start.column, end.row, end.column); +}; \ No newline at end of file diff --git a/src/ace/Search.js b/src/ace/Search.js index 5c4c97dc..d14dd3f8 100644 --- a/src/ace/Search.js +++ b/src/ace/Search.js @@ -142,17 +142,7 @@ ace.Search.SELECTION = 2; this.$rangeFromMatch = function(row, column, length) { - var range = { - start: { - row: row, - column: column - }, - end: { - row: row, - column: column + length - } - }; - return range; + return new ace.Range(row, column, row, column+length); }; this.$assembleRegExp = function() { diff --git a/src/ace/Selection.js b/src/ace/Selection.js index a3cd5be8..6d68825b 100644 --- a/src/ace/Selection.js +++ b/src/ace/Selection.js @@ -27,8 +27,7 @@ ace.Selection = function(doc) { return false; } - var range = this.getRange(); - return (range.start.row !== range.end.row); + return this.getRange().isMultiLine(); }; this.getCursor = function() { @@ -73,16 +72,10 @@ ace.Selection = function(doc) { if (anchor.row > lead.row || (anchor.row == lead.row && anchor.column > lead.column)) { - return { - start : lead, - end : anchor - }; + return ace.Range.fromPoints(lead, anchor); } else { - return { - start : anchor, - end : lead - }; + return ace.Range.fromPoints(anchor, lead); } }; diff --git a/src/ace/layer/Marker.js b/src/ace/layer/Marker.js index 6c129b1a..51b9c9d9 100644 --- a/src/ace/layer/Marker.js +++ b/src/ace/layer/Marker.js @@ -45,30 +45,11 @@ ace.layer.Marker = function(parentEl) { var html = []; for ( var key in this.markers) { var marker = this.markers[key]; - var range = { - start: marker.range.start, - end: marker.range.end - }; - // clip - if (range.start.row > config.lastRow) continue; - if (range.end.row < config.firstRow) continue; + var range = marker.range.clone().clipRows(config.firstRow, config.lastRow); + if (range.isEmpty()) continue; - if (range.end.row > config.lastRow) { - range.end = { - row: config.lastRow+1, - column: 0 - }; - } - - if (range.start.row < config.firstRow) { - range.start = { - row: config.firstRow, - column: 0 - }; - } - - if (range.start.row !== range.end.row) { + if (range.isMultiLine()) { if (marker.type == "text") { this.drawTextMarker(html, range, marker.clazz, config); } else { @@ -86,20 +67,12 @@ ace.layer.Marker = function(parentEl) { // selection start var row = range.start.row; - var lineRange = { start: {}, end: {}}; - - lineRange.start.row = row; - lineRange.start.column = range.start.column; - lineRange.end.row = row; - lineRange.end.column = this.doc.getLine(row).length; + var lineRange = new ace.Range(row, range.start.column, row, this.doc.getLine(row).length); this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig); // selection end var row = range.end.row; - lineRange.start.row = row; - lineRange.start.column = 0; - lineRange.end.row = row; - lineRange.end.column = range.end.column; + var lineRange = new ace.Range(row, 0, row, range.end.column); this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig); for (var row = range.start.row + 1; row < range.end.row; row++) { diff --git a/src/ace/mode/MatchingBraceOutdent.js b/src/ace/mode/MatchingBraceOutdent.js index 5c6e5d0b..62cf8954 100644 --- a/src/ace/mode/MatchingBraceOutdent.js +++ b/src/ace/mode/MatchingBraceOutdent.js @@ -23,18 +23,7 @@ ace.mode.MatchingBraceOutdent = function() {}; if (!openBracePos || openBracePos.row == row) return 0; var indent = this.$getIndent(doc.getLine(openBracePos.row)); - - var range = { - start: { - row: row, - column: 0 - }, - end: { - row: row, - column: column-1 - } - }; - doc.replace(range, indent); + doc.replace(new ace.Range(row, 0, row, column-1), indent); return indent.length - (column-1); }; diff --git a/src/test/DocumentTest.js b/src/test/DocumentTest.js index 08067696..9d788384 100644 --- a/src/test/DocumentTest.js +++ b/src/test/DocumentTest.js @@ -135,15 +135,37 @@ var TextDocumentTest = new TestCase("TextDocumentTest", { var editor = new ace.Editor(new MockRenderer(), doc); editor.removeLines(); - var removedText = doc.toString(); - + var step1 = doc.toString(); + assertEquals("222\n333", step1); // call normally async code now doc.$informUndoManager.call(); + editor.removeLines(); + var step2 = doc.toString(); + assertEquals("333", step2); + // call normally async code now + doc.$informUndoManager.call(); + + editor.removeLines(); + var step3 = doc.toString(); + assertEquals("", step3); + // call normally async code now + doc.$informUndoManager.call(); + + + undoManager.undo(); + assertEquals(step2, doc.toString()); + + undoManager.undo(); + assertEquals(step1, doc.toString()); + undoManager.undo(); assertEquals(initialText, doc.toString()); - undoManager.redo(); - assertEquals(removedText, doc.toString()); + undoManager.undo(); + assertEquals(initialText, doc.toString()); + +// undoManager.redo(); +// assertEquals(removedText, doc.toString()); } }); \ No newline at end of file diff --git a/src/test/RangeTest.js b/src/test/RangeTest.js new file mode 100644 index 00000000..39eb41fd --- /dev/null +++ b/src/test/RangeTest.js @@ -0,0 +1,71 @@ +RangeTest = new TestCase("RangeTest", { + + "test: create range": function() { + var range = new ace.Range(1,2,3,4); + + assertEquals(1, range.start.row); + assertEquals(2, range.start.column); + assertEquals(3, range.end.row); + assertEquals(4, range.end.column); + }, + + "test: create from points": function() { + var range = ace.Range.fromPoints({row: 1, column: 2}, {row:3, column:4}); + + assertEquals(1, range.start.row); + assertEquals(2, range.start.column); + assertEquals(3, range.end.row); + assertEquals(4, range.end.column); + }, + + "test: clip to rows": function() { + var range = new ace.Range(0, 20, 100, 30); + range.clipRows(10, 30); + + assertPosition(10, 0, range.start); + assertPosition(31, 0, range.end); + + var range = new ace.Range(0, 20, 30, 10); + range.clipRows(10, 30); + + assertPosition(10, 0, range.start); + assertPosition(30, 10, range.end); + + var range = new ace.Range(0, 20, 3, 10); + range.clipRows(10, 30); + + assertTrue(range.isEmpty()); + assertPosition(10, 0, range.start); + assertPosition(10, 0, range.end); + }, + + "test: isEmpty": function() { + var range = new ace.Range(1, 2, 1, 2); + assertTrue(range.isEmpty()); + + var range = new ace.Range(1, 2, 1, 6); + assertFalse(range.isEmpty()); + }, + + "test: is multi line": function() { + var range = new ace.Range(1, 2, 1, 6); + assertFalse(range.isMultiLine()); + + var range = new ace.Range(1, 2, 2, 6); + assertTrue(range.isMultiLine()); + }, + + "test: clone": function() { + var range = new ace.Range(1, 2, 3, 4); + var clone = range.clone(); + + assertPosition(1, 2, clone.start); + assertPosition(3, 4, clone.end); + + clone.start.column = 20; + assertPosition(1, 2, range.start); + + clone.end.column = 20; + assertPosition(3, 4, range.end); + } +}); \ No newline at end of file From 3299f0759eb9e822ad282e66fcd1844a81da7eda Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 4 May 2010 11:34:38 +0200 Subject: [PATCH 151/392] Refactor: replace range map with a proper object --- src/test/DocumentTest.js | 5 +---- src/test/mode/CssTest.js | 6 +----- src/test/mode/JavaScriptTest.js | 18 +++--------------- src/test/mode/TextTest.js | 6 +----- src/test/mode/XmlTest.js | 6 +----- 5 files changed, 7 insertions(+), 34 deletions(-) diff --git a/src/test/DocumentTest.js b/src/test/DocumentTest.js index 9d788384..9dc1dd74 100644 --- a/src/test/DocumentTest.js +++ b/src/test/DocumentTest.js @@ -118,10 +118,7 @@ var TextDocumentTest = new TestCase("TextDocumentTest", { doc.setNewLineMode("auto"); assertEquals(["1", "2", "3"].join("\r\n"), doc.toString()); - doc.replace({ - start: {row: 0, column: 0}, - end: {row: 2, column: 1} - }, ["4", "5", "6"].join("\n")); + doc.replace(new ace.Range(0, 0, 2, 1), ["4", "5", "6"].join("\n")); assertEquals(["4", "5", "6"].join("\n"), doc.toString()); }, diff --git a/src/test/mode/CssTest.js b/src/test/mode/CssTest.js index 19e8d781..a7d02c1b 100644 --- a/src/test/mode/CssTest.js +++ b/src/test/mode/CssTest.js @@ -7,11 +7,7 @@ var CssTest = new TestCase("mode.CssTest", { "test: toggle comment lines should not do anything" : function() { var doc = new ace.Document([" abc", "cde", "fg"].join("\n")); - var range = { - start: {row: 0, column: 3}, - end: {row: 1, column: 1} - }; - + var range = new ace.Range(0, 3, 1, 1); var comment = this.mode.toggleCommentLines("start", doc, range); assertEquals([" abc", "cde", "fg"].join("\n"), doc.toString()); }, diff --git a/src/test/mode/JavaScriptTest.js b/src/test/mode/JavaScriptTest.js index fb5ad112..1a03692d 100644 --- a/src/test/mode/JavaScriptTest.js +++ b/src/test/mode/JavaScriptTest.js @@ -16,11 +16,7 @@ var JavaScriptTest = new TestCase("mode.JavaScriptTest", { "test: toggle comment lines should prepend '//' to each line" : function() { var doc = new ace.Document([" abc", "cde", "fg"]); - var range = { - start: {row: 0, column: 3}, - end: {row: 1, column: 1} - }; - + var range = new ace.Range(0, 3, 1, 1); var comment = this.mode.toggleCommentLines("start", doc, range); assertEquals(["// abc", "//cde", "fg"].join("\n"), doc.toString()); }, @@ -28,11 +24,7 @@ var JavaScriptTest = new TestCase("mode.JavaScriptTest", { "test: toggle comment on commented lines should remove leading '//' chars" : function() { var doc = new ace.Document(["// abc", "//cde", "fg"]); - var range = { - start: {row: 0, column: 3}, - end: {row: 1, column: 1} - }; - + var range = new ace.Range(0, 3, 1, 1); var comment = this.mode.toggleCommentLines("start", doc, range); assertEquals([" abc", "cde", "fg"].join("\n"), doc.toString()); }, @@ -40,11 +32,7 @@ var JavaScriptTest = new TestCase("mode.JavaScriptTest", { "test: toggle comment on multiple lines with one commented line prepend '//' to each line" : function() { var doc = new ace.Document(["// abc", "//cde", "fg"]); - var range = { - start: {row: 0, column: 3}, - end: {row: 2, column: 1} - }; - + var range = new ace.Range(0, 3, 2, 1); var comment = this.mode.toggleCommentLines("start", doc, range); assertEquals(["//// abc", "////cde", "//fg"].join("\n"), doc.toString()); }, diff --git a/src/test/mode/TextTest.js b/src/test/mode/TextTest.js index 4150c796..462f317c 100644 --- a/src/test/mode/TextTest.js +++ b/src/test/mode/TextTest.js @@ -7,11 +7,7 @@ var TextTest = new TestCase("mode.TextTest", { "test: toggle comment lines should not do anything" : function() { var doc = new ace.Document([" abc", "cde", "fg"]); - var range = { - start: {row: 0, column: 3}, - end: {row: 1, column: 1} - }; - + var range = new ace.Range(0, 3, 1, 1); var comment = this.mode.toggleCommentLines("start", doc, range); assertEquals([" abc", "cde", "fg"].join("\n"), doc.toString()); }, diff --git a/src/test/mode/XmlTest.js b/src/test/mode/XmlTest.js index df938a67..eccb727b 100644 --- a/src/test/mode/XmlTest.js +++ b/src/test/mode/XmlTest.js @@ -16,11 +16,7 @@ var XmlTest = new TestCase("mode.XmlTest", { "test: toggle comment lines should not do anything" : function() { var doc = new ace.Document([" abc", "cde", "fg"]); - var range = { - start: {row: 0, column: 3}, - end: {row: 1, column: 1} - }; - + var range = new ace.Range(0, 3, 1, 1); var comment = this.mode.toggleCommentLines("start", doc, range); assertEquals([" abc", "cde", "fg"].join("\n"), doc.toString()); } From 4483598166a708aeecbbaec7ff7b46b8130b6c8a Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 4 May 2010 11:41:28 +0200 Subject: [PATCH 152/392] add additional checks --- src/ace/Document.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/ace/Document.js b/src/ace/Document.js index 2df8ae7b..93a68f3d 100644 --- a/src/ace/Document.js +++ b/src/ace/Document.js @@ -304,6 +304,9 @@ ace.Document = function(text, mode) { }; this.$insertLines = function(row, lines) { + if (lines.length == 0) + return; + var args = [row, 0]; args.push.apply(args, lines); this.lines.splice.apply(this.lines, args); @@ -320,6 +323,9 @@ ace.Document = function(text, mode) { }, this.$insert = function(position, text, fromUndo) { + if (text.length == 0) + return; + this.modified = true; if (this.lines.length <= 1) { this.$detectNewLine(text); @@ -383,6 +389,9 @@ ace.Document = function(text, mode) { }; this.remove = function(range, fromUndo) { + if (range.isEmpty()) + return; + this.$remove(range, fromUndo); this.fireChangeEvent(range.start.row, @@ -393,6 +402,9 @@ ace.Document = function(text, mode) { }; this.$remove = function(range, fromUndo) { + if (range.isEmpty()) + return; + if (!fromUndo && this.$undoManager) { var nl = this.$getNewLineCharacter(); this.$deltas.push({ From 574db72780721cd170bc75b663fd3d350262d459 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 4 May 2010 11:55:46 +0200 Subject: [PATCH 153/392] fix undo manger bug --- src/ace/Document.js | 11 ++++++----- src/test/DocumentTest.js | 10 ++++------ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/ace/Document.js b/src/ace/Document.js index 93a68f3d..b0edb4eb 100644 --- a/src/ace/Document.js +++ b/src/ace/Document.js @@ -57,7 +57,8 @@ ace.Document = function(text, mode) { undoManager.setDocument(this); var self = this; this.$informUndoManager = ace.deferredCall(function() { - undoManager.notify(self.$deltas); + if (self.$deltas.length > 0) + undoManager.notify(self.$deltas); self.$deltas = []; }); } @@ -303,7 +304,7 @@ ace.Document = function(text, mode) { return end; }; - this.$insertLines = function(row, lines) { + this.$insertLines = function(row, lines, fromUndo) { if (lines.length == 0) return; @@ -311,7 +312,7 @@ ace.Document = function(text, mode) { args.push.apply(args, lines); this.lines.splice.apply(this.lines, args); - if (this.$undoManager) { + if (!fromUndo && this.$undoManager) { var nl = this.$getNewLineCharacter(); this.$deltas.push({ type: "insert", @@ -359,10 +360,10 @@ ace.Document = function(text, mode) { var lastLine = newLines[newLines.length - 1] + line.substring(position.column); this.lines[position.row] = firstLine; - this.$insertLines(position.row + 1, [lastLine]); + this.$insertLines(position.row + 1, [lastLine], fromUndo); if (newLines.length > 2) { - this.$insertLines(position.row + 1, newLines.slice(1, -1)); + this.$insertLines(position.row + 1, newLines.slice(1, -1), fromUndo); } var end = { diff --git a/src/test/DocumentTest.js b/src/test/DocumentTest.js index 9dc1dd74..67814be0 100644 --- a/src/test/DocumentTest.js +++ b/src/test/DocumentTest.js @@ -134,35 +134,33 @@ var TextDocumentTest = new TestCase("TextDocumentTest", { editor.removeLines(); var step1 = doc.toString(); assertEquals("222\n333", step1); - // call normally async code now doc.$informUndoManager.call(); editor.removeLines(); var step2 = doc.toString(); assertEquals("333", step2); - // call normally async code now doc.$informUndoManager.call(); editor.removeLines(); var step3 = doc.toString(); assertEquals("", step3); - // call normally async code now doc.$informUndoManager.call(); undoManager.undo(); + doc.$informUndoManager.call(); assertEquals(step2, doc.toString()); undoManager.undo(); + doc.$informUndoManager.call(); assertEquals(step1, doc.toString()); undoManager.undo(); + doc.$informUndoManager.call(); assertEquals(initialText, doc.toString()); undoManager.undo(); + doc.$informUndoManager.call(); assertEquals(initialText, doc.toString()); - -// undoManager.redo(); -// assertEquals(removedText, doc.toString()); } }); \ No newline at end of file From d83854e5bc07c63cbdd854a0ac4d162c0148932a Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 4 May 2010 17:04:31 +0200 Subject: [PATCH 154/392] fix triple clicks in IE --- src/ace/lib/event.js | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/ace/lib/event.js b/src/ace/lib/event.js index 4fe4ad14..6fcfc3fa 100644 --- a/src/ace/lib/event.js +++ b/src/ace/lib/event.js @@ -2,6 +2,8 @@ var self = this; + this.isIE = ! + "\v1"; + this.addListener = function(elem, type, callback) { if (elem.addEventListener) { return elem.addEventListener(type, callback, false); @@ -118,27 +120,25 @@ self.addListener(el, "mousewheel", listener); }; - this.autoremoveListener = function(el, type, callback, timeout) { - var listener = function(e) { - clearTimeout(timeoutId); - remove(); - callback(e); - }; - - var remove = function() { - self.removeListener(el, type, listener); - }; - - self.addListener(el, type, listener); - var timeoutId = setTimeout(remove, timeout); - }; - this.addTripleClickListener = function(el, callback) { - self.addListener(el, "mousedown", function() { - self.autoremoveListener(el, "mousedown", function() { - self.autoremoveListener(el, "mousedown", callback, 300); - }, 300); - }); + var clicks = 0; + var listener = function(e) { + clicks += 1; + if (clicks == 1) { + setTimeout(function() { + clicks = 0; + }, 600); + } + + if (clicks == 3) { + clicks = 0; + callback(e); + } + return self.preventDefault(e); + }; + + self.addListener(el, "mousedown", listener); + this.isIE && self.addListener(el, "dblclick", listener); }; this.addKeyListener = function(el, callback) { From 1ed310d5d23676fe03246cca0d801e02913714d9 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 4 May 2010 17:04:57 +0200 Subject: [PATCH 155/392] add a wrapper around all layers to fix mouse selection in IE --- src/ace/VirtualRenderer.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/ace/VirtualRenderer.js b/src/ace/VirtualRenderer.js index 8e4e374e..3472d1a8 100644 --- a/src/ace/VirtualRenderer.js +++ b/src/ace/VirtualRenderer.js @@ -12,16 +12,19 @@ ace.VirtualRenderer = function(container) { this.gutter.className = "gutter"; this.container.appendChild(this.gutter); - this.gutterLayer = new ace.layer.Gutter(this.gutter); - this.markerLayer = new ace.layer.Marker(this.scroller); + this.content = document.createElement("div"); + this.scroller.appendChild(this.content) - var textLayer = this.textLayer = new ace.layer.Text(this.scroller); + this.gutterLayer = new ace.layer.Gutter(this.gutter); + this.markerLayer = new ace.layer.Marker(this.content); + + var textLayer = this.textLayer = new ace.layer.Text(this.content); this.canvas = textLayer.element; this.characterWidth = textLayer.getCharacterWidth(); this.lineHeight = textLayer.getLineHeight(); - this.cursorLayer = new ace.layer.Cursor(this.scroller); + this.cursorLayer = new ace.layer.Cursor(this.content); this.layers = [ this.markerLayer, textLayer, this.cursorLayer ]; @@ -62,8 +65,7 @@ ace.VirtualRenderer = function(container) { }; this.getMouseEventTarget = function() { - // return top most layer - return this.cursorLayer.element; + return this.content; }; this.getFirstVisibleRow = function() { @@ -127,6 +129,7 @@ ace.VirtualRenderer = function(container) { var charCount = this.doc.getWidth(); if (this.$showInvisibles) charCount += 1; + var longestLine = Math.max(this.scroller.clientWidth, Math.round(charCount * this.characterWidth)); var lineCount = Math.ceil(minHeight / this.lineHeight); @@ -140,12 +143,13 @@ ace.VirtualRenderer = function(container) { lineHeight : this.lineHeight, characterWidth : this.characterWidth }; + + this.content.style.marginTop = (-offset) + "px"; 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"; From f8704be5c77ef5133a16ed5d0b77432c4881733b Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 4 May 2010 17:05:41 +0200 Subject: [PATCH 156/392] update triple click experiment --- experiments/triple_click.html | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/experiments/triple_click.html b/experiments/triple_click.html index af6768c0..da953a94 100644 --- a/experiments/triple_click.html +++ b/experiments/triple_click.html @@ -14,13 +14,19 @@ Juhu Kinners - + + From 2851241563787e2e712bb6431b318bf571a57137 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 4 May 2010 17:05:54 +0200 Subject: [PATCH 157/392] fix IE focus issue --- src/ace/Editor.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ace/Editor.js b/src/ace/Editor.js index 49362563..5a5486e4 100644 --- a/src/ace/Editor.js +++ b/src/ace/Editor.js @@ -9,7 +9,10 @@ ace.Editor = function(renderer, doc) { new ace.KeyBinding(container, this); var self = this; ace.addListener(container, "mousedown", function(e) { - self.focus(); + setTimeout(function() {self.focus();}); + return ace.preventDefault(e); + }); + ace.addListener(container, "selectstart", function(e) { return ace.preventDefault(e); }); @@ -107,7 +110,7 @@ ace.Editor = function(renderer, doc) { var pos = self.doc.findMatchingBracket(self.getCursorPosition()); if (pos) { - range = new ace.Range(pos.row, pos.column, pos.row, pos.column=1); + range = new ace.Range(pos.row, pos.column, pos.row, pos.column+1); self.$bracketHighlight = self.renderer.addMarker(range, "bracket"); } }, 10); From 9df0f712c5844d29e00f08d3c6db450c54e9c430 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 4 May 2010 17:06:02 +0200 Subject: [PATCH 158/392] minor refactoring --- src/ace/Document.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/ace/Document.js b/src/ace/Document.js index b0edb4eb..83b5f173 100644 --- a/src/ace/Document.js +++ b/src/ace/Document.js @@ -315,7 +315,7 @@ ace.Document = function(text, mode) { if (!fromUndo && this.$undoManager) { var nl = this.$getNewLineCharacter(); this.$deltas.push({ - type: "insert", + action: "insertText", range: new ace.Range(row, 0, row + lines.length, 0), text: lines.join(nl) + nl }); @@ -375,7 +375,7 @@ ace.Document = function(text, mode) { if (!fromUndo && this.$undoManager) { var nl = this.$getNewLineCharacter(); this.$deltas.push({ - type: "insert", + action: "insertText", range: ace.Range.fromPoints(position, end), text: text }); @@ -409,7 +409,7 @@ ace.Document = function(text, mode) { if (!fromUndo && this.$undoManager) { var nl = this.$getNewLineCharacter(); this.$deltas.push({ - type: "remove", + action: "removeText", range: range.clone(), text: this.getTextRange(range) }); @@ -434,9 +434,8 @@ ace.Document = function(text, mode) { this.selection.clearSelection(); for (var i=0; i Date: Wed, 5 May 2010 10:49:41 +0200 Subject: [PATCH 159/392] add option to show the print margin --- css/editor.css | 7 ++++++ css/tm.css | 5 ++++ src/ace/Editor.js | 24 +++++++++++++++---- src/ace/VirtualRenderer.js | 49 +++++++++++++++++++++++++++++++++----- 4 files changed, 74 insertions(+), 11 deletions(-) diff --git a/css/editor.css b/css/editor.css index 3cd216ea..4aa470f6 100644 --- a/css/editor.css +++ b/css/editor.css @@ -14,6 +14,7 @@ position: absolute; overflow-x: scroll; overflow-y: hidden; + height: 100%; } .editor .sb { @@ -29,11 +30,17 @@ left: 0px; } +.editor .printMargin { + position: absolute; + height: 100%; +} + .layer { z-index: 0; position: absolute; overflow: hidden; white-space: nowrap; + height: 100%; } .text-layer { diff --git a/css/tm.css b/css/tm.css index 1745966c..938c2ee4 100644 --- a/css/tm.css +++ b/css/tm.css @@ -15,6 +15,11 @@ font-size: 12px; } +.editor .printMargin { + width: 1px; + background: rgb(191, 191, 191); +} + .gutter-layer { right: 10px; text-align: right; diff --git a/src/ace/Editor.js b/src/ace/Editor.js index 5a5486e4..1e0244d5 100644 --- a/src/ace/Editor.js +++ b/src/ace/Editor.js @@ -360,18 +360,32 @@ ace.Editor = function(renderer, doc) { return this.$highlightActiveLine; }; - this.$showInvisibles = true; this.setShowInvisibles = function(showInvisibles) { - showInvisibles = !!showInvisibles; - if (this.$showInvisibles == showInvisibles) return; + if (this.getShowInvisibles() == showInvisibles) + return; - this.$showInvisibles = showInvisibles; this.renderer.setShowInvisibles(showInvisibles); this.renderer.draw(); }; this.getShowInvisibles = function() { - return this.showInvisibles; + return this.renderer.getShowInvisibles(); + }; + + this.setShowPrintMargin = function(showPrintMargin) { + this.renderer.setShowPrintMargin(showPrintMargin); + }; + + this.getShowPrintMargin = function() { + return this.renderer.getShowPrintMargin(); + }; + + this.setPrintMarginColumn = function(showPrintMargin) { + this.renderer.setPrintMarginColumn(showPrintMargin); + }; + + this.getPrintMarginColumn = function() { + return this.renderer.getPrintMarginColumn(); }; this.$readOnly = false; diff --git a/src/ace/VirtualRenderer.js b/src/ace/VirtualRenderer.js index 3472d1a8..e8552a53 100644 --- a/src/ace/VirtualRenderer.js +++ b/src/ace/VirtualRenderer.js @@ -13,7 +13,7 @@ ace.VirtualRenderer = function(container) { this.container.appendChild(this.gutter); this.content = document.createElement("div"); - this.scroller.appendChild(this.content) + this.scroller.appendChild(this.content); this.gutterLayer = new ace.layer.Gutter(this.gutter); this.markerLayer = new ace.layer.Marker(this.content); @@ -38,6 +38,7 @@ ace.VirtualRenderer = function(container) { column : 0 }; + this.$updatePrintMargin(); this.onResize(); }; @@ -60,6 +61,45 @@ ace.VirtualRenderer = function(container) { this.textLayer.setShowInvisibles(showInvisibles); }; + this.getShowInvisibles = function() { + return this.showInvisibles; + }; + + this.$showPrintMargin = true; + this.setShowPrintMargin = function(showPrintMargin) { + this.$showPrintMargin = showPrintMargin; + this.$updatePrintMargin(); + }; + + this.getShowPrintMargin = function() { + return this.$showPrintMargin; + }; + + this.$printMarginColumn = 80; + this.setPrintMarginColumn = function(showPrintMargin) { + this.$printMarginColumn = showPrintMargin; + this.$updatePrintMargin(); + }; + + this.getPrintMarginColumn = function() { + return this.$printMarginColumn; + }; + + this.$updatePrintMargin = function() { + if (!this.$showPrintMargin && !this.$printMarginEl) + return; + + if (!this.$printMarginEl) { + this.$printMarginEl = document.createElement("div"); + this.$printMarginEl.className = "printMargin"; + this.content.insertBefore(this.$printMarginEl, this.gutter.element); + } + + var style = this.$printMarginEl.style; + style.left = (this.characterWidth * this.$printMarginColumn) + "px"; + style.visibility = this.$showPrintMargin ? "visible" : "hidden"; + }; + this.getContainerElement = function() { return this.container; }; @@ -79,7 +119,6 @@ ace.VirtualRenderer = function(container) { this.onResize = function() { var height = ace.getInnerHeight(this.container); - this.gutter.style.height = height + "px"; this.scroller.style.height = height + "px"; this.scrollBar.setHeight(height); @@ -129,7 +168,7 @@ ace.VirtualRenderer = function(container) { var charCount = this.doc.getWidth(); if (this.$showInvisibles) charCount += 1; - + var longestLine = Math.max(this.scroller.clientWidth, Math.round(charCount * this.characterWidth)); var lineCount = Math.ceil(minHeight / this.lineHeight); @@ -143,21 +182,19 @@ ace.VirtualRenderer = function(container) { lineHeight : this.lineHeight, characterWidth : this.characterWidth }; - + this.content.style.marginTop = (-offset) + "px"; for ( var i = 0; i < this.layers.length; i++) { var layer = this.layers[i]; var style = layer.element.style; - style.height = minHeight + "px"; style.width = longestLine + "px"; layer.update(layerConfig); }; this.gutterLayer.element.style.marginTop = (-offset) + "px"; - this.gutterLayer.element.style.height = minHeight + "px"; this.gutterLayer.update(layerConfig); this.$updateScrollBar(); From 05fd6df8fe9e42bdfe8b3217f65719c201690d18 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 5 May 2010 10:50:38 +0200 Subject: [PATCH 160/392] fix ie issue to convert document to screen coordinates --- demo/editor.html | 9 ++++----- src/ace/VirtualRenderer.js | 5 +++-- src/test/VirtualRendererTest.js | 13 +++++++++++++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/demo/editor.html b/demo/editor.html index 35dd4d1e..408f59f1 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -115,11 +115,10 @@ + for (var i=0; i + + + + + + + + + + + + + + + + + + + + + + diff --git a/experiments/o3.js b/experiments/o3.js new file mode 100644 index 00000000..b63dd408 --- /dev/null +++ b/experiments/o3.js @@ -0,0 +1,293 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +// #ifdef __WITH_O3 +/** + * Helper class that aids in creating and controlling Ajax O3 instances + * + * @author Mike de Boer + * @version %I%, %G% + * @since 2.1 + * @namespace o3 + * @private + */ + + +(function(global) { +var sId = "Ajax.org", + sDefProduct = "O3Stem", + bAvailable = null, + iVersion = null, + embedded = false, + oO3Count = 0; + bEmbed = false, + sPlatform = null, + oInstMap = {}; + +function detect(o) { + var version; + var name = o && o.fullname ? o.fullname : "Ajax.org O3"; + + if (window.external && window.external.o3) { + version = window.external.o3.versionInfo.match(/v([\d]+\.[\d]+)/)[1]; + embedded = true; + } + else if (navigator.plugins && navigator.plugins[name]) { + version = navigator.plugins[name].description.match(/v([\d]+\.[\d]+)/)[1]; + } + else { + try { + var axo = new ActiveXObject(name); + version = axo.versionInfo.match(/v([\d]+\.[\d]+)/)[1]; + } + catch (e) {} + } + + if (version) { + iVersion = parseFloat(version); + bAvailable = true; + } + else { + iVersion = 0; + bAvailable = false; + } +} + +function sniff() { + var sAgent = navigator.userAgent.toLowerCase(); + var is_opera = sAgent.indexOf("opera") !== -1; + var is_konqueror = sAgent.indexOf("konqueror") != -1; + var is_safari = !is_opera && ((navigator.vendor + && navigator.vendor.match(/Apple/) ? true : false) + || sAgent.indexOf("safari") != -1 || is_konqueror); + var is_ie = (document.all && !is_opera && !is_safari); + bEmbed = !(is_ie && !is_opera); + + // OS sniffing: + + // windows... + if (sAgent.indexOf("win") != -1 || sAgent.indexOf("16bit") != -1) { + sPlatform = "win"; + if (sAgent.indexOf("win16") != -1 + || sAgent.indexOf("16bit") != -1 + || sAgent.indexOf("windows 3.1") != -1 + || sAgent.indexOf("windows 16-bit") != -1) + sPlatform += "16"; + else if (sAgent.indexOf("win32") != -1 + || sAgent.indexOf("32bit") != -1) + sPlatform += "32"; + else if (sAgent.indexOf("win32") != -1 + || sAgent.indexOf("32bit") != -1) + sPlatform += "64"; + } + // mac... + if (sAgent.indexOf("mac") != -1) { + sPlatform = "mac"; + if (sAgent.indexOf("ppc") != -1 || sAgent.indexOf("powerpc") != -1) + sPlatform += "ppc"; + else if (sAgent.indexOf("os x") != -1) + sPlatform += "osx"; + } + // linux... + if (sAgent.indexOf("inux") != -1) { + sPlatform = "linux"; + if (sAgent.indexOf("i686") > -1 || sAgent.indexOf("i386") > -1) + sPlatform += "32"; + else if (sAgent.indexOf("86_64")) + sPlatform += "64"; + else if (sAgent.indexOf("arm")) + sPlatform += "arm"; + } +} + +function installerUrl(o) { + return "http://www.ajax.org/o3/installer" + + (sPlatform ? "/platform/" + sPlatform : "") + + (o.guid ? "/guid/" + encodeURIComponent(o.guid) : ""); +} + +function escapeHtml(s) { + var c, ret = ""; + + if (s == null) return null; + + for (var i = 0, j = s.length; i < j; i++) { + c = s.charCodeAt(i); + if (((c > 96) && (c < 123)) || (( c > 64) && (c < 91)) + || ((c > 43) && (c < 58) && (c != 47)) || (c == 95)) + ret = ret + String.fromCharCode(c); + else + ret = ret + "&#" + c + ";"; + } + return ret; +} + +function createHtml(options) { + var out = []; + if (typeof options.width == "undefined") + options.width = 0; + if (typeof options.height == "undefined") + options.height = 0; + + out.push(bEmbed + ? ''); + if (options.params) { + var i, n, v; + for (i in options.params) { + if (!options.params[i]) continue; + n = escapeHtml(i); + v = escapeHtml(options.params[i]); + out.push(bEmbed + ? n + '="' + v + '" ' + : ' '); + } + } + out.push(bEmbed ? '> ' : ''); + + return out.join(""); +} + +function register(o, options) { + // do some funky registering stuff... + var key = (options.guid ? options.guid : "ajax.o3") + + (options.name ? "." + options.name : ""); + + if (!oInstMap[key]) + oInstMap[key] = []; + oInstMap[key].push(o); +} + +function get(guid) { + for (var i in oInstMap) { + if (i.indexOf(guid) > -1) + return oInstMap[i][0]; + } + + return null; +} + +function destroy(o) { + if (typeof o == "string") //guid provided + o = get(o); + if (!o) return; + // destroy references and domNode of this/ each plugin instance... + var i, j, k, inst; + for (i in oInstMap) { + inst = oInstMap[i]; + if (!inst.length) continue; + for (j = inst.length -1; j >= 0; j--) { + // if we're searching for 'o', check for a match first + if (o && inst[j] != o) continue; + for (k in o) { + if (typeof o[k] == "function") + o[k] = null; + } + inst[j].parentNode.removeChild(inst[j]); + inst.splice(j, 1); + } + } + + if (!o) + oInstMap = {}; +} + +// global API: +global.o3 = { + isAvailable: function(o) { + if (bAvailable === null) + detect(o); + + return bAvailable && ((o && o.version) ? iVersion === o.version : true); + }, + + getVersion: function() { + if (iVersion === null) + detect(); + + return iVersion; + }, + + create: function(guid, options) { + if (!options && typeof guid == "object") { + options = guid; + options.guid = false; + } + else { + options = options || {}; + options.guid = guid || false; + } + if (!options["fullname"]) { + options.fullname = (options.product || sDefProduct) + + (options.guid ? "-" + options.guid : "") + } + + // mini-browser sniffing: + sniff(); + + if (!this.isAvailable(options)) { + var sUrl = installerUrl(options); + return typeof options["oninstallprompt"] == "function" + ? options.oninstallprompt(sUrl) + : window.open(sUrl, "_blank"); + } + + if (typeof options["params"] == "undefined") + options.params = {}; + if (typeof options.params["type"] == "undefined") + options.params.type = "application/" + (options.fullname || "o3-XXXXXXXX"); + + options.id = sId + (options.name ? options.name : ""); + + var oO3; + if (!embedded) { + (options["parent"] || document.body).appendChild( + document.createElement("div")).innerHTML = createHtml(options); + + oO3 = document.getElementById(options.id); + } else { + oO3 = window.external.o3; + } + + if (oO3) { + register(oO3, options); + if (typeof options["onready"] == "function") + options.onready(oO3); + + return oO3; + } + + + return false; + }, + + destroy: destroy, + + get: get +}; + +})(this); + +// #endif diff --git a/experiments/o3debugger.html b/experiments/o3debugger.html new file mode 100644 index 00000000..d26ba98d --- /dev/null +++ b/experiments/o3debugger.html @@ -0,0 +1,532 @@ + + + + + + + + + +

+ + + + diff --git a/experiments/socket.html b/experiments/socket.html new file mode 100644 index 00000000..725d4b20 --- /dev/null +++ b/experiments/socket.html @@ -0,0 +1,43 @@ + + + + + + + + + + +
+ + + + diff --git a/jsTestDriver.conf b/jsTestDriver.conf index f047d8c9..12b88997 100644 --- a/jsTestDriver.conf +++ b/jsTestDriver.conf @@ -11,5 +11,8 @@ load: - src/ace/layer/*.js - src/ace/*.js - - src/test/*.js - - src/test/mode/*.js \ No newline at end of file + - src/debug/*.js + + - src/test/ace/*.js + - src/test/ace/mode/*.js + - src/test/debug/*.js \ No newline at end of file diff --git a/src/ace/BackgroundTokenizer.js b/src/ace/BackgroundTokenizer.js index 64b124d8..c521e026 100644 --- a/src/ace/BackgroundTokenizer.js +++ b/src/ace/BackgroundTokenizer.js @@ -33,8 +33,6 @@ ace.BackgroundTokenizer = function(tokenizer) { self.fireUpdateEvent(startLine, textLines.length - 1); }; - - this.$initEvents(); }; (function(){ diff --git a/src/ace/Document.js b/src/ace/Document.js index 83b5f173..d2de45f3 100644 --- a/src/ace/Document.js +++ b/src/ace/Document.js @@ -1,8 +1,6 @@ ace.provide("ace.Document"); ace.Document = function(text, mode) { - this.$initEvents(); - this.lines = []; this.modified = true; this.selection = new ace.Selection(this); diff --git a/src/ace/MEventEmitter.js b/src/ace/MEventEmitter.js index 22a94dac..cf72f70a 100644 --- a/src/ace/MEventEmitter.js +++ b/src/ace/MEventEmitter.js @@ -2,11 +2,9 @@ ace.provide("ace.MEventEmitter"); ace.MEventEmitter = function() { - this.$initEvents = function() { - this.$eventRegistry = {}; - }; - this.$dispatchEvent = function(eventName, e) { + this.$eventRegistry = this.$eventRegistry || {}; + var listeners = this.$eventRegistry[eventName]; if (!listeners || !listeners.length) return; @@ -19,6 +17,8 @@ ace.MEventEmitter = function() { }; this.addEventListener = function(eventName, callback) { + this.$eventRegistry = this.$eventRegistry || {}; + var listeners = this.$eventRegistry[eventName]; if (!listeners) { var listeners = this.$eventRegistry[eventName] = []; @@ -29,6 +29,8 @@ ace.MEventEmitter = function() { }; this.removeEventListener = function(eventName, callback) { + this.$eventRegistry = this.$eventRegistry || {}; + var listeners = this.$eventRegistry[eventName]; if (!listeners) { return; diff --git a/src/ace/ScrollBar.js b/src/ace/ScrollBar.js index ad622098..8e487f47 100644 --- a/src/ace/ScrollBar.js +++ b/src/ace/ScrollBar.js @@ -1,8 +1,6 @@ ace.provide("ace.ScrollBar"); ace.ScrollBar = function(parent) { - this.$initEvents(); - this.element = document.createElement("div"); this.element.className = "sb"; diff --git a/src/ace/Selection.js b/src/ace/Selection.js index 6d68825b..3f1eb3cc 100644 --- a/src/ace/Selection.js +++ b/src/ace/Selection.js @@ -3,8 +3,6 @@ ace.provide("ace.Selection"); ace.Selection = function(doc) { this.doc = doc; - this.$initEvents(); - this.clearSelection(); this.selectionLead = { row: 0, diff --git a/src/test/ChangeDocumentTest.js b/src/test/ace/ChangeDocumentTest.js similarity index 100% rename from src/test/ChangeDocumentTest.js rename to src/test/ace/ChangeDocumentTest.js diff --git a/src/test/DocumentTest.js b/src/test/ace/DocumentTest.js similarity index 100% rename from src/test/DocumentTest.js rename to src/test/ace/DocumentTest.js diff --git a/src/test/EventEmitterTest.js b/src/test/ace/EventEmitterTest.js similarity index 87% rename from src/test/EventEmitterTest.js rename to src/test/ace/EventEmitterTest.js index fba1793e..2c383c9e 100644 --- a/src/test/EventEmitterTest.js +++ b/src/test/ace/EventEmitterTest.js @@ -1,6 +1,5 @@ -var EventEmitter = function() { - this.$initEvents(); -}; +var EventEmitter = function() {}; + ace.implement(EventEmitter.prototype, ace.MEventEmitter); var EventEmitterTest = new TestCase("EventEmitterTest", { @@ -16,5 +15,4 @@ var EventEmitterTest = new TestCase("EventEmitterTest", { emitter.$dispatchEvent("juhu"); assertTrue(called); } -}); - +}); \ No newline at end of file diff --git a/src/test/MockRenderer.js b/src/test/ace/MockRenderer.js similarity index 100% rename from src/test/MockRenderer.js rename to src/test/ace/MockRenderer.js diff --git a/src/test/NavigationTest.js b/src/test/ace/NavigationTest.js similarity index 100% rename from src/test/NavigationTest.js rename to src/test/ace/NavigationTest.js diff --git a/src/test/RangeTest.js b/src/test/ace/RangeTest.js similarity index 100% rename from src/test/RangeTest.js rename to src/test/ace/RangeTest.js diff --git a/src/test/SearchTest.js b/src/test/ace/SearchTest.js similarity index 100% rename from src/test/SearchTest.js rename to src/test/ace/SearchTest.js diff --git a/src/test/SelectionTest.js b/src/test/ace/SelectionTest.js similarity index 100% rename from src/test/SelectionTest.js rename to src/test/ace/SelectionTest.js diff --git a/src/test/TextEditTest.js b/src/test/ace/TextEditTest.js similarity index 100% rename from src/test/TextEditTest.js rename to src/test/ace/TextEditTest.js diff --git a/src/test/VirtualRendererTest.js b/src/test/ace/VirtualRendererTest.js similarity index 100% rename from src/test/VirtualRendererTest.js rename to src/test/ace/VirtualRendererTest.js diff --git a/src/test/assertions.js b/src/test/ace/assertions.js similarity index 100% rename from src/test/assertions.js rename to src/test/ace/assertions.js diff --git a/src/test/mode/CssTest.js b/src/test/ace/mode/CssTest.js similarity index 100% rename from src/test/mode/CssTest.js rename to src/test/ace/mode/CssTest.js diff --git a/src/test/mode/CssTokenizerTest.js b/src/test/ace/mode/CssTokenizerTest.js similarity index 100% rename from src/test/mode/CssTokenizerTest.js rename to src/test/ace/mode/CssTokenizerTest.js diff --git a/src/test/mode/HtmlTokenizerTest.js b/src/test/ace/mode/HtmlTokenizerTest.js similarity index 100% rename from src/test/mode/HtmlTokenizerTest.js rename to src/test/ace/mode/HtmlTokenizerTest.js diff --git a/src/test/mode/JavaScriptTest.js b/src/test/ace/mode/JavaScriptTest.js similarity index 100% rename from src/test/mode/JavaScriptTest.js rename to src/test/ace/mode/JavaScriptTest.js diff --git a/src/test/mode/JavaScriptTokenizerTest.js b/src/test/ace/mode/JavaScriptTokenizerTest.js similarity index 100% rename from src/test/mode/JavaScriptTokenizerTest.js rename to src/test/ace/mode/JavaScriptTokenizerTest.js diff --git a/src/test/mode/TextTest.js b/src/test/ace/mode/TextTest.js similarity index 100% rename from src/test/mode/TextTest.js rename to src/test/ace/mode/TextTest.js diff --git a/src/test/mode/XmlTest.js b/src/test/ace/mode/XmlTest.js similarity index 100% rename from src/test/mode/XmlTest.js rename to src/test/ace/mode/XmlTest.js diff --git a/src/test/mode/XmlTokenizerTest.js b/src/test/ace/mode/XmlTokenizerTest.js similarity index 100% rename from src/test/mode/XmlTokenizerTest.js rename to src/test/ace/mode/XmlTokenizerTest.js From 92b0339178ff860a939f8dbb9eb1fc4e4fde97b6 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 6 May 2010 17:04:21 +0200 Subject: [PATCH 162/392] add setValue to the Document --- src/ace/Document.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/ace/Document.js b/src/ace/Document.js index d2de45f3..3306eff2 100644 --- a/src/ace/Document.js +++ b/src/ace/Document.js @@ -1,8 +1,8 @@ ace.provide("ace.Document"); ace.Document = function(text, mode) { - this.lines = []; this.modified = true; + this.lines = []; this.selection = new ace.Selection(this); this.listeners = []; @@ -27,6 +27,13 @@ ace.Document = function(text, mode) { return text.split(/\r\n|\r|\n/); }; + this.setValue = function(text) { + var args = [0, this.lines.length]; + args.push.apply(args, this.$split(text)); + this.lines.splice.apply(this.lines, args); + this.fireChangeEvent(0); + }; + this.toString = function() { return this.lines.join(this.$getNewLineCharacter()); }; @@ -323,7 +330,7 @@ ace.Document = function(text, mode) { this.$insert = function(position, text, fromUndo) { if (text.length == 0) - return; + return position; this.modified = true; if (this.lines.length <= 1) { From 34c182705daacdb5e209e838d314ac94ec70ccb2 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 6 May 2010 17:04:47 +0200 Subject: [PATCH 163/392] fix off by one bug in selection --- src/ace/Selection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ace/Selection.js b/src/ace/Selection.js index 3f1eb3cc..be595f5e 100644 --- a/src/ace/Selection.js +++ b/src/ace/Selection.js @@ -345,7 +345,7 @@ ace.Selection = function(doc) { var pos = {}; if (row >= this.doc.getLength()) { - pos.row = this.doc.getLength() - 1; + pos.row = Math.max(0, this.doc.getLength() - 1); pos.column = this.doc.getLine(pos.row).length; } else if (row < 0) { From 71892e4b4166bed8915a5500fb044ca979fd1c97 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 6 May 2010 17:05:14 +0200 Subject: [PATCH 164/392] remove try catch because the related o3 bug has been fixed From 5ac9a3e464f7a7c93d9304a9e1a3c7232a0b9b4d Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 6 May 2010 17:06:08 +0200 Subject: [PATCH 165/392] dispatch messages async to return to the 03 event loop as early as possible From addebed77c5fb007886b053ec7b97a696e84c386 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 6 May 2010 17:06:59 +0200 Subject: [PATCH 166/392] add more V8 debugger commands --- experiments/msg_stream.html | 7 +++++-- src/test/ace/assertions.js | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/experiments/msg_stream.html b/experiments/msg_stream.html index c85e2fb4..8d0991c6 100644 --- a/experiments/msg_stream.html +++ b/experiments/msg_stream.html @@ -48,8 +48,11 @@ function testChrome() { console.log("V8 version:", version.V8Version); }); - v8debugger.scripts(4, null, true, function(scripts) { - console.log("scripts", scripts); + v8debugger.scripts(4, null, false, function(scripts) { + console.log("scripts (short)", scripts); + v8debugger.scripts(4, [scripts[0].id], true, function(scripts) { + console.log("scripts (full)", scripts); + }); }); }); }); diff --git a/src/test/ace/assertions.js b/src/test/ace/assertions.js index c575bf5d..025d19bc 100644 --- a/src/test/ace/assertions.js +++ b/src/test/ace/assertions.js @@ -2,3 +2,7 @@ assertPosition = function(row, column, cursor) { assertEquals(row, cursor.row); assertEquals(column, cursor.column); }; + +assertJsonEquals = function(expectedJson, foundJson) { + assertEquals(JSON.stringify(expectedJson), JSON.stringify(foundJson)); +}; \ No newline at end of file From 7f5ea3d2772d96b88813b42b4640fcd611f5fc7a Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 7 May 2010 16:00:05 +0200 Subject: [PATCH 167/392] add support for "gutter mouse events" --- src/ace/Editor.js | 29 +++++++++++++++++++++++-- src/ace/VirtualRenderer.js | 44 +++++++++++++++++++++++++++----------- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/ace/Editor.js b/src/ace/Editor.js index 1e0244d5..6fdfe5ce 100644 --- a/src/ace/Editor.js +++ b/src/ace/Editor.js @@ -35,6 +35,32 @@ ace.Editor = function(renderer, doc) { (function(){ + ace.implement(this, ace.MEventEmitter); + + this.$forwardEvents = { + gutterclick: 1, + gutterdblclick: 1 + }; + + this.$originalAddEventListener = this.addEventListener; + this.$originalRemoveEventListener = this.removeEventListener; + + this.addEventListener = function(eventName, callback) { + if (this.$forwardEvents[eventName]) { + return this.renderer.addEventListener(eventName, callback); + } else { + return this.$originalAddEventListener(eventName, callback); + } + }; + + this.removeEventListener = function(eventName, callback) { + if (this.$forwardEvents[eventName]) { + return this.renderer.removeEventListener(eventName, callback); + } else { + return this.$originalRemoveEventListener(eventName, callback); + } + }; + this.setDocument = function(doc) { if (this.doc == doc) return; @@ -227,8 +253,7 @@ ace.Editor = function(renderer, doc) { if (mousePageX === undefined || mousePageY === undefined) return; - selectionLead = _self.renderer.screenToTextCoordinates(mousePageX, - mousePageY); + selectionLead = _self.renderer.screenToTextCoordinates(mousePageX, mousePageY); _self.selection.selectToPosition(selectionLead); _self.renderer.scrollCursorIntoView(); diff --git a/src/ace/VirtualRenderer.js b/src/ace/VirtualRenderer.js index 48e02331..dfae5cc7 100644 --- a/src/ace/VirtualRenderer.js +++ b/src/ace/VirtualRenderer.js @@ -8,15 +8,15 @@ ace.VirtualRenderer = function(container) { this.scroller.className = "scroller"; this.container.appendChild(this.scroller); - this.gutter = document.createElement("div"); - this.gutter.className = "gutter"; - this.container.appendChild(this.gutter); + this.$gutter = document.createElement("div"); + this.$gutter.className = "gutter"; + this.container.appendChild(this.$gutter); this.content = document.createElement("div"); this.scroller.appendChild(this.content); - this.gutterLayer = new ace.layer.Gutter(this.gutter); - this.markerLayer = new ace.layer.Marker(this.content); + this.$gutterLayer = new ace.layer.Gutter(this.$gutter); + this.$markerLayer = new ace.layer.Marker(this.content); var textLayer = this.textLayer = new ace.layer.Text(this.content); this.canvas = textLayer.element; @@ -26,7 +26,7 @@ ace.VirtualRenderer = function(container) { this.cursorLayer = new ace.layer.Cursor(this.content); - this.layers = [ this.markerLayer, textLayer, this.cursorLayer ]; + this.layers = [ this.$markerLayer, textLayer, this.cursorLayer ]; this.scrollBar = new ace.ScrollBar(container); this.scrollBar.addEventListener("scroll", ace.bind(this.onScroll, this)); @@ -40,14 +40,19 @@ ace.VirtualRenderer = function(container) { this.$updatePrintMargin(); this.onResize(); + + ace.addListener(this.$gutter, "click", ace.bind(this.$onGutterClick, this)); + ace.addListener(this.$gutter, "dblclick", ace.bind(this.$onGutterClick, this)); }; (function() { + ace.implement(this, ace.MEventEmitter); + this.setDocument = function(doc) { this.lines = doc.lines; this.doc = doc; - this.markerLayer.setDocument(doc); + this.$markerLayer.setDocument(doc); this.textLayer.setDocument(doc); }; @@ -55,6 +60,19 @@ ace.VirtualRenderer = function(container) { this.textLayer.setTokenizer(tokenizer); }; + this.$onGutterClick = function(e) { + var pageX = ace.getDocumentX(e); + var pageY = ace.getDocumentY(e); + + var event = { + row: this.screenToTextCoordinates(pageX, pageY).row, + htmlEvent: e + }; + + var type = "gutter" + e.type; + this.$dispatchEvent(type, event); + }; + this.$showInvisibles = true; this.setShowInvisibles = function(showInvisibles) { this.$showInvisibles = showInvisibles; @@ -92,7 +110,7 @@ ace.VirtualRenderer = function(container) { if (!this.$printMarginEl) { this.$printMarginEl = document.createElement("div"); this.$printMarginEl.className = "printMargin"; - this.content.insertBefore(this.$printMarginEl, this.gutter.element); + this.content.insertBefore(this.$printMarginEl, this.$gutter.element); } var style = this.$printMarginEl.style; @@ -123,7 +141,7 @@ ace.VirtualRenderer = function(container) { this.scrollBar.setHeight(height); var width = ace.getInnerWidth(this.container); - var gutterWidth = this.gutter.offsetWidth; + var gutterWidth = this.$gutter.offsetWidth; this.scroller.style.left = gutterWidth + "px"; this.scroller.style.width = Math.max(0, width - gutterWidth - this.scrollBar.getWidth()) + "px"; @@ -194,8 +212,8 @@ ace.VirtualRenderer = function(container) { layer.update(layerConfig); }; - this.gutterLayer.element.style.marginTop = (-offset) + "px"; - this.gutterLayer.update(layerConfig); + this.$gutterLayer.element.style.marginTop = (-offset) + "px"; + this.$gutterLayer.update(layerConfig); this.$updateScrollBar(); }; @@ -203,11 +221,11 @@ ace.VirtualRenderer = function(container) { this.addMarker = function(range, clazz, type) { range.start = this.$documentToScreenPosition(range.start); range.end = this.$documentToScreenPosition(range.end); - return this.markerLayer.addMarker(range, clazz, type); + return this.$markerLayer.addMarker(range, clazz, type); }; this.removeMarker = function(markerId) { - this.markerLayer.removeMarker(markerId); + this.$markerLayer.removeMarker(markerId); }; this.updateCursor = function(position, overwrite) { From fd4fddb15814e182b1472bcb7cea046e6a739b92 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 7 May 2010 16:01:15 +0200 Subject: [PATCH 168/392] add support for v8 lookup command From 4b02974d68020e825b7184d5e3dabbd6fa44ac2d Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 7 May 2010 16:01:41 +0200 Subject: [PATCH 169/392] fire event if the running state of the debugged code changes From 3d5c67a25cb236dc2e89c2fcdfdd0890c86806ce Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 7 May 2010 16:02:35 +0200 Subject: [PATCH 170/392] minor fixes From 7e0d8213552c21dcb607a715efc2462d2dacba00 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 7 May 2010 16:02:49 +0200 Subject: [PATCH 171/392] sample debug logs From 870f265fe5175710c628560cc8fd6d60c734a7fa Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 7 May 2010 16:02:58 +0200 Subject: [PATCH 172/392] add breakpoint class From ba21db0feb0bdf9dd22236e9d368493b4de1f714 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 11 May 2010 10:21:24 +0200 Subject: [PATCH 173/392] fix some editor rendering issues --- css/editor.css | 4 ++-- src/ace/VirtualRenderer.js | 5 ++++- src/ace/layer/Gutter.js | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/css/editor.css b/css/editor.css index 4aa470f6..7527c2e9 100644 --- a/css/editor.css +++ b/css/editor.css @@ -6,8 +6,7 @@ .scroller { position: absolute; overflow-x: scroll; - overflow-y: hidden; - cursor: text; + overflow-y: hidden; } .gutter { @@ -49,6 +48,7 @@ } .cursor-layer { + cursor: text; } .cursor { diff --git a/src/ace/VirtualRenderer.js b/src/ace/VirtualRenderer.js index dfae5cc7..3d27b52a 100644 --- a/src/ace/VirtualRenderer.js +++ b/src/ace/VirtualRenderer.js @@ -13,6 +13,7 @@ ace.VirtualRenderer = function(container) { this.container.appendChild(this.$gutter); this.content = document.createElement("div"); + this.content.style.position = "absolute"; this.scroller.appendChild(this.content); this.$gutterLayer = new ace.layer.Gutter(this.$gutter); @@ -198,10 +199,12 @@ ace.VirtualRenderer = function(container) { firstRow : firstRow, lastRow : lastRow, lineHeight : this.lineHeight, - characterWidth : this.characterWidth + characterWidth : this.characterWidth, + minHeight : minHeight }; this.content.style.marginTop = (-offset) + "px"; + this.content.style.height = minHeight + "px"; for ( var i = 0; i < this.layers.length; i++) { var layer = this.layers[i]; diff --git a/src/ace/layer/Gutter.js b/src/ace/layer/Gutter.js index 0d6033bc..2436f77b 100644 --- a/src/ace/layer/Gutter.js +++ b/src/ace/layer/Gutter.js @@ -17,6 +17,7 @@ ace.layer.Gutter = function(parentEl) { } this.element.innerHTML = html.join(""); + this.element.style.height = config.minHeight + "px"; }; }).call(ace.layer.Gutter.prototype); \ No newline at end of file From d3b8f98817e88ad25ee8dabfb3e44bb2531dc859 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 11 May 2010 11:48:44 +0200 Subject: [PATCH 174/392] add support for rendering breakpoints --- src/ace/Document.js | 15 +++++++++++++++ src/ace/Editor.js | 9 +++++++++ src/ace/VirtualRenderer.js | 5 +++++ src/ace/layer/Gutter.js | 16 ++++++++++++++-- 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/ace/Document.js b/src/ace/Document.js index 3306eff2..442ebd27 100644 --- a/src/ace/Document.js +++ b/src/ace/Document.js @@ -4,6 +4,7 @@ ace.Document = function(text, mode) { this.modified = true; this.lines = []; this.selection = new ace.Selection(this); + this.$breakpoints = []; this.listeners = []; if (mode) { @@ -109,6 +110,20 @@ ace.Document = function(text, mode) { return this.$tabSize; }; + this.getBreakpoints = function() { + return this.$breakpoints; + }; + + this.setBreakpoint = function(row) { + this.$breakpoints[row] = true; + this.$dispatchEvent("changeBreakpoint", {data: row}); + }; + + this.clearBreakpoint = function(row) { + delete this.$breakpoints[row]; + this.$dispatchEvent("changeBreakpoint", {data: row}); + }; + this.$detectNewLine = function(text) { var match = text.match(/^.*?(\r?\n)/m); if (match) { diff --git a/src/ace/Editor.js b/src/ace/Editor.js index 6fdfe5ce..cab965cc 100644 --- a/src/ace/Editor.js +++ b/src/ace/Editor.js @@ -68,6 +68,7 @@ ace.Editor = function(renderer, doc) { this.doc.removeEventListener("change", this.$onDocumentChange); this.doc.removeEventListener("changeMode", this.$onDocumentModeChange); this.doc.removeEventListener("changeTabSize", this.$onDocumentChangeTabSize); + this.doc.removeEventListener("changeBreakpoint", this.$onDocumentChangeBreakpoint); var selection = this.doc.getSelection(); this.selection.removeEventListener("changeCursor", this.$onCursorChange); @@ -88,6 +89,9 @@ ace.Editor = function(renderer, doc) { this.$onDocumentChangeTabSize = ace.bind(this.renderer.draw, this.renderer); doc.addEventListener("changeTabSize", this.$onDocumentChangeTabSize); + this.$onDocumentChangeBreakpoint = ace.bind(this.onDocumentChangeBreakpoint, this); + this.doc.addEventListener("changeBreakpoint", this.$onDocumentChangeBreakpoint); + this.selection = doc.getSelection(); this.$onCursorChange = ace.bind(this.onCursorChange, this); @@ -103,6 +107,7 @@ ace.Editor = function(renderer, doc) { this.renderer.draw(); this.onCursorChange(); this.onSelectionChange(); + this.onDocumentChangeBreakpoint(); this.renderer.scrollToRow(doc.getScrollTopRow()); }; @@ -209,6 +214,10 @@ ace.Editor = function(renderer, doc) { this.onCursorChange(); }; + this.onDocumentChangeBreakpoint = function() { + this.renderer.setBreakpoints(this.doc.getBreakpoints()); + }; + this.onDocumentModeChange = function() { var mode = this.doc.getMode(); diff --git a/src/ace/VirtualRenderer.js b/src/ace/VirtualRenderer.js index 3d27b52a..71ff2750 100644 --- a/src/ace/VirtualRenderer.js +++ b/src/ace/VirtualRenderer.js @@ -221,6 +221,7 @@ ace.VirtualRenderer = function(container) { this.$updateScrollBar(); }; + this.addMarker = function(range, clazz, type) { range.start = this.$documentToScreenPosition(range.start); range.end = this.$documentToScreenPosition(range.end); @@ -231,6 +232,10 @@ ace.VirtualRenderer = function(container) { this.$markerLayer.removeMarker(markerId); }; + this.setBreakpoints = function(rows) { + this.$gutterLayer.setBreakpoints(rows); + }; + this.updateCursor = function(position, overwrite) { this.cursorLayer.setCursor(this.$documentToScreenPosition(position), overwrite); this.cursorLayer.update(this.layerConfig); diff --git a/src/ace/layer/Gutter.js b/src/ace/layer/Gutter.js index 2436f77b..149e71f3 100644 --- a/src/ace/layer/Gutter.js +++ b/src/ace/layer/Gutter.js @@ -4,15 +4,27 @@ ace.layer.Gutter = function(parentEl) { this.element = document.createElement("div"); this.element.className = "layer gutter-layer"; parentEl.appendChild(this.element); + + this.$breakpoints = []; }; (function() { + this.setBreakpoints = function(rows) { + this.$breakpoints = rows.concat(); + + if (this.$config) + this.update(this.$config); + }; + this.update = function(config) { + this.$config = config; + var html = []; for ( var i = config.firstRow; i <= config.lastRow; i++) { - html.push("
", (i+1), "
"); + html.push("
", (i+1), "
"); html.push(""); } From 4284fd65bf9d867dcf0b91df701e3ab4ef1ef1d9 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 11 May 2010 12:18:49 +0200 Subject: [PATCH 175/392] make sure the element is in the document, while measuring the text size --- src/ace/layer/Text.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ace/layer/Text.js b/src/ace/layer/Text.js index fdb2dc6b..96bbf5c1 100644 --- a/src/ace/layer/Text.js +++ b/src/ace/layer/Text.js @@ -36,6 +36,11 @@ ace.layer.Text = function(parentEl) { style.position = "absolute"; style.overflow = "visible"; + var parent = this.element.parentNode; + var sibling = this.element.nextSibling; + + document.body.appendChild(this.element); + measureNode.innerHTML = new Array(1000).join("Xy"); this.element.appendChild(measureNode); @@ -46,6 +51,12 @@ ace.layer.Text = function(parentEl) { this.characterWidth = measureNode.offsetWidth / 2000; this.element.removeChild(measureNode); + + if (sibling) { + parent.insertBefore(this.element, sibling); + } else { + parent.appendChild(this.element); + } }; this.setDocument = function(doc) { From fa541b64a688f4795758d28f52111f0a5650f9fc Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 11 May 2010 13:37:21 +0200 Subject: [PATCH 176/392] minor changes --- css/editor.css | 9 +++++++-- css/tm.css | 4 ++++ experiments/msg_stream.html | 5 +---- src/ace/Editor.js | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/css/editor.css b/css/editor.css index 7527c2e9..55ecf567 100644 --- a/css/editor.css +++ b/css/editor.css @@ -63,16 +63,21 @@ .marker-layer { } -.marker-layer .selection { +.marker-layer .step { position: absolute; z-index: 2; } -.marker-layer .bracket { +.marker-layer .selection { position: absolute; z-index: 3; } +.marker-layer .bracket { + position: absolute; + z-index: 4; +} + .marker-layer .active_line { position: absolute; z-index: 1; diff --git a/css/tm.css b/css/tm.css index 938c2ee4..bbb9b657 100644 --- a/css/tm.css +++ b/css/tm.css @@ -89,6 +89,10 @@ background: rgb(181, 213, 255); } +.ce .marker-layer .step { + background: rgb(198, 219, 174); +} + .marker-layer .bracket { margin: -1px 0 0 -1px; border: 1px solid rgb(192, 192, 192); diff --git a/experiments/msg_stream.html b/experiments/msg_stream.html index 8d0991c6..72d90e2b 100644 --- a/experiments/msg_stream.html +++ b/experiments/msg_stream.html @@ -48,11 +48,8 @@ function testChrome() { console.log("V8 version:", version.V8Version); }); - v8debugger.scripts(4, null, false, function(scripts) { + v8debugger.scripts(4, null, true, function(scripts) { console.log("scripts (short)", scripts); - v8debugger.scripts(4, [scripts[0].id], true, function(scripts) { - console.log("scripts (full)", scripts); - }); }); }); }); diff --git a/src/ace/Editor.js b/src/ace/Editor.js index cab965cc..3d605239 100644 --- a/src/ace/Editor.js +++ b/src/ace/Editor.js @@ -141,7 +141,7 @@ ace.Editor = function(renderer, doc) { var pos = self.doc.findMatchingBracket(self.getCursorPosition()); if (pos) { - range = new ace.Range(pos.row, pos.column, pos.row, pos.column+1); + var range = new ace.Range(pos.row, pos.column, pos.row, pos.column+1); self.$bracketHighlight = self.renderer.addMarker(range, "bracket"); } }, 10); From 720c691fa48d98a2877dbe3623ca763e4473d6fc Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 11 May 2010 15:28:57 +0200 Subject: [PATCH 177/392] update o3.js --- experiments/o3.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/experiments/o3.js b/experiments/o3.js index b63dd408..2941d13d 100644 --- a/experiments/o3.js +++ b/experiments/o3.js @@ -50,8 +50,19 @@ function detect(o) { version = window.external.o3.versionInfo.match(/v([\d]+\.[\d]+)/)[1]; embedded = true; } - else if (navigator.plugins && navigator.plugins[name]) { - version = navigator.plugins[name].description.match(/v([\d]+\.[\d]+)/)[1]; + + else if (navigator.plugins) { + if (navigator.plugins[name]) { + version = navigator.plugins[name].description.match(/v([\d]+\.[\d]+)/)[1]; + } + else { + // try sniffing the mimeTypes + name = "application/" + name; + for (var i = 0, l = navigator.mimeTypes.length; i < l; ++i) { + if (navigator.mimeTypes[i].type == name) + version = "0.9"; + } + } } else { try { From 7205ed3f284715c005ded9175a4750186ef920bb Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 11 May 2010 16:19:09 +0200 Subject: [PATCH 178/392] fix print margin rendering --- src/ace/VirtualRenderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ace/VirtualRenderer.js b/src/ace/VirtualRenderer.js index 71ff2750..0e65e529 100644 --- a/src/ace/VirtualRenderer.js +++ b/src/ace/VirtualRenderer.js @@ -111,7 +111,7 @@ ace.VirtualRenderer = function(container) { if (!this.$printMarginEl) { this.$printMarginEl = document.createElement("div"); this.$printMarginEl.className = "printMargin"; - this.content.insertBefore(this.$printMarginEl, this.$gutter.element); + this.content.insertBefore(this.$printMarginEl, this.$markerLayer.element); } var style = this.$printMarginEl.style; From 661d5266015d123792f478a999328153ad233f79 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 13 May 2010 09:53:26 +0200 Subject: [PATCH 179/392] add backtrace v8 command From 85749b38aa996947f3a9b3621be5641d9b286f50 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 13 May 2010 09:53:37 +0200 Subject: [PATCH 180/392] fire change events in the editor --- src/ace/Editor.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ace/Editor.js b/src/ace/Editor.js index 3d605239..a0e4aca1 100644 --- a/src/ace/Editor.js +++ b/src/ace/Editor.js @@ -360,6 +360,8 @@ ace.Editor = function(renderer, doc) { this.$blockScrolling = true; this.onCursorChange(); this.$blockScrolling = false; + + this.$dispatchEvent("changeOverwrite", {data: overwrite}); }; this.getOverwrite = function() { @@ -376,6 +378,7 @@ ace.Editor = function(renderer, doc) { this.$selectionStyle = style; this.onSelectionChange(); + this.$dispatchEvent("changeSelectionStyle", {data: style}); }; this.getSelectionStyle = function() { From f4b8d27cc1d439186fb7b068ec4a5c6573dce319 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 13 May 2010 09:53:44 +0200 Subject: [PATCH 181/392] minor --- src/test/ace/MockRenderer.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/ace/MockRenderer.js b/src/test/ace/MockRenderer.js index f72bae3b..1d7a780b 100644 --- a/src/test/ace/MockRenderer.js +++ b/src/test/ace/MockRenderer.js @@ -70,3 +70,6 @@ MockRenderer.prototype.updateLines = function(startRow, endRow) { MockRenderer.prototype.addMarker = function() { }; + +MockRenderer.prototype.setBreakpoints = function() { +}; From 841113a6f399fe1623635d28f4fd565b03a96155 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 13 May 2010 10:13:04 +0200 Subject: [PATCH 182/392] listen and react on character size changes --- src/ace/VirtualRenderer.js | 6 ++++++ src/ace/layer/Text.js | 38 ++++++++++++++++++++++---------------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/ace/VirtualRenderer.js b/src/ace/VirtualRenderer.js index 0e65e529..faed30ed 100644 --- a/src/ace/VirtualRenderer.js +++ b/src/ace/VirtualRenderer.js @@ -42,6 +42,12 @@ ace.VirtualRenderer = function(container) { this.$updatePrintMargin(); this.onResize(); + var self = this; + this.textLayer.addEventListener("changeCharaterSize", function() { + self.characterWidth = textLayer.getCharacterWidth(); + self.lineHeight = textLayer.getLineHeight(); + self.onResize(); + }); ace.addListener(this.$gutter, "click", ace.bind(this.$onGutterClick, this)); ace.addListener(this.$gutter, "dblclick", ace.bind(this.$onGutterClick, this)); }; diff --git a/src/ace/layer/Text.js b/src/ace/layer/Text.js index 96bbf5c1..651cec98 100644 --- a/src/ace/layer/Text.js +++ b/src/ace/layer/Text.js @@ -5,11 +5,14 @@ ace.layer.Text = function(parentEl) { this.element.className = "layer text-layer"; parentEl.appendChild(this.element); - this.$measureSizes(); + this.$characterSize = this.$measureSizes(); + this.$pollSizeChanges(); }; (function() { + ace.implement(this, ace.MEventEmitter); + this.EOF_CHAR = "¶"; this.EOL_CHAR = "¬"; this.TAB_CHAR = "→"; @@ -20,11 +23,22 @@ ace.layer.Text = function(parentEl) { }; this.getLineHeight = function() { - return this.lineHeight; + return this.$characterSize.height || 1; }; this.getCharacterWidth = function() { - return this.characterWidth; + return this.$characterSize.width || 1; + }; + + this.$pollSizeChanges = function() { + var self = this; + setInterval(function() { + var size = self.$measureSizes(); + if (self.$characterSize.width !== size.width || self.$characterSize.height !== size.height) { + self.$characterSize = size; + self.$dispatchEvent("changeCharaterSize", {data: size}); + } + }, 500); }; this.$measureSizes = function() { @@ -36,27 +50,19 @@ ace.layer.Text = function(parentEl) { style.position = "absolute"; style.overflow = "visible"; - var parent = this.element.parentNode; - var sibling = this.element.nextSibling; - - document.body.appendChild(this.element); - measureNode.innerHTML = new Array(1000).join("Xy"); this.element.appendChild(measureNode); // in FF 3.6 monospace fonts can have a fixed sub pixel width. // that's why we have to measure many characters // Note: characterWidth can be a float! - this.lineHeight = measureNode.offsetHeight; - this.characterWidth = measureNode.offsetWidth / 2000; + var size = { + height: measureNode.offsetHeight, + width: measureNode.offsetWidth / 2000 + }; this.element.removeChild(measureNode); - - if (sibling) { - parent.insertBefore(this.element, sibling); - } else { - parent.appendChild(this.element); - } + return size; }; this.setDocument = function(doc) { From 869e37119a4cca2a16ad336fbb7e42159159b88a Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 13 May 2010 16:58:09 +0200 Subject: [PATCH 183/392] move print margin marker above the marker layer --- src/ace/VirtualRenderer.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/ace/VirtualRenderer.js b/src/ace/VirtualRenderer.js index faed30ed..d8d5f8a2 100644 --- a/src/ace/VirtualRenderer.js +++ b/src/ace/VirtualRenderer.js @@ -19,15 +19,15 @@ ace.VirtualRenderer = function(container) { this.$gutterLayer = new ace.layer.Gutter(this.$gutter); this.$markerLayer = new ace.layer.Marker(this.content); - var textLayer = this.textLayer = new ace.layer.Text(this.content); + var textLayer = this.$textLayer = new ace.layer.Text(this.content); this.canvas = textLayer.element; this.characterWidth = textLayer.getCharacterWidth(); this.lineHeight = textLayer.getLineHeight(); - this.cursorLayer = new ace.layer.Cursor(this.content); + this.$cursorLayer = new ace.layer.Cursor(this.content); - this.layers = [ this.$markerLayer, textLayer, this.cursorLayer ]; + this.layers = [ this.$markerLayer, textLayer, this.$cursorLayer ]; this.scrollBar = new ace.ScrollBar(container); this.scrollBar.addEventListener("scroll", ace.bind(this.onScroll, this)); @@ -43,7 +43,7 @@ ace.VirtualRenderer = function(container) { this.onResize(); var self = this; - this.textLayer.addEventListener("changeCharaterSize", function() { + this.$textLayer.addEventListener("changeCharaterSize", function() { self.characterWidth = textLayer.getCharacterWidth(); self.lineHeight = textLayer.getLineHeight(); self.onResize(); @@ -60,11 +60,11 @@ ace.VirtualRenderer = function(container) { this.lines = doc.lines; this.doc = doc; this.$markerLayer.setDocument(doc); - this.textLayer.setDocument(doc); + this.$textLayer.setDocument(doc); }; this.setTokenizer = function(tokenizer) { - this.textLayer.setTokenizer(tokenizer); + this.$textLayer.setTokenizer(tokenizer); }; this.$onGutterClick = function(e) { @@ -83,7 +83,7 @@ ace.VirtualRenderer = function(container) { this.$showInvisibles = true; this.setShowInvisibles = function(showInvisibles) { this.$showInvisibles = showInvisibles; - this.textLayer.setShowInvisibles(showInvisibles); + this.$textLayer.setShowInvisibles(showInvisibles); }; this.getShowInvisibles = function() { @@ -117,7 +117,7 @@ ace.VirtualRenderer = function(container) { if (!this.$printMarginEl) { this.$printMarginEl = document.createElement("div"); this.$printMarginEl.className = "printMargin"; - this.content.insertBefore(this.$printMarginEl, this.$markerLayer.element); + this.content.insertBefore(this.$printMarginEl, this.$cursorLayer.element); } var style = this.$printMarginEl.style; @@ -181,7 +181,7 @@ ace.VirtualRenderer = function(container) { } // else update only the changed rows - this.textLayer.updateLines(layerConfig, firstRow, lastRow); + this.$textLayer.updateLines(layerConfig, firstRow, lastRow); }; this.draw = function() { @@ -243,8 +243,8 @@ ace.VirtualRenderer = function(container) { }; this.updateCursor = function(position, overwrite) { - this.cursorLayer.setCursor(this.$documentToScreenPosition(position), overwrite); - this.cursorLayer.update(this.layerConfig); + this.$cursorLayer.setCursor(this.$documentToScreenPosition(position), overwrite); + this.$cursorLayer.update(this.layerConfig); }; this.$documentToScreenPosition = function(pos) { @@ -302,15 +302,15 @@ ace.VirtualRenderer = function(container) { }; this.hideCursor = function() { - this.cursorLayer.hideCursor(); + this.$cursorLayer.hideCursor(); }; this.showCursor = function() { - this.cursorLayer.showCursor(); + this.$cursorLayer.showCursor(); }; this.scrollCursorIntoView = function() { - var pos = this.cursorLayer.getPixelPosition(); + var pos = this.$cursorLayer.getPixelPosition(); var left = pos.left; var top = pos.top; From 7fa11b08d1394a01f2f2dcaaabbc5712856bd2eb Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 13 May 2010 16:58:33 +0200 Subject: [PATCH 184/392] fix key input for windows --- src/ace/lib/core.js | 6 ++++++ src/ace/lib/event.js | 24 +++++++++++++----------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/ace/lib/core.js b/src/ace/lib/core.js index 92436573..c58e1b98 100644 --- a/src/ace/lib/core.js +++ b/src/ace/lib/core.js @@ -3,6 +3,12 @@ if (!window.ace) (function() { + var os = (navigator.platform.match(/mac|win|linux/i) || ["other"])[0].toLowerCase(); + + this.isWin = (os == "win"); + this.isMac = (os == "mac"); + this.isLinux = (os == "linux"); + this.provide = function(namespace) { var parts = namespace.split("."); var obj = window; diff --git a/src/ace/lib/event.js b/src/ace/lib/event.js index 6fcfc3fa..a8cac740 100644 --- a/src/ace/lib/event.js +++ b/src/ace/lib/event.js @@ -3,7 +3,7 @@ var self = this; this.isIE = ! + "\v1"; - + this.addListener = function(elem, type, callback) { if (elem.addEventListener) { return elem.addEventListener(type, callback, false); @@ -129,14 +129,14 @@ clicks = 0; }, 600); } - + if (clicks == 3) { clicks = 0; callback(e); } return self.preventDefault(e); }; - + self.addListener(el, "mousedown", listener); this.isIE && self.addListener(el, "dblclick", listener); }; @@ -149,13 +149,15 @@ return callback(e); }); - self.addListener(el, "keypress", function(e) { - var keyId = e.keyIdentifier || e.keyCode; - if (lastDown !== keyId) { - return callback(e); - } else { - lastDown = null; - } - }); + if (ace.isMac) { + self.addListener(el, "keypress", function(e) { + var keyId = e.keyIdentifier || e.keyCode; + if (lastDown !== keyId) { + return callback(e); + } else { + lastDown = null; + } + }); + } }; }).call(ace); \ No newline at end of file From 3ce17ef50e44d1f16b344f7da1369520a3c6ea2a Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 13 May 2010 16:58:49 +0200 Subject: [PATCH 185/392] fix resize of editor demo (FF) --- demo/editor.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/demo/editor.html b/demo/editor.html index 408f59f1..57ec722e 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -11,9 +11,11 @@ html { height: 100%; + overflow: hidden; } body { + overflow: hidden; margin: 0; padding: 0; font: sans-serif; From 01ddab9597ba65fea327a27cd62a2a146674dee4 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 13 May 2010 17:01:19 +0200 Subject: [PATCH 186/392] Add key bindings for Control-End and Control-Home --- src/ace/KeyBinding.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ace/KeyBinding.js b/src/ace/KeyBinding.js index fb82fca1..276c05be 100644 --- a/src/ace/KeyBinding.js +++ b/src/ace/KeyBinding.js @@ -108,7 +108,7 @@ ace.KeyBinding = function(element, editor) { this.selection.selectFileStart(); }; - this["Control-Up"] = function() { + this["Control-Home"] = this["Control-Up"] = function() { this.editor.navigateFileStart(); }; @@ -132,7 +132,7 @@ ace.KeyBinding = function(element, editor) { this.selection.selectFileEnd(); }; - this["Control-Down"] = function() { + this["Control-End"] = this["Control-Down"] = function() { this.editor.navigateFileEnd(); }; From 3ec57748a9e72e183dbdc497b9a3cbc808cd9082 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 13 May 2010 20:51:02 +0200 Subject: [PATCH 187/392] fix text measure --- src/ace/layer/Text.js | 22 ++++++++++++++++++---- src/ace/lib/dom.js | 2 +- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/ace/layer/Text.js b/src/ace/layer/Text.js index 651cec98..8b6989e4 100644 --- a/src/ace/layer/Text.js +++ b/src/ace/layer/Text.js @@ -6,7 +6,7 @@ ace.layer.Text = function(parentEl) { parentEl.appendChild(this.element); this.$characterSize = this.$measureSizes(); - this.$pollSizeChanges(); + //this.$pollSizeChanges(); }; (function() { @@ -41,27 +41,41 @@ ace.layer.Text = function(parentEl) { }, 500); }; + this.$fontStyles = { + fontFamily : 1, + fontSize : 1, + fontWeight : 1, + fontStyle : 1, + lineHeight : 1 + }, + this.$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 = new Array(1000).join("Xy"); - this.element.appendChild(measureNode); + for (prop in this.$fontStyles) { + var value = ace.computedStyle(this.element, prop); + style[prop] = value; + } // in FF 3.6 monospace fonts can have a fixed sub pixel width. // that's why we have to measure many characters // Note: characterWidth can be a float! + measureNode.innerHTML = new Array(1000).join("Xy"); + document.body.insertBefore(measureNode, document.body.firstChild); + var size = { height: measureNode.offsetHeight, width: measureNode.offsetWidth / 2000 }; - this.element.removeChild(measureNode); + document.body.removeChild(measureNode); return size; }; diff --git a/src/ace/lib/dom.js b/src/ace/lib/dom.js index d42b4fb3..709eec97 100644 --- a/src/ace/lib/dom.js +++ b/src/ace/lib/dom.js @@ -47,7 +47,7 @@ this.computedStyle = function(element, style) { if (window.getComputedStyle) { - return (window.getComputedStyle(element, null))[style]; + return (window.getComputedStyle(element, "") || {})[style] || ""; } else { return element.currentStyle[style]; From a9952b56099c39e4d25d816c53285d03f3379b4d Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 14 May 2010 10:18:41 +0200 Subject: [PATCH 188/392] fix text measure regression --- src/ace/layer/Text.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ace/layer/Text.js b/src/ace/layer/Text.js index 8b6989e4..df7f15c4 100644 --- a/src/ace/layer/Text.js +++ b/src/ace/layer/Text.js @@ -59,7 +59,7 @@ ace.layer.Text = function(parentEl) { style.position = "absolute"; style.overflow = "visible"; - for (prop in this.$fontStyles) { + for (var prop in this.$fontStyles) { var value = ace.computedStyle(this.element, prop); style[prop] = value; } @@ -124,7 +124,7 @@ ace.layer.Text = function(parentEl) { var html = []; for ( var i = config.firstRow; i <= config.lastRow; i++) { - html.push("
"); this.renderLine(html, i), html.push("
"); } From d4e253ad14e14b0362b52c5e650fcdfaa3b40d59 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Fri, 14 May 2010 12:42:10 +0200 Subject: [PATCH 189/392] prefix all editor css classes --- css/editor.css | 32 +++++++++++++------------- css/tm.css | 46 +++++++++++++++++++------------------- src/ace/Editor.js | 6 ++--- src/ace/ScrollBar.js | 2 +- src/ace/VirtualRenderer.js | 12 +++++----- src/ace/layer/Cursor.js | 8 +++---- src/ace/layer/Gutter.js | 6 ++--- src/ace/layer/Marker.js | 2 +- src/ace/layer/Text.js | 14 ++++++------ 9 files changed, 64 insertions(+), 64 deletions(-) diff --git a/css/editor.css b/css/editor.css index 55ecf567..e6afd1fa 100644 --- a/css/editor.css +++ b/css/editor.css @@ -1,40 +1,40 @@ -.editor { +.ace_editor { position: absolute; overflow: hidden; } -.scroller { +.ace_scroller { position: absolute; overflow-x: scroll; overflow-y: hidden; } -.gutter { +.ace_gutter { position: absolute; overflow-x: scroll; overflow-y: hidden; height: 100%; } -.editor .sb { +.ace_editor .ace_sb { position: absolute; overflow-x: hidden; overflow-y: scroll; right: 0; } -.editor .sb div { +.ace_editor .ace_sb div { position: absolute; width: 1px; left: 0px; } -.editor .printMargin { +.ace_editor .ace_printMargin { position: absolute; height: 100%; } -.layer { +.ace_layer { z-index: 0; position: absolute; overflow: hidden; @@ -42,43 +42,43 @@ height: 100%; } -.text-layer { +.ace_text-layer { font-family: Monaco, "Courier New", monospace; color: black; } -.cursor-layer { +.ace_cursor-layer { cursor: text; } -.cursor { +.ace_cursor { z-index: 3; position: absolute; } -.line { +.ace_line { white-space: nowrap; } -.marker-layer { +.ace_marker-layer { } -.marker-layer .step { +.ace_marker-layer .ace_step { position: absolute; z-index: 2; } -.marker-layer .selection { +.ace_marker-layer .ace_selection { position: absolute; z-index: 3; } -.marker-layer .bracket { +.ace_marker-layer .ace_bracket { position: absolute; z-index: 4; } -.marker-layer .active_line { +.ace_marker-layer .ace_active_line { position: absolute; z-index: 1; } \ No newline at end of file diff --git a/css/tm.css b/css/tm.css index bbb9b657..71d59656 100644 --- a/css/tm.css +++ b/css/tm.css @@ -1,12 +1,12 @@ -.editor { +.ace_editor { border: 2px solid rgb(159, 159, 159); } -.editor.focus { +.ace_editor.ace_focus { border: 2px solid #327fbd;; } -.gutter { +.ace_gutter { width: 40px; background: rgb(227, 227, 227); border-right: 1px solid rgb(159, 159, 159); @@ -15,89 +15,89 @@ font-size: 12px; } -.editor .printMargin { +.ace_editor .ace_printMargin { width: 1px; background: rgb(191, 191, 191); } -.gutter-layer { +.ace_gutter-layer { right: 10px; text-align: right; } -.text-layer { +.ace_text-layer { font-family: Monaco, "Courier New", monospace; font-size: 12px; cursor: text; } -.cursor { +.ace_cursor { border-left: 2px solid black; } -.cursor.overwrite { +.ace_cursor.ace_overwrite { border-left: 0px; border-bottom: 1px solid black; } -.line .invisible { +.ace_line .ace_invisible { color: rgb(191, 191, 191); } -.line .keyword { +.ace_line .ace_keyword { color: blue; } -.line .buildin-constant { +.ace_line .ace_buildin-constant { color: rgb(88, 72, 246); } -.line .library-constant { +.ace_line .ace_library-constant { color: rgb(6, 150, 14); } -.line .buildin-function { +.ace_line .ace_buildin-function { color: rgb(60, 76, 114); } -.line .string { +.ace_line .ace_string { color: rgb(3, 106, 7); } -.line .comment { +.ace_line .ace_comment { font-style: italic; color: rgb(76, 136, 107); } -.line .doc-comment { +.ace_line .ace_doc-comment { color: rgb(0, 102, 255); } -.line .doc-comment-tag { +.ace_line .ace_doc-comment-tag { color: rgb(128, 159, 191); } -.line .number { +.ace_line .ace_number { color: rgb(0, 0, 205); } -.line .xml_pe { +.ace_line .ace_xml_pe { color: rgb(104, 104, 91); } -.marker-layer .selection { +.ace_marker-layer .ace_selection { background: rgb(181, 213, 255); } -.ce .marker-layer .step { +.ace_marker-layer .ace_step { background: rgb(198, 219, 174); } -.marker-layer .bracket { +.ace_marker-layer .ace_bracket { margin: -1px 0 0 -1px; border: 1px solid rgb(192, 192, 192); } -.marker-layer .active_line { +.ace_marker-layer .ace_active_line { background: rgb(232, 242, 254); } \ No newline at end of file diff --git a/src/ace/Editor.js b/src/ace/Editor.js index a0e4aca1..8c490d50 100644 --- a/src/ace/Editor.js +++ b/src/ace/Editor.js @@ -142,7 +142,7 @@ ace.Editor = function(renderer, doc) { var pos = self.doc.findMatchingBracket(self.getCursorPosition()); if (pos) { var range = new ace.Range(pos.row, pos.column, pos.row, pos.column+1); - self.$bracketHighlight = self.renderer.addMarker(range, "bracket"); + self.$bracketHighlight = self.renderer.addMarker(range, "ace_bracket"); } }, 10); }; @@ -195,7 +195,7 @@ ace.Editor = function(renderer, doc) { if (this.getHighlightActiveLine() && !this.selection.isMultiLine()) { var cursor = this.getCursorPosition(); var range = new ace.Range(cursor.row, 0, cursor.row+1, 0); - this.$highlightLineMarker = this.renderer.addMarker(range, "active_line", "line"); + this.$highlightLineMarker = this.renderer.addMarker(range, "ace_active_line", "line"); } }; @@ -208,7 +208,7 @@ ace.Editor = function(renderer, doc) { if (!this.selection.isEmpty()) { var range = this.selection.getRange(); var style = this.getSelectionStyle(); - this.$selectionMarker = this.renderer.addMarker(range, "selection", style); + this.$selectionMarker = this.renderer.addMarker(range, "ace_selection", style); } this.onCursorChange(); diff --git a/src/ace/ScrollBar.js b/src/ace/ScrollBar.js index 8e487f47..c652418a 100644 --- a/src/ace/ScrollBar.js +++ b/src/ace/ScrollBar.js @@ -2,7 +2,7 @@ ace.provide("ace.ScrollBar"); ace.ScrollBar = function(parent) { this.element = document.createElement("div"); - this.element.className = "sb"; + this.element.className = "ace_sb"; this.inner = document.createElement("div"); this.element.appendChild(this.inner); diff --git a/src/ace/VirtualRenderer.js b/src/ace/VirtualRenderer.js index d8d5f8a2..7996ae37 100644 --- a/src/ace/VirtualRenderer.js +++ b/src/ace/VirtualRenderer.js @@ -2,14 +2,14 @@ ace.provide("ace.VirtualRenderer"); ace.VirtualRenderer = function(container) { this.container = container; - ace.addCssClass(this.container, "editor"); + ace.addCssClass(this.container, "ace_editor"); this.scroller = document.createElement("div"); - this.scroller.className = "scroller"; + this.scroller.className = "ace_scroller"; this.container.appendChild(this.scroller); this.$gutter = document.createElement("div"); - this.$gutter.className = "gutter"; + this.$gutter.className = "ace_gutter"; this.container.appendChild(this.$gutter); this.content = document.createElement("div"); @@ -116,7 +116,7 @@ ace.VirtualRenderer = function(container) { if (!this.$printMarginEl) { this.$printMarginEl = document.createElement("div"); - this.$printMarginEl.className = "printMargin"; + this.$printMarginEl.className = "ace_printMargin"; this.content.insertBefore(this.$printMarginEl, this.$cursorLayer.element); } @@ -374,11 +374,11 @@ ace.VirtualRenderer = function(container) { }; this.visualizeFocus = function() { - ace.addCssClass(this.container, "focus"); + ace.addCssClass(this.container, "ace_focus"); }; this.visualizeBlur = function() { - ace.removeCssClass(this.container, "focus"); + ace.removeCssClass(this.container, "ace_focus"); }; this.showComposition = function(position) { diff --git a/src/ace/layer/Cursor.js b/src/ace/layer/Cursor.js index e907440c..06480a7f 100644 --- a/src/ace/layer/Cursor.js +++ b/src/ace/layer/Cursor.js @@ -2,11 +2,11 @@ ace.provide("ace.layer.Cursor"); ace.layer.Cursor = function(parentEl) { this.element = document.createElement("div"); - this.element.className = "layer cursor-layer"; + this.element.className = "ace_layer ace_cursor-layer"; parentEl.appendChild(this.element); this.cursor = document.createElement("div"); - this.cursor.className = "cursor"; + this.cursor.className = "ace_cursor"; this.isVisible = false; }; @@ -19,9 +19,9 @@ ace.layer.Cursor = function(parentEl) { column : position.column }; if (overwrite) { - ace.addCssClass(this.cursor, "overwrite"); + ace.addCssClass(this.cursor, "ace_overwrite"); } else { - ace.removeCssClass(this.cursor, "overwrite"); + ace.removeCssClass(this.cursor, "ace_overwrite"); } }; diff --git a/src/ace/layer/Gutter.js b/src/ace/layer/Gutter.js index 149e71f3..6b6def41 100644 --- a/src/ace/layer/Gutter.js +++ b/src/ace/layer/Gutter.js @@ -2,7 +2,7 @@ ace.provide("ace.layer.Gutter"); ace.layer.Gutter = function(parentEl) { this.element = document.createElement("div"); - this.element.className = "layer gutter-layer"; + this.element.className = "ace_layer ace_gutter-layer"; parentEl.appendChild(this.element); this.$breakpoints = []; @@ -22,8 +22,8 @@ ace.layer.Gutter = function(parentEl) { var html = []; for ( var i = config.firstRow; i <= config.lastRow; i++) { - html.push("
", (i+1), "
"); html.push(""); } diff --git a/src/ace/layer/Marker.js b/src/ace/layer/Marker.js index 51b9c9d9..9d4c76e3 100644 --- a/src/ace/layer/Marker.js +++ b/src/ace/layer/Marker.js @@ -2,7 +2,7 @@ ace.provide("ace.layer.Marker"); ace.layer.Marker = function(parentEl) { this.element = document.createElement("div"); - this.element.className = "layer marker-layer"; + this.element.className = "ace_layer ace_marker-layer"; parentEl.appendChild(this.element); this.markers = {}; diff --git a/src/ace/layer/Text.js b/src/ace/layer/Text.js index df7f15c4..0f483db4 100644 --- a/src/ace/layer/Text.js +++ b/src/ace/layer/Text.js @@ -2,7 +2,7 @@ ace.provide("ace.layer.Text"); ace.layer.Text = function(parentEl) { this.element = document.createElement("div"); - this.element.className = "layer text-layer"; + this.element.className = "ace_layer ace_text-layer"; parentEl.appendChild(this.element); this.$characterSize = this.$measureSizes(); @@ -92,7 +92,7 @@ ace.layer.Text = function(parentEl) { var tabSize = this.doc.getTabSize(); if (this.$showInvisibles) { var halfTab = (tabSize) / 2; - this.$tabString = "