indent guides

This commit is contained in:
nightwing 2012-07-15 23:32:20 +04:00
commit f73e38625b
5 changed files with 163 additions and 71 deletions

View file

@ -286,7 +286,7 @@ env.editor.commands.addCommands([{
name: "gotoline", name: "gotoline",
bindKey: {win: "Ctrl-L", mac: "Command-L"}, bindKey: {win: "Ctrl-L", mac: "Command-L"},
exec: function(editor, line) { exec: function(editor, line) {
if (typeof needle == "object") { if (typeof line == "object") {
var arg = this.name + " " + editor.getCursorPosition().row; var arg = this.name + " " + editor.getCursorPosition().row;
editor.cmdLine.setValue(arg, 1) editor.cmdLine.setValue(arg, 1)
editor.cmdLine.focus() 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. * 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); env.editor.setShowInvisibles(checked);
}); });
bindCheckbox("display_indent_guides", function(checked) {
env.editor.setDisplayIndentGuides(checked);
});
bindCheckbox("show_gutter", function(checked) { bindCheckbox("show_gutter", function(checked) {
env.editor.renderer.setShowGutter(checked); env.editor.renderer.setShowGutter(checked);
}); });

View file

@ -165,7 +165,7 @@ var Editor = function(renderer, session) {
this.$onTokenizerUpdate = this.onTokenizerUpdate.bind(this); this.$onTokenizerUpdate = this.onTokenizerUpdate.bind(this);
session.addEventListener("tokenizerUpdate", this.$onTokenizerUpdate); 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); session.addEventListener("changeTabSize", this.$onChangeTabSize);
this.$onChangeWrapLimit = this.onChangeWrapLimit.bind(this); this.$onChangeWrapLimit = this.onChangeWrapLimit.bind(this);
@ -218,6 +218,7 @@ var Editor = function(renderer, session) {
this.onChangeBreakpoint(); this.onChangeBreakpoint();
this.onChangeAnnotation(); this.onChangeAnnotation();
this.session.getUseWrapMode() && this.renderer.adjustWrapLimit(); this.session.getUseWrapMode() && this.renderer.adjustWrapLimit();
this.renderer.onChangeTabSize();
this.renderer.updateFull(); this.renderer.updateFull();
this._emit("changeSession", { 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. * If `showInvisibiles` is set to `true`, invisible characters—like spaces or new lines—are show in the editor.
**/ **/
this.setShowInvisibles = function(showInvisibles) { this.setShowInvisibles = function(showInvisibles) {
if (this.getShowInvisibles() == showInvisibles)
return;
this.renderer.setShowInvisibles(showInvisibles); this.renderer.setShowInvisibles(showInvisibles);
}; };
@ -1003,6 +1001,14 @@ var Editor = function(renderer, session) {
return this.renderer.getShowInvisibles(); return this.renderer.getShowInvisibles();
}; };
this.setDisplayIndentGuides = function(display) {
this.renderer.setDisplayIndentGuides(display);
};
this.getDisplayIndentGuides = function() {
return this.renderer.getDisplayIndentGuides();
};
/** /**
* Editor.setShowPrintMargin(showPrintMargin) * Editor.setShowPrintMargin(showPrintMargin)
* - showPrintMargin (Boolean): Specifies whether or not to show the print margin * - showPrintMargin (Boolean): Specifies whether or not to show the print margin

View file

@ -204,28 +204,52 @@ var Text = function(parentEl) {
return false; return false;
this.showInvisibles = showInvisibles; 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; return true;
}; };
this.$tabStrings = []; this.$tabStrings = [];
this.onChangeTabSize =
this.$computeTabString = function() { this.$computeTabString = function() {
var tabSize = this.session.getTabSize(); var tabSize = this.session.getTabSize();
this.tabSize = tabSize;
var tabStr = this.$tabStrings = [0]; var tabStr = this.$tabStrings = [0];
for (var i = 1; i < tabSize + 1; i++) { for (var i = 1; i < tabSize + 1; i++) {
if (this.showInvisibles) { if (this.showInvisibles) {
tabStr.push("<span class='ace_invisible'>" tabStr.push("<span class='ace_invisible'>"
+ this.TAB_CHAR + this.TAB_CHAR
+ new Array(i).join("&#160;") + Array(i).join("&#160;")
+ "</span>"); + "</span>");
} else { } else {
tabStr.push(new Array(i+1).join("&#160;")); tabStr.push(new Array(i+1).join("&#160;"));
} }
} }
if (this.displayIndentGuides) {
this.$indentGuideRe = /\s\S| \t|\t |\s$/;
var className = "ace_indent-guide";
var content = Array(this.tabSize + 1).join("&#160;");
var tabContent = content;
if (this.showInvisibles) {
className += " ace_invisible";
tabContent = this.TAB_CHAR + content.substr(6);
}
this.$tabStrings[" "] = "<span class='" + className + "' >" + content + "</span>";
this.$tabStrings["\t"] = "<span class='" + className + "' >" + tabContent + "</span>";
}
}; };
this.updateLines = function(config, firstRow, lastRow) { this.updateLines = function(config, firstRow, lastRow) {
this.$computeTabString();
// Due to wrap line changes there can be new lines if e.g. // Due to wrap line changes there can be new lines if e.g.
// the line to updated wrapped in the meantime. // the line to updated wrapped in the meantime.
if (this.config.lastRow != config.lastRow || if (this.config.lastRow != config.lastRow ||
@ -253,22 +277,32 @@ var Text = function(parentEl) {
lineElementsIdx ++; 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++]; var lineElement = lineElements[lineElementsIdx++];
if (!lineElement) if (lineElement) {
continue; var html = [];
this.$renderLine(
var html = []; html, row, !this.$useLineGroups(), row == foldStart ? foldLine : false
var tokens = this.session.getTokens(i); );
this.$renderLine(html, i, tokens, !this.$useLineGroups()); dom.setInnerHtml(lineElement, html.join(""));
lineElement = dom.setInnerHtml(lineElement, html.join("")); }
row++;
i = this.session.getRowFoldEnd(i);
} }
}; };
this.scrollLines = function(config) { this.scrollLines = function(config) {
this.$computeTabString();
var oldConfig = this.config; var oldConfig = this.config;
this.config = config; this.config = config;
@ -321,8 +355,7 @@ var Text = function(parentEl) {
var html = []; var html = [];
// Get the tokens per line as there might be some lines in between // Get the tokens per line as there might be some lines in between
// beeing folded. // beeing folded.
var tokens = this.session.getTokens(row); this.$renderLine(html, row, false, row == foldStart ? foldLine : false);
this.$renderLine(html, row, tokens, false);
// don't use setInnerHtml since we are working with an empty DIV // don't use setInnerHtml since we are working with an empty DIV
container.innerHTML = html.join(""); container.innerHTML = html.join("");
@ -341,7 +374,6 @@ var Text = function(parentEl) {
}; };
this.update = function(config) { this.update = function(config) {
this.$computeTabString();
this.config = config; this.config = config;
var html = []; var html = [];
@ -363,10 +395,7 @@ var Text = function(parentEl) {
if (this.$useLineGroups()) if (this.$useLineGroups())
html.push("<div class='ace_line_group'>") html.push("<div class='ace_line_group'>")
// Get the tokens per line as there might be some lines in between this.$renderLine(html, row, false, row == foldStart ? foldLine : false);
// beeing folded.
var tokens = this.session.getTokens(row);
this.$renderLine(html, row, tokens, false);
if (this.$useLineGroups()) if (this.$useLineGroups())
html.push("</div>"); // end the line group html.push("</div>"); // end the line group
@ -429,38 +458,44 @@ var Text = function(parentEl) {
return screenColumn + value.length; 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 chars = 0;
var split = 0; var split = 0;
var splitChars; var splitChars = splits[0];
var screenColumn = 0; var screenColumn = 0;
var self = this;
if (!splits || splits.length == 0)
splitChars = Number.MAX_VALUE;
else
splitChars = splits[0];
if (!onlyContents) {
stringBuilder.push("<div class='ace_line' style='height:",
this.config.lineHeight, "px",
"'>"
);
}
for (var i = 0; i < tokens.length; i++) { for (var i = 0; i < tokens.length; i++) {
var token = tokens[i]; var token = tokens[i];
var value = token.value; 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) { if (chars + value.length < splitChars) {
screenColumn = self.$renderToken( screenColumn = this.$renderToken(stringBuilder, screenColumn, token, value);
stringBuilder, screenColumn, token, value
);
chars += value.length; chars += value.length;
} } else {
else {
while (chars + value.length >= splitChars) { while (chars + value.length >= splitChars) {
screenColumn = self.$renderToken( screenColumn = this.$renderToken(
stringBuilder, screenColumn, stringBuilder, screenColumn,
token, value.substring(0, splitChars - chars) token, value.substring(0, splitChars - chars)
); );
@ -470,8 +505,7 @@ var Text = function(parentEl) {
if (!onlyContents) { if (!onlyContents) {
stringBuilder.push("</div>", stringBuilder.push("</div>",
"<div class='ace_line' style='height:", "<div class='ace_line' style='height:",
this.config.lineHeight, "px", this.config.lineHeight, "px'>"
"'>"
); );
} }
@ -481,37 +515,70 @@ var Text = function(parentEl) {
} }
if (value.length != 0) { if (value.length != 0) {
chars += value.length; chars += value.length;
screenColumn = self.$renderToken( screenColumn = this.$renderToken(
stringBuilder, screenColumn, token, value 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(
"<div class='ace_line' style='height:", this.config.lineHeight, "px'>"
);
}
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 (this.showInvisibles) {
if (lastRow !== this.session.getLength() - 1) if (foldLine)
stringBuilder.push("<span class='ace_invisible'>" + this.EOL_CHAR + "</span>"); row = foldLine.end.row
else
stringBuilder.push("<span class='ace_invisible'>" + this.EOF_CHAR + "</span>"); stringBuilder.push(
"<span class='ace_invisible'>",
row == this.session.getLength() - 1 ? this.EOF_CHAR : this.EOL_CHAR,
"</span>"
);
} }
if (!onlyContents) if (!onlyContents)
stringBuilder.push("</div>"); stringBuilder.push("</div>");
}; };
this.$renderLine = function(stringBuilder, row, tokens, onlyContents) { this.$getFoldLineTokens = function(row, foldLine) {
// 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) {
var session = this.session; var session = this.session;
var foldLine = session.getFoldLine(row);
var renderTokens = []; var renderTokens = [];
function addTokens(tokens, from, to) { 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) { foldLine.walk(function(placeholder, row, column, lastColumn, isNewRow) {
if (placeholder) { if (placeholder) {
renderTokens.push({ renderTokens.push({
@ -567,9 +635,7 @@ var Text = function(parentEl) {
} }
}, foldLine.end.row, this.session.getLine(foldLine.end.row).length); }, foldLine.end.row, this.session.getLine(foldLine.end.row).length);
// splits for foldline are stored at its' first row return renderTokens;
var splits = this.session.$useWrapMode ? this.session.$wrapData[row] : null;
this.$renderLineCore(stringBuilder, row, renderTokens, splits, onlyContents);
}; };
this.$useLineGroups = function() { this.$useLineGroups = function() {

View file

@ -154,6 +154,9 @@ MockRenderer.prototype.getScrollTopRow = function() {
MockRenderer.prototype.draw = function() { MockRenderer.prototype.draw = function() {
}; };
MockRenderer.prototype.onChangeTabSize = function(startRow, endRow) {
};
MockRenderer.prototype.updateLines = function(startRow, endRow) { MockRenderer.prototype.updateLines = function(startRow, endRow) {
}; };

View file

@ -104,7 +104,6 @@ var VirtualRenderer = function(container, theme) {
this.$gutterLayer = new GutterLayer(this.$gutter); this.$gutterLayer = new GutterLayer(this.$gutter);
this.$gutterLayer.on("changeGutterWidth", this.onResize.bind(this, true)); this.$gutterLayer.on("changeGutterWidth", this.onResize.bind(this, true));
this.setFadeFoldWidgets(true);
this.$markerBack = new MarkerLayer(this.content); this.$markerBack = new MarkerLayer(this.content);
@ -249,6 +248,11 @@ var VirtualRenderer = function(container, theme) {
this.$loop.schedule(this.CHANGE_LINES); this.$loop.schedule(this.CHANGE_LINES);
}; };
this.onChangeTabSize = function() {
this.$loop.schedule(this.CHANGE_TEXT | this.CHANGE_MARKER);
this.$textLayer.onChangeTabSize();
};
/** /**
* VirtualRenderer.updateText() -> Void * VirtualRenderer.updateText() -> Void
* *
@ -388,6 +392,15 @@ var VirtualRenderer = function(container, theme) {
return this.$textLayer.showInvisibles; 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; this.$showPrintMargin = true;
/** /**