diff --git a/AUTHORS b/AUTHORS index ad2ec99576..260c13a864 100644 --- a/AUTHORS +++ b/AUTHORS @@ -84,6 +84,7 @@ Ford_Lawnmower Gabriel Nahmias galambalazs Gautam Mehta +Glenn Jorde Glenn Ruehle Golevka Gordon Smith diff --git a/addon/edit/closetag.js b/addon/edit/closetag.js index 0bc3e8be17..d6a8fafd3c 100644 --- a/addon/edit/closetag.js +++ b/addon/edit/closetag.js @@ -72,7 +72,7 @@ function autoCloseSlash(cm) { var pos = cm.getCursor(), tok = cm.getTokenAt(pos); var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state; - if (tok.string.charAt(0) != "<" || inner.mode.name != "xml") return CodeMirror.Pass; + if (tok.string.charAt(0) != "<" || tok.start != pos.ch - 1 || inner.mode.name != "xml") return CodeMirror.Pass; var tagName = state.context && state.context.tagName; if (tagName) cm.replaceSelection("/" + tagName + ">", "end"); diff --git a/addon/edit/continuelist.js b/addon/edit/continuelist.js index fb1fc38ba2..826d17d716 100644 --- a/addon/edit/continuelist.js +++ b/addon/edit/continuelist.js @@ -6,7 +6,7 @@ CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) { var pos = cm.getCursor(), - inList = cm.getStateAfter(pos.line).list, + inList = cm.getStateAfter(pos.line).list !== false, match; if (!inList || !(match = cm.getLine(pos.line).match(listRE))) { diff --git a/addon/fold/foldcode.js b/addon/fold/foldcode.js index 931c3726cc..c497bc29b6 100644 --- a/addon/fold/foldcode.js +++ b/addon/fold/foldcode.js @@ -1,7 +1,7 @@ (function() { "use strict"; - function doFold(cm, pos, options) { + function doFold(cm, pos, options, force) { var finder = options && (options.call ? options : options.rangeFinder); if (!finder) finder = cm.getHelper(pos, "fold"); if (!finder) return; @@ -13,7 +13,7 @@ if (!range || range.to.line - range.from.line < minSize) return null; var marks = cm.findMarksAt(range.from); for (var i = 0; i < marks.length; ++i) { - if (marks[i].__isFold) { + if (marks[i].__isFold && force !== "fold") { if (!allowFolded) return null; range.cleared = true; marks[i].clear(); @@ -27,7 +27,7 @@ pos = CodeMirror.Pos(pos.line - 1, 0); range = getRange(false); } - if (!range || range.cleared) return; + if (!range || range.cleared || force === "unfold") return; var myWidget = makeWidget(options); CodeMirror.on(myWidget, "mousedown", function() { myRange.clear(); }); @@ -59,7 +59,9 @@ }; // New-style interface - CodeMirror.defineExtension("foldCode", function(pos, options) { doFold(this, pos, options); }); + CodeMirror.defineExtension("foldCode", function(pos, options, force) { + doFold(this, pos, options, force); + }); CodeMirror.registerHelper("fold", "combine", function() { var funcs = Array.prototype.slice.call(arguments, 0); diff --git a/addon/fold/foldgutter.css b/addon/fold/foldgutter.css new file mode 100644 index 0000000000..49805393d0 --- /dev/null +++ b/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/addon/fold/foldgutter.js b/addon/fold/foldgutter.js index b809c93f56..e3c52bc229 100644 --- a/addon/fold/foldgutter.js +++ b/addon/fold/foldgutter.js @@ -10,6 +10,7 @@ cm.off("viewportChange", onViewportChange); cm.off("fold", onFold); cm.off("unfold", onFold); + cm.off("swapDoc", updateInViewport); } if (val) { cm.state.foldGutter = new State(parseOptions(val)); @@ -19,6 +20,7 @@ cm.on("viewportChange", onViewportChange); cm.on("fold", onFold); cm.on("unfold", onFold); + cm.on("swapDoc", updateInViewport); } }); diff --git a/addon/fold/indent-fold.js b/addon/fold/indent-fold.js index fcbff96696..b54da34777 100644 --- a/addon/fold/indent-fold.js +++ b/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/addon/hint/javascript-hint.js b/addon/hint/javascript-hint.js index 042fe1325d..513fb782b0 100644 --- a/addon/hint/javascript-hint.js +++ b/addon/hint/javascript-hint.js @@ -33,21 +33,6 @@ tprop = getToken(editor, Pos(cur.line, tprop.start)); if (tprop.string != ".") return; tprop = getToken(editor, Pos(cur.line, tprop.start)); - if (tprop.string == ')') { - var level = 1; - do { - tprop = getToken(editor, Pos(cur.line, tprop.start)); - switch (tprop.string) { - case ')': level++; break; - case '(': level--; break; - default: break; - } - } while (level > 0); - tprop = getToken(editor, Pos(cur.line, tprop.start)); - if (tprop.type.indexOf("variable") === 0) - tprop.type = "function"; - else return; // no clue - } if (!context) var context = []; context.push(tprop); } @@ -110,11 +95,11 @@ for (var name in obj) maybeAdd(name); } - if (context) { + if (context && context.length) { // If this is a property, see if it belongs to some object we can // find in the current environment. var obj = context.pop(), base; - if (obj.type.indexOf("variable") === 0) { + if (obj.type && obj.type.indexOf("variable") === 0) { if (options && options.additionalContext) base = options.additionalContext[obj.string]; base = base || window[obj.string]; @@ -132,8 +117,7 @@ while (base != null && context.length) base = base[context.pop().string]; if (base != null) gatherCompletions(base); - } - else { + } else { // If not, just look in the window object and any local scope // (reading into JS mode internals to get at the local and global variables) for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name); diff --git a/addon/hint/sql-hint.js b/addon/hint/sql-hint.js new file mode 100644 index 0000000000..95f6b50a15 --- /dev/null +++ b/addon/hint/sql-hint.js @@ -0,0 +1,105 @@ +(function () { + "use strict"; + + var tables; + var keywords; + + function getKeywords(editor) { + var mode = editor.doc.modeOption; + if(mode === "sql") mode = "text/x-sql"; + return CodeMirror.resolveMode(mode).keywords; + } + + function match(string, word) { + var len = string.length; + var sub = word.substr(0, len); + return string.toUpperCase() === sub.toUpperCase(); + } + + function addMatches(result, search, wordlist, formatter) { + for(var word in wordlist) { + if(!wordlist.hasOwnProperty(word)) continue; + if(Array.isArray(wordlist)) { + word = wordlist[word]; + } + if(match(search, word)) { + result.push(formatter(word)); + } + } + } + + function columnCompletion(result, editor) { + var cur = editor.getCursor(); + var token = editor.getTokenAt(cur); + var string = token.string.substr(1); + var prevCur = CodeMirror.Pos(cur.line, token.start); + var table = editor.getTokenAt(prevCur).string; + var columns = tables[table]; + if(!columns) { + table = findTableByAlias(table, editor); + } + columns = tables[table]; + if(!columns) { + return; + } + addMatches(result, string, columns, + function(w) {return "." + w;}); + } + + function eachWord(line, f) { + var words = line.text.split(" "); + for(var i = 0; i < words.length; i++) { + f(words[i]); + } + } + + // Tries to find possible table name from alias. + function findTableByAlias(alias, editor) { + var aliasUpperCase = alias.toUpperCase(); + var previousWord = ""; + var table = ""; + + editor.eachLine(function(line) { + eachWord(line, function(word) { + var wordUpperCase = word.toUpperCase(); + if(wordUpperCase === aliasUpperCase) { + if(tables.hasOwnProperty(previousWord)) { + table = previousWord; + } + } + if(wordUpperCase !== "AS") { + previousWord = word; + } + }); + }); + return table; + } + + function sqlHint(editor, options) { + tables = (options && options.tables) || {}; + keywords = keywords || getKeywords(editor); + var cur = editor.getCursor(); + var token = editor.getTokenAt(cur); + + var result = []; + + var search = token.string.trim(); + + addMatches(result, search, keywords, + function(w) {return w.toUpperCase();}); + + addMatches(result, search, tables, + function(w) {return w;}); + + if(search.lastIndexOf('.') === 0) { + columnCompletion(result, editor); + } + + return { + list: result, + from: CodeMirror.Pos(cur.line, token.start), + to: CodeMirror.Pos(cur.line, token.end) + }; + } + CodeMirror.registerHelper("hint", "sql", sqlHint); +})(); diff --git a/addon/hint/xml-hint.js b/addon/hint/xml-hint.js index b6c1da2ce2..a721743449 100644 --- a/addon/hint/xml-hint.js +++ b/addon/hint/xml-hint.js @@ -37,6 +37,7 @@ Pos(cur.line, token.type == "string" ? token.start : token.end)); var atName = before.match(/([^\s\u00a0=<>\"\']+)=$/), atValues; if (!atName || !attrs.hasOwnProperty(atName[1]) || !(atValues = attrs[atName[1]])) return; + if (typeof atValues == 'function') atValues = atValues.call(this, cm); // Functions can be used to supply values for autocomplete widget if (token.type == "string") { prefix = token.string; if (/['"]/.test(token.string.charAt(0))) { diff --git a/addon/lint/lint.css b/addon/lint/lint.css index e592b3672a..414a9a0e06 100644 --- a/addon/lint/lint.css +++ b/addon/lint/lint.css @@ -14,6 +14,7 @@ padding: 2px 5px; position: fixed; white-space: pre; + white-space: pre-wrap; z-index: 100; max-width: 600px; opacity: 0; diff --git a/addon/search/search.js b/addon/search/search.js index c30922df4f..71ed75b500 100644 --- a/addon/search/search.js +++ b/addon/search/search.js @@ -70,6 +70,7 @@ if (!cursor.find(rev)) return; } cm.setSelection(cursor.from(), cursor.to()); + cm.scrollIntoView({from: cursor.from(), to: cursor.to()}); state.posFrom = cursor.from(); state.posTo = cursor.to(); });} function clearSearch(cm) {cm.operation(function() { @@ -108,6 +109,7 @@ (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return; } cm.setSelection(cursor.from(), cursor.to()); + cm.scrollIntoView({from: cursor.from(), to: cursor.to()}); confirmDialog(cm, doReplaceConfirm, "Replace?", [function() {doReplace(match);}, advance]); }; diff --git a/addon/tern/tern.js b/addon/tern/tern.js index 29ccc94886..fe93fa7335 100644 --- a/addon/tern/tern.js +++ b/addon/tern/tern.js @@ -439,7 +439,7 @@ for (var file in perFile) { var known = ts.docs[file], chs = perFile[file];; if (!known) continue; - chs.sort(function(a, b) { return cmpPos(b, a); }); + chs.sort(function(a, b) { return cmpPos(b.start, a.start); }); var origin = "*rename" + (++nextChangeOrig); for (var i = 0; i < chs.length; ++i) { var ch = chs[i]; diff --git a/addon/wrap/hardwrap.js b/addon/wrap/hardwrap.js new file mode 100644 index 0000000000..f252ffc9e9 --- /dev/null +++ b/addon/wrap/hardwrap.js @@ -0,0 +1,99 @@ +(function() { + "use strict"; + + var Pos = CodeMirror.Pos; + + function findParagraph(cm, pos, options) { + var startRE = options.paragraphStart || cm.getHelper(pos, "paragraphStart"); + for (var start = pos.line, first = cm.firstLine(); start > first; --start) { + var line = cm.getLine(start); + if (startRE && startRE.test(line)) break; + if (!/\S/.test(line)) { ++start; break; } + } + var endRE = options.paragraphEnd || cm.getHelper(pos, "paragraphEnd"); + for (var end = pos.line + 1, last = cm.lastLine(); end <= last; ++end) { + var line = cm.getLine(end); + if (endRE && endRE.test(line)) { ++end; break; } + if (!/\S/.test(line)) break; + } + return {from: start, to: end}; + } + + function findBreakPoint(text, column, wrapOn, killTrailingSpace) { + for (var at = column; at > 0; --at) + if (wrapOn.test(text.slice(at - 1, at + 1))) break; + if (at == 0) at = column; + var endOfText = at; + if (killTrailingSpace) + while (text.charAt(endOfText - 1) == " ") --endOfText; + return {from: endOfText, to: at}; + } + + function wrapRange(cm, from, to, options) { + from = cm.clipPos(from); to = cm.clipPos(to); + var column = options.column || 80; + var wrapOn = options.wrapOn || /\s\S|-[^\.\d]/; + var killTrailing = options.killTrailingSpace !== false; + var changes = [], curLine = "", curNo = from.line; + var lines = cm.getRange(from, to, false); + for (var i = 0; i < lines.length; ++i) { + var text = lines[i], oldLen = curLine.length, spaceInserted = 0; + if (curLine && text && !wrapOn.test(curLine.charAt(curLine.length - 1) + text.charAt(0))) { + curLine += " "; + spaceInserted = 1; + } + curLine += text; + if (i) { + var firstBreak = curLine.length > column && findBreakPoint(curLine, column, wrapOn, killTrailing); + // If this isn't broken, or is broken at a different point, remove old break + if (!firstBreak || firstBreak.from != oldLen || firstBreak.to != oldLen + spaceInserted) { + changes.push({text: spaceInserted ? " " : "", + from: Pos(curNo, oldLen), + to: Pos(curNo + 1, 0)}); + } else { + curLine = text; + ++curNo; + } + } + while (curLine.length > column) { + var bp = findBreakPoint(curLine, column, wrapOn, killTrailing); + changes.push({text: "\n", + from: Pos(curNo, bp.from), + to: Pos(curNo, bp.to)}); + curLine = curLine.slice(bp.to); + ++curNo; + } + } + if (changes.length) cm.operation(function() { + for (var i = 0; i < changes.length; ++i) { + var change = changes[i]; + cm.replaceRange(change.text, change.from, change.to); + } + }); + } + + CodeMirror.defineExtension("wrapParagraph", function(pos, options) { + options = options || {}; + if (!pos) pos = this.getCursor(); + var para = findParagraph(this, pos, options); + wrapRange(this, Pos(para.from, 0), Pos(para.to - 1), options); + }); + + CodeMirror.defineExtension("wrapRange", function(from, to, options) { + wrapRange(this, from, to, options || {}); + }); + + CodeMirror.defineExtension("wrapParagraphsInRange", function(from, to, options) { + options = options || {}; + var cm = this, paras = []; + for (var line = from.line; line <= to.line;) { + var para = findParagraph(cm, Pos(line, 0), options); + paras.push(para); + line = para.to; + } + if (paras.length) cm.operation(function() { + for (var i = paras.length - 1; i >= 0; --i) + wrapRange(cm, Pos(paras[i].from, 0), Pos(paras[i].to - 1), options); + }); + }); +})(); diff --git a/bower.json b/bower.json index 8259431c4d..66e049dfbe 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,5 @@ { "name": "CodeMirror", - "version": "3.18.0", "main": ["lib/codemirror.js", "lib/codemirror.css"], "ignore": [ "**/.*", diff --git a/demo/folding.html b/demo/folding.html index 29ddb15cb1..9461801277 100644 --- a/demo/folding.html +++ b/demo/folding.html @@ -5,6 +5,7 @@ + @@ -15,28 +16,7 @@ +