proper display of tabs and add option to show invisible characters

This commit is contained in:
Fabian Jakobs 2010-04-22 18:38:04 +02:00
commit 61f0cb63d9
5 changed files with 164 additions and 14 deletions

View file

@ -31,6 +31,10 @@
background: black;
}
.line .invisible {
color: rgb(191, 191, 191);
}
.line .keyword {
color: blue;
}

View file

@ -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();

View file

@ -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<line.length; i++) {
var len = line[i].length;
if (remaining > 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<line.length; i++) {
var len = line[i].length;
if (remaining >= 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)
};
};

View file

@ -6,11 +6,14 @@ ace.layer.Text = function(parentEl) {
parentEl.appendChild(this.element);
this.$measureSizes();
this.$tabString = " ";
};
(function() {
// this.ENTER_CHAR = "&para;";
this.ENTER_CHAR = "&not;";
this.TAB_CHAR = "&#x2023;";
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("&nbsp;");
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 = "<span class='invisible'>" + this.TAB_CHAR + new Array(tabSize).join("&nbsp;") + "</span>";
} else {
this.$tabString = new Array(tabSize+1).join("&nbsp;");
}
};
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("<div class='line' style='height:" + this.lineHeight + "px;", "width:",
@ -82,20 +103,20 @@ ace.layer.Text = function(parentEl) {
var output = token.value
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
//.replace(/\t/g, "&#x2023; ")
.replace(/\t/g, this.$tabString)
.replace(/\s/g, "&nbsp;");
.replace(/ /g, "&nbsp;")
.replace(/\t/g, this.$tabString);
// TODO: proper space matching!
if (token.type !== "text") {
stringBuilder.push("<span class='", token.type, "'>", output,
"</span>");
stringBuilder.push("<span class='", token.type, "'>", output, "</span>");
}
else {
stringBuilder.push(output);
}
};
// TODO: show invisibles
//stringBuilder.push("&para;");
if (this.$showInvisibles)
stringBuilder.push("<span class='invisible'>" + this.ENTER_CHAR + "</span>");
};
}).call(ace.layer.Text.prototype);

View file

@ -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)
});