From 61f0cb63d951af07f1e4810a7136fad7d0e438e8 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 22 Apr 2010 18:38:04 +0200 Subject: [PATCH] proper display of tabs and add option to show invisible characters --- css/tm.css | 4 +++ src/Editor.js | 14 ++++++++ src/VirtualRenderer.js | 72 ++++++++++++++++++++++++++++++++++--- src/layer/Text.js | 41 +++++++++++++++------ test/VirtualRendererTest.js | 47 ++++++++++++++++++++++++ 5 files changed, 164 insertions(+), 14 deletions(-) create mode 100644 test/VirtualRendererTest.js diff --git a/css/tm.css b/css/tm.css index b9d8dfd2..0bf48fee 100644 --- a/css/tm.css +++ b/css/tm.css @@ -31,6 +31,10 @@ background: black; } +.line .invisible { + color: rgb(191, 191, 191); +} + .line .keyword { color: blue; } diff --git a/src/Editor.js b/src/Editor.js index fb0dcb11..16524e4e 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -338,6 +338,20 @@ ace.Editor = function(renderer, doc) { return this.$highlightActiveLine; }; + this.$showInvisibles = true; + this.setShowInvisibles = function(showInvisibles) { + showInvisibles = !!showInvisibles; + if (this.$showInvisibles == showInvisibles) return; + + this.$showInvisibles = showInvisibles; + this.renderer.setShowInvisibles(showInvisibles); + this.renderer.draw(); + }; + + this.getShowInvisibles = function() { + return this.showInvisibles; + }; + this.removeRight = function() { if (this.selection.isEmpty()) { this.selection.selectRight(); diff --git a/src/VirtualRenderer.js b/src/VirtualRenderer.js index 979afb39..3b87e938 100644 --- a/src/VirtualRenderer.js +++ b/src/VirtualRenderer.js @@ -44,13 +44,19 @@ ace.VirtualRenderer = function(container) { this.lines = doc.lines; this.doc = doc; this.markerLayer.setDocument(doc); - this.textLayer.setTabSize(doc.getTabSize()); + this.textLayer.setDocument(doc); }; this.setTokenizer = function(tokenizer) { this.textLayer.setTokenizer(tokenizer); }; + this.$showInvisibles = true; + this.setShowInvisibles = function(showInvisibles) { + this.$showInvisibles = showInvisibles; + this.textLayer.setShowInvisibles(showInvisibles); + }; + this.getContainerElement = function() { return this.container; }; @@ -117,7 +123,10 @@ ace.VirtualRenderer = function(container) { var offset = this.scrollTop % this.lineHeight; var minHeight = this.scroller.clientHeight + offset; - var longestLine = Math.max(this.scroller.clientWidth, Math.round(this.doc.getWidth() * this.characterWidth)); + 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); var firstRow = Math.round((this.scrollTop - offset) / this.lineHeight); @@ -150,6 +159,8 @@ 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); }; @@ -158,10 +169,63 @@ ace.VirtualRenderer = function(container) { }; this.updateCursor = function(position) { - this.cursorLayer.setCursor(position); + this.cursorLayer.setCursor(this.$documentToScreenPosition(position)); this.cursorLayer.update(this.layerConfig); }; + this.$documentToScreenPosition = function(pos) { + return { + row: pos.row, + column: this.$documentToScreenColumn(pos.row, pos.column) + }; + }; + + this.$documentToScreenColumn = function(row, docColumn) { + var tabSize = this.doc.getTabSize(); + + var screenColumn = 0; + var remaining = docColumn; + + var line = this.doc.getLine(row).split(/\t/g); + for (var i=0; i len) { + remaining -= (len + 1); + screenColumn += len + tabSize; + } + else { + screenColumn += remaining; + break; + } + } + return screenColumn; + }; + + this.$screenToDocumentColumn = function(row, screenColumn) { + var tabSize = this.doc.getTabSize(); + + var docColumn = 0; + var remaining = screenColumn; + + var line = this.doc.getLine(row).split(/\t/g); + for (var i=0; i= len + tabSize) { + remaining -= (len + tabSize); + docColumn += (len + 1); + } + else if (remaining > len){ + docColumn += len; + break; + } + else { + docColumn += remaining; + break; + } + } + return docColumn; + }; + this.hideCursor = function() { this.cursorLayer.hideCursor(); }; @@ -230,7 +294,7 @@ ace.VirtualRenderer = function(container) { return { row : row, - column : col + column : this.$screenToDocumentColumn(row, col) }; }; diff --git a/src/layer/Text.js b/src/layer/Text.js index 2009e6f0..2e7f120d 100644 --- a/src/layer/Text.js +++ b/src/layer/Text.js @@ -6,11 +6,14 @@ ace.layer.Text = function(parentEl) { parentEl.appendChild(this.element); this.$measureSizes(); - this.$tabString = " "; }; (function() { +// this.ENTER_CHAR = "¶"; + this.ENTER_CHAR = "¬"; + this.TAB_CHAR = "‣"; + this.setTokenizer = function(tokenizer) { this.tokenizer = tokenizer; }; @@ -44,11 +47,27 @@ ace.layer.Text = function(parentEl) { this.element.removeChild(measureNode); }; - this.setTabSize = function(tabSize) { - this.$tabString = new Array(tabSize+1).join(" "); + this.setDocument = function(doc) { + this.doc = doc; + }; + + this.$showInvisibles = true; + this.setShowInvisibles = function(showInvisibles) { + this.$showInvisibles = showInvisibles; + }; + + this.$computeTabString = function() { + var tabSize = this.doc.getTabSize(); + if (this.$showInvisibles) { + this.$tabString = ""; + } else { + this.$tabString = new Array(tabSize+1).join(" "); + } }; this.updateLines = function(layerConfig, firstRow, lastRow) { + this.$computeTabString(); + var first = Math.max(firstRow, layerConfig.firstRow); var last = Math.min(lastRow, layerConfig.lastRow); @@ -64,6 +83,8 @@ ace.layer.Text = function(parentEl) { }; this.update = function(config) { + this.$computeTabString(); + var html = []; for ( var i = config.firstRow; i <= config.lastRow; i++) { html.push("
", output, - ""); + stringBuilder.push("", output, ""); } else { stringBuilder.push(output); } }; - // TODO: show invisibles - //stringBuilder.push("¶"); + + if (this.$showInvisibles) + stringBuilder.push(""); }; }).call(ace.layer.Text.prototype); \ No newline at end of file diff --git a/test/VirtualRendererTest.js b/test/VirtualRendererTest.js new file mode 100644 index 00000000..13cf3cfa --- /dev/null +++ b/test/VirtualRendererTest.js @@ -0,0 +1,47 @@ +var VirtualRendererTest = new TestCase("VirtualRendererTest", { + + "test: convert document to screen coordinates" : function() { + var el = document.createElement("div"); + var renderer = new ace.VirtualRenderer(el); + + var doc = new ace.Document("01234\t567890\t1234"); + doc.setTabSize(4); + renderer.setDocument(doc); + + assertEquals(0, renderer.$documentToScreenColumn(0, 0)); + assertEquals(4, renderer.$documentToScreenColumn(0, 4)); + assertEquals(5, renderer.$documentToScreenColumn(0, 5)); + assertEquals(9, renderer.$documentToScreenColumn(0, 6)); + assertEquals(15, renderer.$documentToScreenColumn(0, 12)); + assertEquals(19, renderer.$documentToScreenColumn(0, 13)); + + doc.setTabSize(2); + + assertEquals(0, renderer.$documentToScreenColumn(0, 0)); + assertEquals(4, renderer.$documentToScreenColumn(0, 4)); + assertEquals(5, renderer.$documentToScreenColumn(0, 5)); + assertEquals(7, renderer.$documentToScreenColumn(0, 6)); + assertEquals(13, renderer.$documentToScreenColumn(0, 12)); + assertEquals(15, renderer.$documentToScreenColumn(0, 13)); + }, + + "test: convert screen to document coordinates" : function() { + var el = document.createElement("div"); + var renderer = new ace.VirtualRenderer(el); + + var doc = new ace.Document("01234\t567890\t1234"); + doc.setTabSize(4); + renderer.setDocument(doc); + + assertEquals(0, renderer.$screenToDocumentColumn(0, 0)); + assertEquals(4, renderer.$screenToDocumentColumn(0, 4)); + assertEquals(5, renderer.$screenToDocumentColumn(0, 5)); + assertEquals(5, renderer.$screenToDocumentColumn(0, 6)); + assertEquals(5, renderer.$screenToDocumentColumn(0, 7)); + assertEquals(5, renderer.$screenToDocumentColumn(0, 8)); + assertEquals(6, renderer.$screenToDocumentColumn(0, 9)); + assertEquals(12, renderer.$screenToDocumentColumn(0, 15)); + assertEquals(13, renderer.$screenToDocumentColumn(0, 19)); + } + // change tab size after setDocument (for text layer) +}); \ No newline at end of file