diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py index 51faf83a..9b3a0772 100644 --- a/applications/admin/controllers/default.py +++ b/applications/admin/controllers/default.py @@ -563,7 +563,7 @@ def edit(): # Load json only if it is ajax edited... app = get_app(request.vars.app) app_path = apath(app, r=request) - editor_defaults={'theme':'web2py', 'editor': 'default', 'closetag': 'true'} + editor_defaults={'theme':'web2py', 'editor': 'default', 'closetag': 'true', 'codefolding': 'false'} config = Config(os.path.join(request.folder, 'settings.cfg'), section='editor', default_values=editor_defaults) preferences = config.read() @@ -580,7 +580,7 @@ def edit(): response.headers["web2py-component-flash"] = T('Preferences saved correctly') else: response.headers["web2py-component-flash"] = T('Preferences saved on session only') - response.headers["web2py-component-command"] = "update_editor('%s', '%s', '%s');jQuery('a[href=#editor_settings] button.close').click();" % (config.read()['theme'], config.read()['editor'], config.read()['closetag']) + response.headers["web2py-component-command"] = "update_editor('%(theme)s', '%(editor)s', %(closetag)s, %(codefolding)s);jQuery('a[href=#editor_settings] button.close').click();" % (config.read()) return else: details = {'filename':'settings', 'id':'editor_settings', 'force': False} diff --git a/applications/admin/static/codemirror/addon/fold/comment-fold.js b/applications/admin/static/codemirror/addon/fold/comment-fold.js new file mode 100644 index 00000000..a064cf8f --- /dev/null +++ b/applications/admin/static/codemirror/addon/fold/comment-fold.js @@ -0,0 +1,40 @@ +CodeMirror.registerHelper("fold", "comment", function(cm, start) { + var mode = cm.getModeAt(start), startToken = mode.blockCommentStart, endToken = mode.blockCommentEnd; + if (!startToken || !endToken) return; + var line = start.line, lineText = cm.getLine(line); + + var startCh; + for (var at = start.ch, pass = 0;;) { + var found = at <= 0 ? -1 : lineText.lastIndexOf(startToken, at - 1); + if (found == -1) { + if (pass == 1) return; + pass = 1; + at = lineText.length; + continue; + } + if (pass == 1 && found < start.ch) return; + if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1)))) { + startCh = found + startToken.length; + break; + } + at = found - 1; + } + + var depth = 1, lastLine = cm.lastLine(), end, endCh; + outer: for (var i = line; i <= lastLine; ++i) { + var text = cm.getLine(i), pos = i == line ? startCh : 0; + for (;;) { + var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos); + if (nextOpen < 0) nextOpen = text.length; + if (nextClose < 0) nextClose = text.length; + pos = Math.min(nextOpen, nextClose); + if (pos == text.length) break; + if (pos == nextOpen) ++depth; + else if (!--depth) { end = i; endCh = pos; break outer; } + ++pos; + } + } + if (end == null || line == end && endCh == startCh) return; + return {from: CodeMirror.Pos(line, startCh), + to: CodeMirror.Pos(end, endCh)}; +}); diff --git a/applications/admin/static/codemirror/addon/fold/foldgutter.css b/applications/admin/static/codemirror/addon/fold/foldgutter.css new file mode 100644 index 00000000..49805393 --- /dev/null +++ b/applications/admin/static/codemirror/addon/fold/foldgutter.css @@ -0,0 +1,21 @@ +.CodeMirror-foldmarker { + color: blue; + text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px; + font-family: arial; + line-height: .3; + cursor: pointer; +} +.CodeMirror-foldgutter { + width: .7em; +} +.CodeMirror-foldgutter-open, +.CodeMirror-foldgutter-folded { + color: #555; + cursor: pointer; +} +.CodeMirror-foldgutter-open:after { + content: "\25BE"; +} +.CodeMirror-foldgutter-folded:after { + content: "\25B8"; +} diff --git a/applications/admin/static/codemirror/addon/fold/foldgutter.js b/applications/admin/static/codemirror/addon/fold/foldgutter.js new file mode 100644 index 00000000..b809c93f --- /dev/null +++ b/applications/admin/static/codemirror/addon/fold/foldgutter.js @@ -0,0 +1,122 @@ +(function() { + "use strict"; + + CodeMirror.defineOption("foldGutter", false, function(cm, val, old) { + if (old && old != CodeMirror.Init) { + cm.clearGutter(cm.state.foldGutter.options.gutter); + cm.state.foldGutter = null; + cm.off("gutterClick", onGutterClick); + cm.off("change", onChange); + cm.off("viewportChange", onViewportChange); + cm.off("fold", onFold); + cm.off("unfold", onFold); + } + if (val) { + cm.state.foldGutter = new State(parseOptions(val)); + updateInViewport(cm); + cm.on("gutterClick", onGutterClick); + cm.on("change", onChange); + cm.on("viewportChange", onViewportChange); + cm.on("fold", onFold); + cm.on("unfold", onFold); + } + }); + + var Pos = CodeMirror.Pos; + + function State(options) { + this.options = options; + this.from = this.to = 0; + } + + function parseOptions(opts) { + if (opts === true) opts = {}; + if (opts.gutter == null) opts.gutter = "CodeMirror-foldgutter"; + if (opts.indicatorOpen == null) opts.indicatorOpen = "CodeMirror-foldgutter-open"; + if (opts.indicatorFolded == null) opts.indicatorFolded = "CodeMirror-foldgutter-folded"; + return opts; + } + + function isFolded(cm, line) { + var marks = cm.findMarksAt(Pos(line)); + for (var i = 0; i < marks.length; ++i) + if (marks[i].__isFold && marks[i].find().from.line == line) return true; + } + + function marker(spec) { + if (typeof spec == "string") { + var elt = document.createElement("div"); + elt.className = spec; + return elt; + } else { + return spec.cloneNode(true); + } + } + + function updateFoldInfo(cm, from, to) { + var opts = cm.state.foldGutter.options, cur = from; + cm.eachLine(from, to, function(line) { + var mark = null; + if (isFolded(cm, cur)) { + mark = marker(opts.indicatorFolded); + } else { + var pos = Pos(cur, 0), func = opts.rangeFinder || cm.getHelper(pos, "fold"); + var range = func && func(cm, pos); + if (range && range.from.line + 1 < range.to.line) + mark = marker(opts.indicatorOpen); + } + cm.setGutterMarker(line, opts.gutter, mark); + ++cur; + }); + } + + function updateInViewport(cm) { + var vp = cm.getViewport(), state = cm.state.foldGutter; + if (!state) return; + cm.operation(function() { + updateFoldInfo(cm, vp.from, vp.to); + }); + state.from = vp.from; state.to = vp.to; + } + + function onGutterClick(cm, line, gutter) { + var opts = cm.state.foldGutter.options; + if (gutter != opts.gutter) return; + cm.foldCode(Pos(line, 0), opts.rangeFinder); + } + + function onChange(cm) { + var state = cm.state.foldGutter; + state.from = state.to = 0; + clearTimeout(state.changeUpdate); + state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, 600); + } + + function onViewportChange(cm) { + var state = cm.state.foldGutter; + clearTimeout(state.changeUpdate); + state.changeUpdate = setTimeout(function() { + var vp = cm.getViewport(); + if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) { + updateInViewport(cm); + } else { + cm.operation(function() { + if (vp.from < state.from) { + updateFoldInfo(cm, vp.from, state.from); + state.from = vp.from; + } + if (vp.to > state.to) { + updateFoldInfo(cm, state.to, vp.to); + state.to = vp.to; + } + }); + } + }, 400); + } + + function onFold(cm, from) { + var state = cm.state.foldGutter, line = from.line; + if (line >= state.from && line < state.to) + updateFoldInfo(cm, line, line + 1); + } +})(); diff --git a/applications/admin/static/codemirror/addon/fold/indent-fold.js b/applications/admin/static/codemirror/addon/fold/indent-fold.js index fcbff966..b54da347 100644 --- a/applications/admin/static/codemirror/addon/fold/indent-fold.js +++ b/applications/admin/static/codemirror/addon/fold/indent-fold.js @@ -1,12 +1,26 @@ CodeMirror.registerHelper("fold", "indent", function(cm, start) { - var tabSize = cm.getOption("tabSize"), firstLine = cm.getLine(start.line); - var myIndent = CodeMirror.countColumn(firstLine, null, tabSize); - for (var i = start.line + 1, end = cm.lineCount(); i < end; ++i) { - var curLine = cm.getLine(i); - if (CodeMirror.countColumn(curLine, null, tabSize) < myIndent && - CodeMirror.countColumn(cm.getLine(i-1), null, tabSize) > myIndent) + var lastLine = cm.lastLine(), + tabSize = cm.getOption("tabSize"), + firstLine = cm.getLine(start.line), + myIndent = CodeMirror.countColumn(firstLine, null, tabSize); + + function foldEnded(curColumn, prevColumn) { + return curColumn < myIndent || + (curColumn == myIndent && prevColumn >= myIndent) || + (curColumn > myIndent && i == lastLine); + } + + for (var i = start.line + 1; i <= lastLine; i++) { + var curColumn = CodeMirror.countColumn(cm.getLine(i), null, tabSize); + var prevColumn = CodeMirror.countColumn(cm.getLine(i-1), null, tabSize); + + if (foldEnded(curColumn, prevColumn)) { + var lastFoldLineNumber = curColumn > myIndent && i == lastLine ? i : i-1; + var lastFoldLine = cm.getLine(lastFoldLineNumber); return {from: CodeMirror.Pos(start.line, firstLine.length), - to: CodeMirror.Pos(i, curLine.length)}; + to: CodeMirror.Pos(lastFoldLineNumber, lastFoldLine.length)}; + } } }); + CodeMirror.indentRangeFinder = CodeMirror.fold.indent; // deprecated diff --git a/applications/admin/views/default/edit.html b/applications/admin/views/default/edit.html index b3acb746..e33115a5 100644 --- a/applications/admin/views/default/edit.html +++ b/applications/admin/views/default/edit.html @@ -37,6 +37,13 @@ + + + + + + + @@ -49,6 +56,7 @@ var current_editor = "{{=editor_settings['editor']}}"; //Default editor {{else:}} var current_closetag = false; //Default closetag {{pass}} +var current_codefolding = {{=editor_settings['codefolding']}}; //Default codefolding var current_font_incr = 0; // Default font-size, 0 means don't set jQuery(document).on('shown click', 'a[data-toggle="tab"]', function (e) { @@ -102,7 +110,7 @@ jQuery(document).on('click', 'a.editor_filelink, a#editor_settingslink', functio load_file(url); }); -function update_editor(editor_theme, editor_name, editor_closetag) { +function update_editor(editor_theme, editor_name, editor_closetag, editor_codefolding) { var href = "{{="%s/theme/" % cm}}" + editor_theme + ".css"; var link = jQuery(""); link.attr({ @@ -122,15 +130,14 @@ function update_editor(editor_theme, editor_name, editor_closetag) { } current_theme = editor_theme; current_editor = editor_name; - switch (editor_closetag){ - case 'true': current_closetag = true; break; - case 'false': current_closetag = false; - } + current_codefolding = editor_codefolding; + current_closetag = editor_closetag; jQuery('textarea[name="data"]') .each(function(id, ta) { editor = jQuery(ta).data('editor'); editor.setOption("theme", current_theme); editor.setOption("keyMap", current_editor); editor.setOption("autoCloseTags", current_closetag); + editor.setOption("foldGutter", current_codefolding); }); } diff --git a/applications/admin/views/default/edit_js.html b/applications/admin/views/default/edit_js.html index 8096fbd9..59bccbbe 100644 --- a/applications/admin/views/default/edit_js.html +++ b/applications/admin/views/default/edit_js.html @@ -54,15 +54,18 @@ theme: current_theme, tabMode: "shift", lineWrapping: true, - gutters: ["CodeMirror-linenumbers", "breakpoints"], + foldGutter: current_codefolding, + gutters: ["CodeMirror-linenumbers", "breakpoints", "CodeMirror-foldgutter"], keyMap: current_editor, matchBrackets: true, autofocus: false, height: "350px", showTrailingSpace: true }); + editor.foldCode(CodeMirror.Pos(8, 0)); - editor.on("gutterClick", function(cm, n) { + editor.on("gutterClick", function(cm, n, gutter) { + if (gutter !== "breakpoints" ) return; var info = cm.lineInfo(n); cm.setGutterMarker(n, "breakpoints", info.gutterMarkers ? null : makeMarker()); sel = {start: n, end: n, data: ''}; diff --git a/applications/admin/views/default/editor_settings.html b/applications/admin/views/default/editor_settings.html index 7f35fd87..64e8514b 100644 --- a/applications/admin/views/default/editor_settings.html +++ b/applications/admin/views/default/editor_settings.html @@ -7,7 +7,8 @@ }} {{themes = [f[:-4] for f in listfiles('admin', "static/codemirror/theme", regexp='.*\.css$' )]}} {{editors = ['default', 'vim', 'emacs']}} -{{closetag = ['true', 'false']}} +{{booleanOptions = ['true', 'false']}} +
@@ -19,7 +20,11 @@
-
{{=SELECT(closetag, value=editor_settings['closetag'], _name="closetag" )}}
+
{{=SELECT(booleanOptions, value=editor_settings['closetag'], _name="closetag" )}}
+
+
+ +
{{=SELECT(booleanOptions, value=editor_settings['codefolding'], _name="codefolding" )}}