From e1cda7058213bf17a04193a9f8cbe6911eba8df6 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijnh@gmail.com> Date: Fri, 6 Mar 2015 10:12:31 +0100 Subject: [PATCH] [closebrackets addon] Make closing rules configurable per mode Make JavaScript mode autoclose backticks, remove single quote closing for Lisp modes, and add explicit triple-quote rule to languages supporting triple quoting. Issue #3110 --- addon/edit/closebrackets.js | 90 +++++++++++++++++++---------------- lib/codemirror.js | 2 +- mode/clike/clike.js | 3 +- mode/clojure/clojure.js | 1 + mode/commonlisp/commonlisp.js | 1 + mode/groovy/groovy.js | 1 + mode/javascript/javascript.js | 1 + mode/kotlin/kotlin.js | 1 + mode/python/python.js | 1 + mode/scheme/scheme.js | 1 + 10 files changed, 59 insertions(+), 43 deletions(-) diff --git a/addon/edit/closebrackets.js b/addon/edit/closebrackets.js index da89af58c..2bbc6f2a3 100644 --- a/addon/edit/closebrackets.js +++ b/addon/edit/closebrackets.js @@ -9,11 +9,11 @@ else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { - var DEFAULT_BRACKETS = "()[]{}''\"\""; - var BIND = DEFAULT_BRACKETS + "`"; - var DEFAULT_TRIPLES = "'\""; - var DEFAULT_EXPLODE_ON_ENTER = "[]{}"; - var SPACE_CHAR_REGEX = /\s/; + var defaults = { + pairs: "()[]{}''\"\"", + triples: "", + explode: "[]{}" + }; var Pos = CodeMirror.Pos; @@ -22,39 +22,44 @@ cm.removeKeyMap(keyMap); cm.state.closeBrackets = null; } - if (!val) return; - var config = cm.state.closeBrackets = { - pairs: DEFAULT_BRACKETS, - triples: DEFAULT_TRIPLES, - explode: DEFAULT_EXPLODE_ON_ENTER - }; - if (typeof val == "string") { - config.pairs = val; - } else if (typeof val == "object") { - if (val.pairs != null) config.pairs = val.pairs; - if (val.triples != null) config.triples = val.triples; - if (val.explode != null) config.explode = val.explode; + if (val) { + cm.state.closeBrackets = val; + cm.addKeyMap(keyMap); } - cm.addKeyMap(keyMap); }); + function getOption(conf, name) { + if (name == "pairs" && typeof conf == "string") return conf; + if (typeof conf == "object" && conf[name] != null) return conf[name]; + return defaults[name]; + } + + var bind = defaults.pairs + "`"; var keyMap = {Backspace: handleBackspace, Enter: handleEnter}; - for (var i = 0; i < BIND.length; i++) - keyMap["'" + BIND.charAt(i) + "'"] = handler(BIND.charAt(i)); + for (var i = 0; i < bind.length; i++) + keyMap["'" + bind.charAt(i) + "'"] = handler(bind.charAt(i)); function handler(ch) { return function(cm) { return handleChar(cm, ch); }; } + function getConfig(cm) { + var deflt = cm.state.closeBrackets; + if (!deflt) return null; + var mode = cm.getModeAt(cm.getCursor()); + return mode.closeBrackets || deflt; + } + function handleBackspace(cm) { - var data = cm.state.closeBrackets; - if (!data || cm.getOption("disableInput")) return CodeMirror.Pass; + var conf = getConfig(cm); + if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass; + var pairs = getOption(conf, "pairs"); var ranges = cm.listSelections(); for (var i = 0; i < ranges.length; i++) { if (!ranges[i].empty()) return CodeMirror.Pass; var around = charsAround(cm, ranges[i].head); - if (!around || data.pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass; + if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass; } for (var i = ranges.length - 1; i >= 0; i--) { var cur = ranges[i].head; @@ -63,14 +68,15 @@ } function handleEnter(cm) { - var data = cm.state.closeBrackets; - if (!data || !data.explode || cm.getOption("disableInput")) return CodeMirror.Pass; + var conf = getConfig(cm); + var explode = conf && getOption(conf, "explode"); + if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass; var ranges = cm.listSelections(); for (var i = 0; i < ranges.length; i++) { if (!ranges[i].empty()) return CodeMirror.Pass; var around = charsAround(cm, ranges[i].head); - if (!around || data.explode.indexOf(around) % 2 != 0) return CodeMirror.Pass; + if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass; } cm.operation(function() { cm.replaceSelection("\n\n", null); @@ -84,19 +90,16 @@ }); } - function isClosingBracket(ch, pairs) { - var pos = pairs.lastIndexOf(ch); - return pos > -1 && pos % 2 == 1; - } - function handleChar(cm, ch) { - var data = cm.state.closeBrackets; - if (!data || cm.getOption("disableInput")) return CodeMirror.Pass; + var conf = getConfig(cm); + if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass; - var pos = data.pairs.indexOf(ch); + var pairs = getOption(conf, "pairs"); + var pos = pairs.indexOf(ch); if (pos == -1) return CodeMirror.Pass; + var triples = getOption(conf, "triples"); - var identical = data.pairs.charAt(pos + 1) == ch; + var identical = pairs.charAt(pos + 1) == ch; var ranges = cm.listSelections(); var opening = pos % 2 == 0; @@ -107,11 +110,11 @@ if (opening && !range.empty()) { curType = "surround"; } else if ((identical || !opening) && next == ch) { - if (data.triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch) + if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch) curType = "skipThree"; else curType = "skip"; - } else if (identical && cur.ch > 1 && data.triples.indexOf(ch) >= 0 && + } else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 && cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch && (cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != ch)) { curType = "addFour"; @@ -119,8 +122,8 @@ if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, ch)) curType = "both"; else return CodeMirror.Pass; } else if (opening && (cm.getLine(cur.line).length == cur.ch || - isClosingBracket(next, data.pairs) || - SPACE_CHAR_REGEX.test(next))) { + isClosingBracket(next, pairs) || + /\s/.test(next))) { curType = "both"; } else { return CodeMirror.Pass; @@ -129,8 +132,8 @@ else if (type != curType) return CodeMirror.Pass; } - var left = pos % 2 ? data.pairs.charAt(pos - 1) : ch; - var right = pos % 2 ? ch : data.pairs.charAt(pos + 1); + var left = pos % 2 ? pairs.charAt(pos - 1) : ch; + var right = pos % 2 ? ch : pairs.charAt(pos + 1); cm.operation(function() { if (type == "skip") { cm.execCommand("goCharRight"); @@ -152,6 +155,11 @@ }); } + function isClosingBracket(ch, pairs) { + var pos = pairs.lastIndexOf(ch); + return pos > -1 && pos % 2 == 1; + } + function charsAround(cm, pos) { var str = cm.getRange(Pos(pos.line, pos.ch - 1), Pos(pos.line, pos.ch + 1)); diff --git a/lib/codemirror.js b/lib/codemirror.js index 6806c084c..197c40df3 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -4841,7 +4841,7 @@ getHelpers: function(pos, type) { var found = []; - if (!helpers.hasOwnProperty(type)) return helpers; + if (!helpers.hasOwnProperty(type)) return found; var help = helpers[type], mode = this.getModeAt(pos); if (typeof mode[type] == "string") { if (help[mode[type]]) found.push(help[mode[type]]); diff --git a/mode/clike/clike.js b/mode/clike/clike.js index e2223ccd1..f996f019d 100644 --- a/mode/clike/clike.js +++ b/mode/clike/clike.js @@ -403,7 +403,8 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { stream.eatWhile(/[\w\$_\xa1-\uffff]/); return "atom"; } - } + }, + modeProps: {closeBrackets: {triples: '"'}} }); def(["x-shader/x-vertex", "x-shader/x-fragment"], { diff --git a/mode/clojure/clojure.js b/mode/clojure/clojure.js index c334de730..d531022a2 100644 --- a/mode/clojure/clojure.js +++ b/mode/clojure/clojure.js @@ -234,6 +234,7 @@ CodeMirror.defineMode("clojure", function (options) { return state.indentStack.indent; }, + closeBrackets: {pairs: "()[]{}\"\""}, lineComment: ";;" }; }); diff --git a/mode/commonlisp/commonlisp.js b/mode/commonlisp/commonlisp.js index 5f50b352d..fb1f99c63 100644 --- a/mode/commonlisp/commonlisp.js +++ b/mode/commonlisp/commonlisp.js @@ -111,6 +111,7 @@ CodeMirror.defineMode("commonlisp", function (config) { return typeof i == "number" ? i : state.ctx.start + 1; }, + closeBrackets: {pairs: "()[]{}\"\""}, lineComment: ";;", blockCommentStart: "#|", blockCommentEnd: "|#" diff --git a/mode/groovy/groovy.js b/mode/groovy/groovy.js index 89b8224cf..e3a1db869 100644 --- a/mode/groovy/groovy.js +++ b/mode/groovy/groovy.js @@ -217,6 +217,7 @@ CodeMirror.defineMode("groovy", function(config) { }, electricChars: "{}", + closeBrackets: {triples: "'\""}, fold: "brace" }; }); diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js index 3f05ac46c..ddaa01ef4 100644 --- a/mode/javascript/javascript.js +++ b/mode/javascript/javascript.js @@ -669,6 +669,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { blockCommentEnd: jsonMode ? null : "*/", lineComment: jsonMode ? null : "//", fold: "brace", + closeBrackets: "()[]{}''\"\"``", helperType: jsonMode ? "json" : "javascript", jsonldMode: jsonldMode, diff --git a/mode/kotlin/kotlin.js b/mode/kotlin/kotlin.js index 73c84f6c4..1efec85df 100644 --- a/mode/kotlin/kotlin.js +++ b/mode/kotlin/kotlin.js @@ -271,6 +271,7 @@ CodeMirror.defineMode("kotlin", function (config, parserConfig) { else return ctx.indented + (closing ? 0 : config.indentUnit); }, + closeBrackets: {triples: "'\""}, electricChars: "{}" }; }); diff --git a/mode/python/python.js b/mode/python/python.js index 98c0409ae..979e84982 100644 --- a/mode/python/python.js +++ b/mode/python/python.js @@ -339,6 +339,7 @@ return scope.offset; }, + closeBrackets: {triples: "'\""}, lineComment: "#", fold: "indent" }; diff --git a/mode/scheme/scheme.js b/mode/scheme/scheme.js index 979edc096..223464591 100644 --- a/mode/scheme/scheme.js +++ b/mode/scheme/scheme.js @@ -239,6 +239,7 @@ CodeMirror.defineMode("scheme", function () { return state.indentStack.indent; }, + closeBrackets: {pairs: "()[]{}\"\""}, lineComment: ";;" }; }); -- GitLab