From 4677db58d86fbf17bbbf86f73b8f7be133b218d4 Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 28 Nov 2011 18:09:39 +0400 Subject: [PATCH] add fold detection rules for python and c style languages --- lib/ace/edit_session/folding.js | 229 ++++++++++++++++++++++++++++---- lib/ace/mode/c_cpp.js | 2 + lib/ace/mode/csharp.js | 54 ++++---- lib/ace/mode/css.js | 2 + lib/ace/mode/java.js | 2 + lib/ace/mode/javascript.js | 2 + lib/ace/mode/json.js | 2 + lib/ace/mode/python.js | 5 + lib/ace/virtual_renderer.js | 3 + 9 files changed, 250 insertions(+), 51 deletions(-) diff --git a/lib/ace/edit_session/folding.js b/lib/ace/edit_session/folding.js index 79a28786..73cef50a 100644 --- a/lib/ace/edit_session/folding.js +++ b/lib/ace/edit_session/folding.js @@ -534,27 +534,7 @@ function Folding() { range.start.column++; } else { - var iterator = new TokenIterator(this, cursor.row, cursor.column); - var token = iterator.getCurrentToken(); - if (token && /^comment|string/.test(token.type)) { - do { - t = iterator.stepBackward(); - } while(t && t.type == token.type) - - iterator.stepForward(); - range.start.row = iterator.getCurrentTokenRow(); - range.start.column = iterator.getCurrentTokenColumn() + 2; - - var iterator = new TokenIterator(this, cursor.row, cursor.column); - - do { - t = iterator.stepForward(); - } while(t && t.type == token.type) - t = iterator.stepBackward(); - - range.end.row = iterator.getCurrentTokenRow(); - range.end.column = iterator.getCurrentTokenColumn() + t.value.length - 1; - } + range = this.getCommentFoldRange(cursor.row, cursor.column) || range; } } else { var folds = this.getFoldsInRange(range); @@ -574,7 +554,6 @@ function Folding() { return } - var placeholder = "..."; if (!range.isMultiLine()) { placeholder = this.getTextRange(range); @@ -586,12 +565,212 @@ function Folding() { this.addFold(placeholder, range); }; - // structured folding - this.$setFolding = function(mode) { + this.getCommentFoldRange = function(row, column) { + var iterator = new TokenIterator(this, row, column); + var token = iterator.getCurrentToken(); + if (token && /^comment|string/.test(token.type)) { + var range = new Range; + do { + t = iterator.stepBackward(); + } while(t && t.type == token.type) + + iterator.stepForward(); + range.start.row = iterator.getCurrentTokenRow(); + range.start.column = iterator.getCurrentTokenColumn() + 2; + + var iterator = new TokenIterator(this, row, column); + + do { + t = iterator.stepForward(); + } while(t && t.type == token.type) + t = iterator.stepBackward(); + + range.end.row = iterator.getCurrentTokenRow(); + range.end.column = iterator.getCurrentTokenColumn() + t.value.length - 1; + return range + } }; - this.onFoldWidgetClick = function(e) { + this.foldAll = function() { + var foldWidgets = this.foldWidgets + for (var row = foldWidgets.length; row--; ) { + if (foldWidgets[row] == null) + foldWidgets[row] = this.getFoldWidget(row) + if (foldWidgets[row] != "start") + continue + + var range = this.getFoldWidgetRange(row); + console.log(row, range) + if (range) + this.addFold("...", range) + } + } + + // structured folding + this.$setFolding = function(mode) { + mode = mode && mode.foldingRules; + var foldRules = Folding.commonFoldingRules + if (typeof mode == "string") + mode = foldRules[mode]; + + if (mode) { + this.foldWidgets = []; + this.removeListener('change', this.$updateFoldWidgets); + + if (mode.getFoldWidget) + this.getFoldWidget = mode.getFoldWidget; + else if (mode.foldingStopMarker) + this.getFoldWidget = foldRules.$testBoth; + else + this.getFoldWidget = foldRules.$testStart; + + this.foldingStopMarker = mode.foldingStopMarker; + this.foldingStartMarker = mode.foldingStartMarker; + + if (typeof mode.getFoldWidgetRange == "string") + this.getFoldWidgetRange = foldRules[mode.getFoldWidgetRange]; + else + this.getFoldWidgetRange = mode.getFoldWidgetRange; + + + this.$updateFoldWidgets = (mode.onChange || foldRules.onChange).bind(this); + + this.on('change', this.$updateFoldWidgets); + } else { + this.foldWidgets = null; + this.removeListener('change', this.$updateFoldWidgets); + } }; + + this.onFoldWidgetClick = function(row, htmlEvent) { + var type = this.getFoldWidget(row); + var line = this.getLine(row); + + if (type == "end") + var fold = this.getFoldAt(row, 0, -1); + else + var fold = this.getFoldAt(row, line.length, 1); + + if (fold) { + this.expandFold(fold); + return; + } + + range = this.getFoldWidgetRange(row); + if (range) + this.addFold("...", range); + } +} + +Folding.commonFoldingRules = { + $testStart: function(row) { + if(this.foldingStartMarker.test(this.getLine(row))) + return "start"; + return ""; + }, + $testBoth: function(row) { + var line = this.getLine(row); + if(this.foldingStartMarker.test(line)) + return "start"; + if(this.foldingStopMarker.test(line)) + return "end"; + return ""; + }, + onChange: function(e) { + var delta = e.data; + var range = delta.range; + var firstRow = range.start.row; + var len = range.end.row - firstRow; + + if (len == 0) { + this.foldWidgets[firstRow] = null; + } else if (delta.action == "removeText" || delta.action == "removeLines") { + this.foldWidgets.splice(firstRow, len + 1, null); + } else { + var args = Array(len + 1); + args.unshift(firstRow, 1) + this.foldWidgets.splice.apply(this.foldWidgets, args); + } + }, + + indentationBlock: function(row) { + var re = /^\s*/; + var startRow = row, endRow = row; + var line = this.getLine(row); + var startColumn = line.length - 1; + var startLevel = line.match(re)[0].length; + + while (line = this.getLine(++row)) { + var level = line.match(re)[0].length; + + if (level == line.length) + continue; + + if (level <= startLevel) + break; + + endRow = row; + } + + if (endRow > startRow) { + endColumn = this.getLine(endRow).length; + return new Range(startRow, startColumn, endRow, endColumn); + } + }, + + "cStyle": { + foldingStartMarker : /(\{|\[)[^\}\]]*$|^\s*(\/\*)/, + foldingStopMarker : /^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/, + getFoldWidgetRange: function(row) { + var line = this.getLine(row); + var match = line.match(this.foldingStartMarker); + if (match) { + var i = match.index; + + if (match[2]) + return this.getCommentFoldRange(row, i + match[0].length); + + var start = {row: row, column: i+1}; + var end = this.$findClosingBracket(match[1], start); + if (end) { + var fw = this.foldWidgets[end.row]; + if (fw == null) + fw = this.getFoldWidget(end.row); + + if (fw == "start"){ + end.row --; + end.column = this.getLine(end.row).length; + } + + } else { + end = {row: this.getLength(), column: 0}; + } + + return Range.fromPoints(start, end); + } + + var match = line.match(this.foldingStopMarker); + if (match) { + var i = match.index + match[0].length; + + if (match[2]) + return this.getCommentFoldRange(row, i); + + var end = {row: row, column: i}; + var start = this.$findOpeningBracket(match[1], end) + if (start){ + start.column++; + end.column--; + } else { + start = {row: 0, column: this.getLine(0).length} + } + + return Range.fromPoints(start, end); + } + } + }, + // TODO: folding based only on indentation + "indentation": null } exports.Folding = Folding; diff --git a/lib/ace/mode/c_cpp.js b/lib/ace/mode/c_cpp.js index 0d141904..9ff9a67f 100644 --- a/lib/ace/mode/c_cpp.js +++ b/lib/ace/mode/c_cpp.js @@ -55,6 +55,8 @@ oop.inherits(Mode, TextMode); (function() { + this.foldingRules = "cStyle"; + this.toggleCommentLines = function(state, doc, startRow, endRow) { var outdent = true; var outentedRows = []; diff --git a/lib/ace/mode/csharp.js b/lib/ace/mode/csharp.js index a5a8c27b..e5332cef 100644 --- a/lib/ace/mode/csharp.js +++ b/lib/ace/mode/csharp.js @@ -16,34 +16,36 @@ oop.inherits(Mode, TextMode); (function() { - this.getNextLineIndent = function(state, line, tab) { - var indent = this.$getIndent(line); + this.foldingRules = "cStyle"; - var tokenizedLine = this.$tokenizer.getLineTokens(line, state); - var tokens = tokenizedLine.tokens; - var endState = tokenizedLine.state; + this.getNextLineIndent = function(state, line, tab) { + var indent = this.$getIndent(line); + + var tokenizedLine = this.$tokenizer.getLineTokens(line, state); + var tokens = tokenizedLine.tokens; + var endState = tokenizedLine.state; + + if (tokens.length && tokens[tokens.length-1].type == "comment") { + return indent; + } + + if (state == "start") { + var match = line.match(/^.*[\{\(\[]\s*$/); + if (match) { + indent += tab; + } + } + + return indent; + }; - if (tokens.length && tokens[tokens.length-1].type == "comment") { - return indent; - } - - if (state == "start") { - var match = line.match(/^.*[\{\(\[]\s*$/); - if (match) { - indent += tab; - } - } - - return indent; - }; - - this.checkOutdent = function(state, line, input) { - return this.$outdent.checkOutdent(line, input); - }; - - this.autoOutdent = function(state, doc, row) { - this.$outdent.autoOutdent(doc, row); - }; + this.checkOutdent = function(state, line, input) { + return this.$outdent.checkOutdent(line, input); + }; + + this.autoOutdent = function(state, doc, row) { + this.$outdent.autoOutdent(doc, row); + }; this.createWorker = function(session) { diff --git a/lib/ace/mode/css.js b/lib/ace/mode/css.js index 6c8267db..02ea953f 100644 --- a/lib/ace/mode/css.js +++ b/lib/ace/mode/css.js @@ -52,6 +52,8 @@ oop.inherits(Mode, TextMode); (function() { + this.foldingRules = "cStyle"; + this.getNextLineIndent = function(state, line, tab) { var indent = this.$getIndent(line); diff --git a/lib/ace/mode/java.js b/lib/ace/mode/java.js index d8f5a241..f69bb8ad 100644 --- a/lib/ace/mode/java.js +++ b/lib/ace/mode/java.js @@ -16,6 +16,8 @@ oop.inherits(Mode, JavaScriptMode); (function() { + this.foldingRules = "cStyle"; + this.createWorker = function(session) { return null; }; diff --git a/lib/ace/mode/javascript.js b/lib/ace/mode/javascript.js index 38553837..5524c874 100644 --- a/lib/ace/mode/javascript.js +++ b/lib/ace/mode/javascript.js @@ -55,6 +55,8 @@ oop.inherits(Mode, TextMode); (function() { + this.foldingRules = "cStyle"; + this.toggleCommentLines = function(state, doc, startRow, endRow) { var outdent = true; var outentedRows = []; diff --git a/lib/ace/mode/json.js b/lib/ace/mode/json.js index e908d53c..d725a22f 100644 --- a/lib/ace/mode/json.js +++ b/lib/ace/mode/json.js @@ -54,6 +54,8 @@ oop.inherits(Mode, TextMode); (function() { + this.foldingRules = "cStyle"; + this.getNextLineIndent = function(state, line, tab) { var indent = this.$getIndent(line); diff --git a/lib/ace/mode/python.js b/lib/ace/mode/python.js index 5d95012c..caeee70b 100644 --- a/lib/ace/mode/python.js +++ b/lib/ace/mode/python.js @@ -52,6 +52,11 @@ oop.inherits(Mode, TextMode); (function() { + this.foldingRules = { + foldingStartMarker: /\:(:?\s*)?(:?#.*)?$/, + getFoldWidgetRange: "indentationBlock" + }; + this.toggleCommentLines = function(state, doc, startRow, endRow) { var outdent = true; var outentedRows = []; diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index b3ac15b3..1013384a 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -267,6 +267,9 @@ var VirtualRenderer = function(container, theme) { var pageY = event.getDocumentY(e); var row = this.screenToTextCoordinates(0, pageY).row; + if (e.target.className.indexOf('ace_fold-widget') != -1) + return this.session.onFoldWidgetClick(row, e); + this._dispatchEvent("gutter" + e.type, { row: row, htmlEvent: e