Merge pull request #264 from ilvalle/fix-editor
Code folding in admin editor
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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)};
|
||||
});
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
})();
|
||||
@@ -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
|
||||
|
||||
@@ -37,6 +37,13 @@
|
||||
<script src="{{=cm}}/addon/selection/active-line.js"></script>
|
||||
<script src="{{=cm}}/addon/display/fullscreen.js"></script>
|
||||
<link rel="stylesheet" href="{{=cm}}/addon/display/fullscreen.css">
|
||||
<script src="{{=cm}}/addon/fold/brace-fold.js"></script>
|
||||
<script src="{{=cm}}/addon/fold/comment-fold.js"></script>
|
||||
<script src="{{=cm}}/addon/fold/foldcode.js"></script>
|
||||
<script src="{{=cm}}/addon/fold/foldgutter.js"></script>
|
||||
<script src="{{=cm}}/addon/fold/indent-fold.js"></script>
|
||||
<script src="{{=cm}}/addon/fold/xml-fold.js"></script>
|
||||
<link rel="stylesheet" href="{{=cm}}/addon/fold/foldgutter.css">
|
||||
<script src="{{=cm}}/emmet.min.js"></script>
|
||||
<script src="{{=URL('static','js/ajax_editor.js')}}"></script>
|
||||
<link rel="stylesheet" href="{{=URL('static/css','typeahead.js-bootstrap.css')}}">
|
||||
@@ -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>");
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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: ''};
|
||||
|
||||
@@ -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']}}
|
||||
|
||||
<form id="editor_settings_form" class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="selectTheme">{{=T('Theme')}}</label>
|
||||
@@ -19,7 +20,11 @@
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="closeTag">{{=T('Enable Close-Tag')}}</label>
|
||||
<div class="controls">{{=SELECT(closetag, value=editor_settings['closetag'], _name="closetag" )}}</div>
|
||||
<div class="controls">{{=SELECT(booleanOptions, value=editor_settings['closetag'], _name="closetag" )}}</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="codefolding">{{=T('Enable Code Folding')}}</label>
|
||||
<div class="controls">{{=SELECT(booleanOptions, value=editor_settings['codefolding'], _name="codefolding" )}}</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls"><button type="submit" class="disabled btn btn-primary">{{=T('Save')}}</button></div>
|
||||
|
||||
Reference in New Issue
Block a user