From f73e38625b2c416ec4d36cb823869738b7d2012c Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 15 Jul 2012 23:32:20 +0400 Subject: [PATCH] indent guides --- demo/kitchen-sink/demo.js | 8 +- lib/ace/editor.js | 14 ++- lib/ace/layer/text.js | 194 +++++++++++++++++++++++------------ lib/ace/test/mockrenderer.js | 3 + lib/ace/virtual_renderer.js | 15 ++- 5 files changed, 163 insertions(+), 71 deletions(-) diff --git a/demo/kitchen-sink/demo.js b/demo/kitchen-sink/demo.js index 5a375412..7a2f2e76 100644 --- a/demo/kitchen-sink/demo.js +++ b/demo/kitchen-sink/demo.js @@ -286,7 +286,7 @@ env.editor.commands.addCommands([{ name: "gotoline", bindKey: {win: "Ctrl-L", mac: "Command-L"}, exec: function(editor, line) { - if (typeof needle == "object") { + if (typeof line == "object") { var arg = this.name + " " + editor.getCursorPosition().row; editor.cmdLine.setValue(arg, 1) editor.cmdLine.focus() @@ -328,7 +328,7 @@ cmdLine.commands.bindKeys({ }, }) -cmdLine.commands.removeCommands(["find", "goToLine", "findAll", "replace", "replaceAll"]) +cmdLine.commands.removeCommands(["find", "gotoline", "findall", "replace", "replaceall"]) /** * This demonstrates how you can define commands and bind shortcuts to them. @@ -557,6 +557,10 @@ bindCheckbox("show_hidden", function(checked) { env.editor.setShowInvisibles(checked); }); +bindCheckbox("display_indent_guides", function(checked) { + env.editor.setDisplayIndentGuides(checked); +}); + bindCheckbox("show_gutter", function(checked) { env.editor.renderer.setShowGutter(checked); }); diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 5b387f98..476a08c6 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -165,7 +165,7 @@ var Editor = function(renderer, session) { this.$onTokenizerUpdate = this.onTokenizerUpdate.bind(this); session.addEventListener("tokenizerUpdate", this.$onTokenizerUpdate); - this.$onChangeTabSize = this.renderer.updateText.bind(this.renderer); + this.$onChangeTabSize = this.renderer.onChangeTabSize.bind(this.renderer); session.addEventListener("changeTabSize", this.$onChangeTabSize); this.$onChangeWrapLimit = this.onChangeWrapLimit.bind(this); @@ -218,6 +218,7 @@ var Editor = function(renderer, session) { this.onChangeBreakpoint(); this.onChangeAnnotation(); this.session.getUseWrapMode() && this.renderer.adjustWrapLimit(); + this.renderer.onChangeTabSize(); this.renderer.updateFull(); this._emit("changeSession", { @@ -988,9 +989,6 @@ var Editor = function(renderer, session) { * If `showInvisibiles` is set to `true`, invisible characters—like spaces or new lines—are show in the editor. **/ this.setShowInvisibles = function(showInvisibles) { - if (this.getShowInvisibles() == showInvisibles) - return; - this.renderer.setShowInvisibles(showInvisibles); }; @@ -1003,6 +1001,14 @@ var Editor = function(renderer, session) { return this.renderer.getShowInvisibles(); }; + this.setDisplayIndentGuides = function(display) { + this.renderer.setDisplayIndentGuides(display); + }; + + this.getDisplayIndentGuides = function() { + return this.renderer.getDisplayIndentGuides(); + }; + /** * Editor.setShowPrintMargin(showPrintMargin) * - showPrintMargin (Boolean): Specifies whether or not to show the print margin diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index e96dff28..6c0f736a 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -204,28 +204,52 @@ var Text = function(parentEl) { return false; this.showInvisibles = showInvisibles; + this.$computeTabString(); + return true; + }; + + this.displayIndentGuides = true; + this.setDisplayIndentGuides = function(display) { + if (this.displayIndentGuides == display) + return false; + + this.displayIndentGuides = display; + this.$computeTabString(); return true; }; this.$tabStrings = []; + this.onChangeTabSize = this.$computeTabString = function() { var tabSize = this.session.getTabSize(); + this.tabSize = tabSize; var tabStr = this.$tabStrings = [0]; for (var i = 1; i < tabSize + 1; i++) { if (this.showInvisibles) { tabStr.push("" + this.TAB_CHAR - + new Array(i).join(" ") + + Array(i).join(" ") + ""); } else { tabStr.push(new Array(i+1).join(" ")); } } + if (this.displayIndentGuides) { + this.$indentGuideRe = /\s\S| \t|\t |\s$/; + var className = "ace_indent-guide"; + var content = Array(this.tabSize + 1).join(" "); + var tabContent = content; + if (this.showInvisibles) { + className += " ace_invisible"; + tabContent = this.TAB_CHAR + content.substr(6); + } + this.$tabStrings[" "] = "" + content + ""; + this.$tabStrings["\t"] = "" + tabContent + ""; + } }; this.updateLines = function(config, firstRow, lastRow) { - this.$computeTabString(); // Due to wrap line changes there can be new lines if e.g. // the line to updated wrapped in the meantime. if (this.config.lastRow != config.lastRow || @@ -253,22 +277,32 @@ var Text = function(parentEl) { lineElementsIdx ++; } - for (var i=first; i<=last; i++) { + var row = first; + var foldLine = this.session.getNextFoldLine(row); + var foldStart = foldLine ? foldLine.start.row : Infinity; + + while (true) { + if (row > foldStart) { + row = foldLine.end.row+1; + foldLine = this.session.getNextFoldLine(row, foldLine); + foldStart = foldLine ? foldLine.start.row :Infinity; + } + if (row > last) + break; + var lineElement = lineElements[lineElementsIdx++]; - if (!lineElement) - continue; - - var html = []; - var tokens = this.session.getTokens(i); - this.$renderLine(html, i, tokens, !this.$useLineGroups()); - lineElement = dom.setInnerHtml(lineElement, html.join("")); - - i = this.session.getRowFoldEnd(i); + if (lineElement) { + var html = []; + this.$renderLine( + html, row, !this.$useLineGroups(), row == foldStart ? foldLine : false + ); + dom.setInnerHtml(lineElement, html.join("")); + } + row++; } }; this.scrollLines = function(config) { - this.$computeTabString(); var oldConfig = this.config; this.config = config; @@ -321,8 +355,7 @@ var Text = function(parentEl) { var html = []; // Get the tokens per line as there might be some lines in between // beeing folded. - var tokens = this.session.getTokens(row); - this.$renderLine(html, row, tokens, false); + this.$renderLine(html, row, false, row == foldStart ? foldLine : false); // don't use setInnerHtml since we are working with an empty DIV container.innerHTML = html.join(""); @@ -341,7 +374,6 @@ var Text = function(parentEl) { }; this.update = function(config) { - this.$computeTabString(); this.config = config; var html = []; @@ -363,10 +395,7 @@ var Text = function(parentEl) { if (this.$useLineGroups()) html.push("
") - // Get the tokens per line as there might be some lines in between - // beeing folded. - var tokens = this.session.getTokens(row); - this.$renderLine(html, row, tokens, false); + this.$renderLine(html, row, false, row == foldStart ? foldLine : false); if (this.$useLineGroups()) html.push("
"); // end the line group @@ -429,38 +458,44 @@ var Text = function(parentEl) { return screenColumn + value.length; }; - this.$renderLineCore = function(stringBuilder, lastRow, tokens, splits, onlyContents) { + this.renderIndentGuide = function(stringBuilder, value) { + var cols = value.search(this.$indentGuideRe); + if (cols <= 0) + return value; + if (value[0] == " ") { + cols -= cols % this.tabSize; + stringBuilder.push(Array(cols/this.tabSize + 1).join(this.$tabStrings[" "])); + return value.substr(cols); + } else if (value[0] == "\t") { + stringBuilder.push(Array(cols + 1).join(this.$tabStrings["\t"])); + return value.substr(cols); + } + return value; + }; + + this.$renderWrappedLine = function(stringBuilder, tokens, splits, onlyContents) { var chars = 0; var split = 0; - var splitChars; + var splitChars = splits[0]; var screenColumn = 0; - var self = this; - - if (!splits || splits.length == 0) - splitChars = Number.MAX_VALUE; - else - splitChars = splits[0]; - - if (!onlyContents) { - stringBuilder.push("
" - ); - } for (var i = 0; i < tokens.length; i++) { var token = tokens[i]; var value = token.value; + if (i == 0 && this.displayIndentGuides) { + chars = value.length; + value = this.renderIndentGuide(stringBuilder, value); + if (!value) + continue; + chars -= value.length; + } if (chars + value.length < splitChars) { - screenColumn = self.$renderToken( - stringBuilder, screenColumn, token, value - ); + screenColumn = this.$renderToken(stringBuilder, screenColumn, token, value); chars += value.length; - } - else { + } else { while (chars + value.length >= splitChars) { - screenColumn = self.$renderToken( + screenColumn = this.$renderToken( stringBuilder, screenColumn, token, value.substring(0, splitChars - chars) ); @@ -470,8 +505,7 @@ var Text = function(parentEl) { if (!onlyContents) { stringBuilder.push("
", "
" + this.config.lineHeight, "px'>" ); } @@ -481,37 +515,70 @@ var Text = function(parentEl) { } if (value.length != 0) { chars += value.length; - screenColumn = self.$renderToken( + screenColumn = this.$renderToken( stringBuilder, screenColumn, token, value ); } } } + }; + + this.$renderSimpleLine = function(stringBuilder, tokens) { + var screenColumn = 0; + var token = tokens[0]; + var value = token.value; + if (this.displayIndentGuides) + value = this.renderIndentGuide(stringBuilder, value); + if (value) + screenColumn = this.$renderToken(stringBuilder, screenColumn, token, value); + for (var i = 1; i < tokens.length; i++) { + token = tokens[i]; + value = token.value; + screenColumn = this.$renderToken(stringBuilder, screenColumn, token, value); + } + }; + + // row is either first row of foldline or not in fold + this.$renderLine = function(stringBuilder, row, onlyContents, foldLine) { + if (!foldLine && foldLine != false) + foldLine = this.session.getFoldLine(row); + + if (foldLine) + var tokens = this.$getFoldLineTokens(row, foldLine); + else + var tokens = this.session.getTokens(row); + + + if (!onlyContents) { + stringBuilder.push( + "
" + ); + } + + if (tokens.length) { + var splits = this.session.getRowSplitData(row); + if (splits && splits.length) + this.$renderWrappedLine(stringBuilder, tokens, splits, onlyContents); + else + this.$renderSimpleLine(stringBuilder, tokens); + } if (this.showInvisibles) { - if (lastRow !== this.session.getLength() - 1) - stringBuilder.push("" + this.EOL_CHAR + ""); - else - stringBuilder.push("" + this.EOF_CHAR + ""); + if (foldLine) + row = foldLine.end.row + + stringBuilder.push( + "", + row == this.session.getLength() - 1 ? this.EOF_CHAR : this.EOL_CHAR, + "" + ); } if (!onlyContents) stringBuilder.push("
"); }; - this.$renderLine = function(stringBuilder, row, tokens, onlyContents) { - // Check if the line to render is folded or not. If not, things are - // simple, otherwise, we need to fake some things... - if (!this.session.isRowFolded(row)) { - var splits = this.session.getRowSplitData(row); - this.$renderLineCore(stringBuilder, row, tokens, splits, onlyContents); - } else { - this.$renderFoldLine(stringBuilder, row, tokens, onlyContents); - } - }; - - this.$renderFoldLine = function(stringBuilder, row, tokens, onlyContents) { + this.$getFoldLineTokens = function(row, foldLine) { var session = this.session; - var foldLine = session.getFoldLine(row); var renderTokens = []; function addTokens(tokens, from, to) { @@ -552,6 +619,7 @@ var Text = function(parentEl) { } } + var tokens = session.getTokens(row); foldLine.walk(function(placeholder, row, column, lastColumn, isNewRow) { if (placeholder) { renderTokens.push({ @@ -567,9 +635,7 @@ var Text = function(parentEl) { } }, foldLine.end.row, this.session.getLine(foldLine.end.row).length); - // splits for foldline are stored at its' first row - var splits = this.session.$useWrapMode ? this.session.$wrapData[row] : null; - this.$renderLineCore(stringBuilder, row, renderTokens, splits, onlyContents); + return renderTokens; }; this.$useLineGroups = function() { diff --git a/lib/ace/test/mockrenderer.js b/lib/ace/test/mockrenderer.js index 4e5c515d..2ba4231d 100644 --- a/lib/ace/test/mockrenderer.js +++ b/lib/ace/test/mockrenderer.js @@ -154,6 +154,9 @@ MockRenderer.prototype.getScrollTopRow = function() { MockRenderer.prototype.draw = function() { }; +MockRenderer.prototype.onChangeTabSize = function(startRow, endRow) { +}; + MockRenderer.prototype.updateLines = function(startRow, endRow) { }; diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index d9a0c241..bba5ddf7 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -104,7 +104,6 @@ var VirtualRenderer = function(container, theme) { this.$gutterLayer = new GutterLayer(this.$gutter); this.$gutterLayer.on("changeGutterWidth", this.onResize.bind(this, true)); - this.setFadeFoldWidgets(true); this.$markerBack = new MarkerLayer(this.content); @@ -249,6 +248,11 @@ var VirtualRenderer = function(container, theme) { this.$loop.schedule(this.CHANGE_LINES); }; + this.onChangeTabSize = function() { + this.$loop.schedule(this.CHANGE_TEXT | this.CHANGE_MARKER); + this.$textLayer.onChangeTabSize(); + }; + /** * VirtualRenderer.updateText() -> Void * @@ -388,6 +392,15 @@ var VirtualRenderer = function(container, theme) { return this.$textLayer.showInvisibles; }; + this.getDisplayIndentGuides = function() { + return this.$textLayer.displayIndentGuides; + }; + + this.setDisplayIndentGuides = function(display) { + if (this.$textLayer.setDisplayIndentGuides(display)) + this.$loop.schedule(this.CHANGE_TEXT); + }; + this.$showPrintMargin = true; /**