From d25531977e186efa7f5a619b408b8faf2d01e1f5 Mon Sep 17 00:00:00 2001 From: tophf Date: Thu, 23 Apr 2015 05:44:04 +0300 Subject: [PATCH] CodeMirror 5.2.1 update * keep our custom css-lint.js, no changes in upstream * use newer show-hint.js - {completeSingle: true} was broken in 5.2.0 * use newer search.js - replace() didn't use previous search query in 5.2.0 --- codemirror/.gitignore | 4 +- codemirror/.travis.yml | 2 +- codemirror/AUTHORS | 18 + codemirror/README.md | 2 +- codemirror/addon/dialog/dialog.css | 4 +- codemirror/addon/dialog/dialog.js | 4 +- codemirror/addon/fold/foldgutter.js | 6 +- codemirror/addon/hint/css-hint.js | 4 + codemirror/addon/hint/show-hint.js | 132 ++++---- codemirror/addon/lint/lint.js | 2 + codemirror/addon/scroll/annotatescrollbar.js | 23 +- codemirror/addon/search/matchesonscrollbar.js | 4 +- codemirror/addon/search/search.js | 11 +- codemirror/addon/search/searchcursor.js | 4 +- codemirror/bower.json | 6 +- codemirror/index.html | 4 +- codemirror/keymap/sublime.js | 13 + codemirror/keymap/vim.js | 315 +++++++++++------- codemirror/lib/codemirror.css | 21 +- codemirror/lib/codemirror.js | 283 ++++++++++------ codemirror/mode/css/css.js | 5 +- codemirror/mode/css/less.html | 2 +- codemirror/mode/css/less_test.js | 3 + codemirror/mode/css/scss.html | 2 +- codemirror/mode/css/scss_test.js | 2 +- codemirror/package.json | 2 +- codemirror/theme/liquibyte.css | 95 ++++++ codemirror/theme/mdn-like.css | 2 +- codemirror/theme/monokai.css | 2 +- codemirror/theme/solarized.css | 2 +- 30 files changed, 644 insertions(+), 335 deletions(-) create mode 100644 codemirror/theme/liquibyte.css diff --git a/codemirror/.gitignore b/codemirror/.gitignore index b471fe6e..f91c241f 100644 --- a/codemirror/.gitignore +++ b/codemirror/.gitignore @@ -1,6 +1,8 @@ /node_modules /npm-debug.log -test.html +/test*.html .tern-* *~ *.swp +.idea +*.iml diff --git a/codemirror/.travis.yml b/codemirror/.travis.yml index baa0031d..20fd86b6 100644 --- a/codemirror/.travis.yml +++ b/codemirror/.travis.yml @@ -1,3 +1,3 @@ language: node_js node_js: - - 0.8 + - 0.10 diff --git a/codemirror/AUTHORS b/codemirror/AUTHORS index 9d62d48e..b9bf7210 100644 --- a/codemirror/AUTHORS +++ b/codemirror/AUTHORS @@ -25,6 +25,7 @@ Alexandre Bique alexey-k Alex Piggott Aliaksei Chapyzhenka +Amin Shali Amsul amuntean Amy @@ -81,12 +82,16 @@ Cheah Chu Yeow Chris Coyier Chris Granger Chris Houseknecht +Chris Lohfink Chris Morgan Christian Oyarzun Christian Petrov Christopher Brown +Christopher Mitchell +Christopher Pfohl ciaranj CodeAnimal +coderaiser ComFreek Curtis Gagliardi dagsta @@ -105,6 +110,7 @@ Danny Yoo darealshinji Darius Roberts Dave Myers +David Barnett David Mignot David Pathakjee David Vázquez @@ -181,6 +187,7 @@ ilvalle Ingo Richter Irakli Gozalishvili Ivan Kurnosov +Ivoah Jacob Lee Jakob Miland Jakub Vrana @@ -212,6 +219,7 @@ John Connor John Lees-Miller John Snelson John Van Der Loo +Jonas Döbertin Jonathan Malmaud jongalloway Jon Malmaud @@ -222,6 +230,7 @@ Joshua Newman Josh Watzman jots jsoojeon +ju1ius Juan Benavides Romero Jucovschi Constantin Juho Vuori @@ -230,6 +239,7 @@ jwallers@gmail.com kaniga Ken Newman Ken Rockot +Kevin Earls Kevin Sawicki Kevin Ushey Klaus Silveira @@ -248,6 +258,8 @@ Leonid Khachaturov Leon Sorokin Leonya Khachaturov Liam Newman +Libo Cannici +LloydMilligan LM lochel Lorenzo Stoakes @@ -272,6 +284,7 @@ Marko Bonaci Martin Balek Martín Gaitán Martin Hasoň +Martin Hunt Mason Malone Mateusz Paprocki Mathias Bynens @@ -290,6 +303,7 @@ Max Xiantu mbarkhau Metatheos Micah Dubinko +Michael Grey Michael Lehenbauer Michael Zhou Mighty Guava @@ -306,6 +320,7 @@ misfo mloginov Moritz Schwörer mps +ms mtaran-google Narciso Jaramillo Nathan Williams @@ -317,6 +332,7 @@ nguillaumin Ng Zhi An Nicholas Bollweg Nicholas Bollweg (Nick) +Nick Kreeger Nick Small Niels van Groningen nightwing @@ -331,6 +347,7 @@ pablo Page Panupong Pasupat paris +Paris Patil Arpith Patrick Stoica Patrick Strawderman @@ -351,6 +368,7 @@ Randall Mason Randy Burden Randy Edmunds Rasmus Erik Voel Jensen +ray ratchup Ray Ratchup Richard van der Meer Richard Z.H. Wang diff --git a/codemirror/README.md b/codemirror/README.md index bc6e7f5c..38156a74 100644 --- a/codemirror/README.md +++ b/codemirror/README.md @@ -1,7 +1,7 @@ # CodeMirror [![Build Status](https://travis-ci.org/codemirror/CodeMirror.svg)](https://travis-ci.org/codemirror/CodeMirror) [![NPM version](https://img.shields.io/npm/v/codemirror.svg)](https://www.npmjs.org/package/codemirror) -[Funding status: ![maintainer happiness](https://marijnhaverbeke.nl/fund/status_s.png)](https://marijnhaverbeke.nl/fund/) +[Funding status: ![maintainer happiness](https://marijnhaverbeke.nl/fund/status_s.png?again)](https://marijnhaverbeke.nl/fund/) CodeMirror is a JavaScript component that provides a code editor in the browser. When a mode is available for the language you are coding diff --git a/codemirror/addon/dialog/dialog.css b/codemirror/addon/dialog/dialog.css index 2e7c0fc9..677c0783 100644 --- a/codemirror/addon/dialog/dialog.css +++ b/codemirror/addon/dialog/dialog.css @@ -1,11 +1,11 @@ .CodeMirror-dialog { position: absolute; left: 0; right: 0; - background: white; + background: inherit; z-index: 15; padding: .1em .8em; overflow: hidden; - color: #333; + color: inherit; } .CodeMirror-dialog-top { diff --git a/codemirror/addon/dialog/dialog.js b/codemirror/addon/dialog/dialog.js index e0e8ad4e..323b2007 100644 --- a/codemirror/addon/dialog/dialog.js +++ b/codemirror/addon/dialog/dialog.js @@ -58,7 +58,9 @@ if (inp) { if (options.value) { inp.value = options.value; - inp.select(); + if (options.selectValueOnOpen !== false) { + inp.select(); + } } if (options.onInput) diff --git a/codemirror/addon/fold/foldgutter.js b/codemirror/addon/fold/foldgutter.js index 199120c7..ed7bd87d 100644 --- a/codemirror/addon/fold/foldgutter.js +++ b/codemirror/addon/fold/foldgutter.js @@ -52,7 +52,7 @@ 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; + if (marks[i].__isFold && marks[i].find().from.line == line) return marks[i]; } function marker(spec) { @@ -98,7 +98,9 @@ if (!state) return; var opts = state.options; if (gutter != opts.gutter) return; - cm.foldCode(Pos(line, 0), opts.rangeFinder); + var folded = isFolded(cm, line); + if (folded) folded.clear(); + else cm.foldCode(Pos(line, 0), opts.rangeFinder); } function onChange(cm) { diff --git a/codemirror/addon/hint/css-hint.js b/codemirror/addon/hint/css-hint.js index 488da344..22642727 100644 --- a/codemirror/addon/hint/css-hint.js +++ b/codemirror/addon/hint/css-hint.js @@ -20,6 +20,10 @@ var inner = CodeMirror.innerMode(cm.getMode(), token.state); if (inner.mode.name != "css") return; + if (token.type == "keyword" && "!important".indexOf(token.string) == 0) + return {list: ["!important"], from: CodeMirror.Pos(cur.line, token.start), + to: CodeMirror.Pos(cur.line, token.end)}; + var start = token.start, end = cur.ch, word = token.string.slice(0, end - start); if (/[^\w$_-]/.test(word)) { word = ""; start = end = cur.ch; diff --git a/codemirror/addon/hint/show-hint.js b/codemirror/addon/hint/show-hint.js index f5446194..cb60afe0 100644 --- a/codemirror/addon/hint/show-hint.js +++ b/codemirror/addon/hint/show-hint.js @@ -24,44 +24,44 @@ return cm.showHint(newOpts); }; - var asyncRunID = 0; - function retrieveHints(getter, cm, options, then) { - if (getter.async) { - var id = ++asyncRunID; - getter(cm, function(hints) { - if (asyncRunID == id) then(hints); - }, options); - } else { - then(getter(cm, options)); - } - } - CodeMirror.defineExtension("showHint", function(options) { // We want a single cursor position. if (this.listSelections().length > 1 || this.somethingSelected()) return; if (this.state.completionActive) this.state.completionActive.close(); var completion = this.state.completionActive = new Completion(this, options); - var getHints = completion.options.hint; - if (!getHints) return; + if (!completion.options.hint) return; CodeMirror.signal(this, "startCompletion", this); - return retrieveHints(getHints, this, completion.options, function(hints) { completion.showHints(hints); }); + completion.update(true); }); function Completion(cm, options) { this.cm = cm; this.options = this.buildOptions(options); - this.widget = this.onClose = null; + this.widget = null; + this.debounce = 0; + this.tick = 0; + this.startPos = this.cm.getCursor(); + this.startLen = this.cm.getLine(this.startPos.line).length; + + var self = this; + cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); }); } + var requestAnimationFrame = window.requestAnimationFrame || function(fn) { + return setTimeout(fn, 1000/60); + }; + var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout; + Completion.prototype = { close: function() { if (!this.active()) return; this.cm.state.completionActive = null; + this.tick = null; + this.cm.off("cursorActivity", this.activityFunc); if (this.widget) this.widget.close(); - if (this.onClose) this.onClose(); CodeMirror.signal(this.cm, "endCompletion", this.cm); }, @@ -78,70 +78,46 @@ this.close(); }, - showHints: function(data) { - if (!data || !data.list.length || !this.active()) return this.close(); + cursorActivity: function() { + if (this.debounce) { + cancelAnimationFrame(this.debounce); + this.debounce = 0; + } - if (this.options.completeSingle && data.list.length == 1) - this.pick(data, 0); - else - this.showWidget(data); + var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line); + if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch || + pos.ch < this.startPos.ch || this.cm.somethingSelected() || + (pos.ch && this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) { + this.close(); + } else { + var self = this; + this.debounce = requestAnimationFrame(function() {self.update();}); + if (this.widget) this.widget.disable(); + } }, - showWidget: function(data) { - this.widget = new Widget(this, data); - CodeMirror.signal(data, "shown"); - - var debounce = 0, completion = this, finished; - var closeOn = this.options.closeCharacters; - var startPos = this.cm.getCursor(), startLen = this.cm.getLine(startPos.line).length; - - var requestAnimationFrame = window.requestAnimationFrame || function(fn) { - return setTimeout(fn, 1000/60); - }; - var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout; - - function done() { - if (finished) return; - finished = true; - completion.close(); - completion.cm.off("cursorActivity", activity); - if (data) CodeMirror.signal(data, "close"); + update: function(first) { + if (this.tick == null) return; + if (this.data) CodeMirror.signal(this.data, "update"); + if (!this.options.hint.async) { + this.finishUpdate(this.options.hint(this.cm, this.options), first); + } else { + var myTick = ++this.tick, self = this; + this.options.hint(this.cm, function(data) { + if (self.tick == myTick) self.finishUpdate(data, first); + }, this.options); } + }, - function update() { - if (finished) return; - CodeMirror.signal(data, "update"); - retrieveHints(completion.options.hint, completion.cm, completion.options, finishUpdate); - } - function finishUpdate(data_) { - data = data_; - if (finished) return; - if (!data || !data.list.length) return done(); - if (completion.widget) completion.widget.close(); - completion.widget = new Widget(completion, data); - } + finishUpdate: function(data, first) { + this.data = data; - function clearDebounce() { - if (debounce) { - cancelAnimationFrame(debounce); - debounce = 0; - } + var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle); + if (this.widget) this.widget.close(); + if (data && data.list.length) { + if (picked && data.list.length == 1) this.pick(data, 0); + else this.widget = new Widget(this, data); } - - function activity() { - clearDebounce(); - var pos = completion.cm.getCursor(), line = completion.cm.getLine(pos.line); - if (pos.line != startPos.line || line.length - pos.ch != startLen - startPos.ch || - pos.ch < startPos.ch || completion.cm.somethingSelected() || - (pos.ch && closeOn.test(line.charAt(pos.ch - 1)))) { - completion.close(); - } else { - debounce = requestAnimationFrame(update); - if (completion.widget) completion.widget.close(); - } - } - this.cm.on("cursorActivity", activity); - this.onClose = done; }, buildOptions: function(options) { @@ -206,6 +182,7 @@ function Widget(completion, data) { this.completion = completion; this.data = data; + this.picked = false; var widget = this, cm = completion.cm; var hints = this.hints = document.createElement("ul"); @@ -320,6 +297,13 @@ cm.off("scroll", this.onScroll); }, + disable: function() { + this.completion.cm.removeKeyMap(this.keyMap); + var widget = this; + this.keyMap = {Enter: function() { widget.picked = true; }}; + this.completion.cm.addKeyMap(this.keyMap); + }, + pick: function() { this.completion.pick(this.data, this.selectedHint); }, diff --git a/codemirror/addon/lint/lint.js b/codemirror/addon/lint/lint.js index 18eb7090..c7e09396 100644 --- a/codemirror/addon/lint/lint.js +++ b/codemirror/addon/lint/lint.js @@ -163,6 +163,7 @@ function onChange(cm) { var state = cm.state.lint; + if (!state) return; clearTimeout(state.timeout); state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay || 500); } @@ -188,6 +189,7 @@ clearMarks(cm); cm.off("change", onChange); CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver); + clearTimeout(cm.state.lint.timeout); delete cm.state.lint; } diff --git a/codemirror/addon/scroll/annotatescrollbar.js b/codemirror/addon/scroll/annotatescrollbar.js index 54aeacf2..e62a45ac 100644 --- a/codemirror/addon/scroll/annotatescrollbar.js +++ b/codemirror/addon/scroll/annotatescrollbar.js @@ -68,15 +68,30 @@ var cm = this.cm, hScale = this.hScale; var frag = document.createDocumentFragment(), anns = this.annotations; + + var wrapping = cm.getOption("lineWrapping"); + var singleLineH = wrapping && cm.defaultTextHeight() * 1.5; + var curLine = null, curLineObj = null; + function getY(pos, top) { + if (curLine != pos.line) { + curLine = pos.line; + curLineObj = cm.getLineHandle(curLine); + } + if (wrapping && curLineObj.height > singleLineH) + return cm.charCoords(pos, "local")[top ? "top" : "bottom"]; + var topY = cm.heightAtLine(curLineObj, "local"); + return topY + (top ? 0 : curLineObj.height); + } + if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) { var ann = anns[i]; - var top = nextTop || cm.charCoords(ann.from, "local").top * hScale; - var bottom = cm.charCoords(ann.to, "local").bottom * hScale; + var top = nextTop || getY(ann.from, true) * hScale; + var bottom = getY(ann.to, false) * hScale; while (i < anns.length - 1) { - nextTop = cm.charCoords(anns[i + 1].from, "local").top * hScale; + nextTop = getY(anns[i + 1].from, true) * hScale; if (nextTop > bottom + .9) break; ann = anns[++i]; - bottom = cm.charCoords(ann.to, "local").bottom * hScale; + bottom = getY(ann.to, false) * hScale; } if (bottom == top) continue; var height = Math.max(bottom - top, 3); diff --git a/codemirror/addon/search/matchesonscrollbar.js b/codemirror/addon/search/matchesonscrollbar.js index dbd67a4a..8d192289 100644 --- a/codemirror/addon/search/matchesonscrollbar.js +++ b/codemirror/addon/search/matchesonscrollbar.js @@ -19,6 +19,7 @@ function SearchAnnotation(cm, query, caseFold, options) { this.cm = cm; + this.options = options; var annotateOptions = {listenForChanges: false}; for (var prop in options) annotateOptions[prop] = options[prop]; if (!annotateOptions.className) annotateOptions.className = "CodeMirror-search-match"; @@ -46,11 +47,12 @@ if (match.to.line >= this.gap.from) this.matches.splice(i--, 1); } var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), this.caseFold); + var maxMatches = this.options && this.options.maxMatches || MAX_MATCHES; while (cursor.findNext()) { var match = {from: cursor.from(), to: cursor.to()}; if (match.from.line >= this.gap.to) break; this.matches.splice(i++, 0, match); - if (this.matches.length > MAX_MATCHES) break; + if (this.matches.length > maxMatches) break; } this.gap = null; }; diff --git a/codemirror/addon/search/search.js b/codemirror/addon/search/search.js index 0251067a..761cb949 100644 --- a/codemirror/addon/search/search.js +++ b/codemirror/addon/search/search.js @@ -39,7 +39,7 @@ } function SearchState() { - this.posFrom = this.posTo = this.query = null; + this.posFrom = this.posTo = this.lastQuery = this.query = null; this.overlay = null; } function getSearchState(cm) { @@ -53,7 +53,7 @@ return cm.getSearchCursor(query, pos, queryCaseInsensitive(query)); } function dialog(cm, text, shortText, deflt, f) { - if (cm.openDialog) cm.openDialog(text, f, {value: deflt}); + if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true}); else f(prompt(shortText, deflt)); } function confirmDialog(cm, text, shortText, fs) { @@ -75,7 +75,8 @@ function doSearch(cm, rev) { var state = getSearchState(cm); if (state.query) return findNext(cm, rev); - dialog(cm, queryDialog, "Search for:", cm.getSelection(), function(query) { + var q = cm.getSelection() || state.lastQuery; + dialog(cm, queryDialog, "Search for:", q, function(query) { cm.operation(function() { if (!query || state.query) return; state.query = parseQuery(query); @@ -104,6 +105,7 @@ });} function clearSearch(cm) {cm.operation(function() { var state = getSearchState(cm); + state.lastQuery = state.query; if (!state.query) return; state.query = null; cm.removeOverlay(state.overlay); @@ -116,7 +118,8 @@ var doReplaceConfirm = "Replace? "; function replace(cm, all) { if (cm.getOption("readOnly")) return; - dialog(cm, replaceQueryDialog, "Replace:", cm.getSelection(), function(query) { + var query = cm.getSelection() || getSearchState(cm).lastQuery; + dialog(cm, replaceQueryDialog, "Replace:", query, function(query) { if (!query) return; query = parseQuery(query); dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) { diff --git a/codemirror/addon/search/searchcursor.js b/codemirror/addon/search/searchcursor.js index 55c108b5..97088bf1 100644 --- a/codemirror/addon/search/searchcursor.js +++ b/codemirror/addon/search/searchcursor.js @@ -148,10 +148,10 @@ from: function() {if (this.atOccurrence) return this.pos.from;}, to: function() {if (this.atOccurrence) return this.pos.to;}, - replace: function(newText) { + replace: function(newText, origin) { if (!this.atOccurrence) return; var lines = CodeMirror.splitLines(newText); - this.doc.replaceRange(lines, this.pos.from, this.pos.to); + this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin); this.pos.to = Pos(this.pos.from.line + lines.length - 1, lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0)); } diff --git a/codemirror/bower.json b/codemirror/bower.json index c59f2d9d..13e7e5b4 100644 --- a/codemirror/bower.json +++ b/codemirror/bower.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version":"5.0.0", + "version":"5.2.1", "main": ["lib/codemirror.js", "lib/codemirror.css"], "ignore": [ "**/.*", @@ -11,6 +11,8 @@ "doc", "test", "index.html", - "package.json" + "package.json", + "mode/*/*test.js", + "mode/*/*.html" ] } diff --git a/codemirror/index.html b/codemirror/index.html index 3813fda1..e2cca9f0 100644 --- a/codemirror/index.html +++ b/codemirror/index.html @@ -96,7 +96,7 @@
- Get the current version: 5.0.
+ Get the current version: 5.2.
You can see the code or
read the release notes.
There is a minification helper. @@ -119,7 +119,7 @@

Features

    -
  • Support for over 90 languages out of the box +
  • Support for over 100 languages out of the box
  • A powerful, composable language mode system
  • Autocompletion (XML)
  • Code folding diff --git a/codemirror/keymap/sublime.js b/codemirror/keymap/sublime.js index 45936c36..441456f5 100644 --- a/codemirror/keymap/sublime.js +++ b/codemirror/keymap/sublime.js @@ -409,6 +409,19 @@ map[cK + ctrl + "Backspace"] = "delLineLeft"; + cmds[map["Backspace"] = "smartBackspace"] = function(cm) { + if (cm.somethingSelected()) return CodeMirror.Pass; + + var cursor = cm.getCursor(); + var toStartOfLine = cm.getRange({line: cursor.line, ch: 0}, cursor); + var column = CodeMirror.countColumn(toStartOfLine, null, cm.getOption("tabSize")); + + if (toStartOfLine && !/\S/.test(toStartOfLine) && column % cm.getOption("indentUnit") == 0) + return cm.indentSelection("subtract"); + else + return CodeMirror.Pass; + }; + cmds[map[cK + ctrl + "K"] = "delLineRight"] = function(cm) { cm.operation(function() { var ranges = cm.listSelections(); diff --git a/codemirror/keymap/vim.js b/codemirror/keymap/vim.js index 682eb7a8..2ede2167 100644 --- a/codemirror/keymap/vim.js +++ b/codemirror/keymap/vim.js @@ -3,42 +3,16 @@ /** * Supported keybindings: + * Too many to list. Refer to defaultKeyMap below. * - * Motion: - * h, j, k, l - * gj, gk - * e, E, w, W, b, B, ge, gE - * f, F, t, T - * $, ^, 0, -, +, _ - * gg, G - * % - * ', ` - * - * Operator: - * d, y, c - * dd, yy, cc - * g~, g~g~ - * >, <, >>, << - * - * Operator-Motion: - * x, X, D, Y, C, ~ - * - * Action: - * a, i, s, A, I, S, o, O - * zz, z., z, zt, zb, z- - * J - * u, Ctrl-r - * m - * r - * - * Modes: - * ESC - leave insert mode, visual mode, and clear input state. - * Ctrl-[, Ctrl-c - same as ESC. + * Supported Ex commands: + * Refer to defaultExCommandMap below. * * Registers: unnamed, -, a-z, A-Z, 0-9 * (Does not respect the special case for number registers when delete * operator is made with these commands: %, (, ), , /, ?, n, N, {, } ) * TODO: Implement the remaining registers. + * * Marks: a-z, A-Z, and 0-9 * TODO: Implement the remaining special marks. They have more complex * behavior. @@ -57,6 +31,7 @@ * 6. Motion, operator, and action implementations * 7. Helper functions for the key handler, motions, operators, and actions * 8. Set up Vim to work as a keymap for CodeMirror. + * 9. Ex command implementations. */ (function(mod) { @@ -227,6 +202,34 @@ { keys: ':', type: 'ex' } ]; + /** + * Ex commands + * Care must be taken when adding to the default Ex command map. For any + * pair of commands that have a shared prefix, at least one of their + * shortNames must not match the prefix of the other command. + */ + var defaultExCommandMap = [ + { name: 'colorscheme', shortName: 'colo' }, + { name: 'map' }, + { name: 'imap', shortName: 'im' }, + { name: 'nmap', shortName: 'nm' }, + { name: 'vmap', shortName: 'vm' }, + { name: 'unmap' }, + { name: 'write', shortName: 'w' }, + { name: 'undo', shortName: 'u' }, + { name: 'redo', shortName: 'red' }, + { name: 'set', shortName: 'se' }, + { name: 'set', shortName: 'se' }, + { name: 'setlocal', shortName: 'setl' }, + { name: 'setglobal', shortName: 'setg' }, + { name: 'sort', shortName: 'sor' }, + { name: 'substitute', shortName: 's', possiblyAsync: true }, + { name: 'nohlsearch', shortName: 'noh' }, + { name: 'delmarks', shortName: 'delm' }, + { name: 'registers', shortName: 'reg', excludeFromCommandHistory: true }, + { name: 'global', shortName: 'g' } + ]; + var Pos = CodeMirror.Pos; var Vim = function() { @@ -336,7 +339,11 @@ } var numberRegex = /[\d]/; - var wordRegexp = [(/\w/), (/[^\w\s]/)], bigWordRegexp = [(/\S/)]; + var wordCharTest = [CodeMirror.isWordChar, function(ch) { + return ch && !CodeMirror.isWordChar(ch) && !/\s/.test(ch); + }], bigWordCharTest = [function(ch) { + return /\S/.test(ch); + }]; function makeKeyRange(start, size) { var keys = []; for (var i = start; i < start + size; i++) { @@ -378,18 +385,30 @@ } var options = {}; - function defineOption(name, defaultValue, type) { - if (defaultValue === undefined) { throw Error('defaultValue is required'); } + function defineOption(name, defaultValue, type, aliases, callback) { + if (defaultValue === undefined && !callback) { + throw Error('defaultValue is required unless callback is provided'); + } if (!type) { type = 'string'; } options[name] = { type: type, - defaultValue: defaultValue + defaultValue: defaultValue, + callback: callback }; - setOption(name, defaultValue); + if (aliases) { + for (var i = 0; i < aliases.length; i++) { + options[aliases[i]] = options[name]; + } + } + if (defaultValue) { + setOption(name, defaultValue); + } } - function setOption(name, value) { + function setOption(name, value, cm, cfg) { var option = options[name]; + cfg = cfg || {}; + var scope = cfg.scope; if (!option) { throw Error('Unknown option: ' + name); } @@ -401,17 +420,60 @@ value = true; } } - option.value = option.type == 'boolean' ? !!value : value; + if (option.callback) { + if (scope !== 'local') { + option.callback(value, undefined); + } + if (scope !== 'global' && cm) { + option.callback(value, cm); + } + } else { + if (scope !== 'local') { + option.value = option.type == 'boolean' ? !!value : value; + } + if (scope !== 'global' && cm) { + cm.state.vim.options[name] = {value: value}; + } + } } - function getOption(name) { + function getOption(name, cm, cfg) { var option = options[name]; + cfg = cfg || {}; + var scope = cfg.scope; if (!option) { throw Error('Unknown option: ' + name); } - return option.value; + if (option.callback) { + var local = cm && option.callback(undefined, cm); + if (scope !== 'global' && local !== undefined) { + return local; + } + if (scope !== 'local') { + return option.callback(); + } + return; + } else { + var local = (scope !== 'global') && (cm && cm.state.vim.options[name]); + return (local || (scope !== 'local') && option || {}).value; + } } + defineOption('filetype', undefined, 'string', ['ft'], function(name, cm) { + // Option is local. Do nothing for global. + if (cm === undefined) { + return; + } + // The 'filetype' option proxies to the CodeMirror 'mode' option. + if (name === undefined) { + var mode = cm.getOption('mode'); + return mode == 'null' ? '' : mode; + } else { + var mode = name == '' ? 'null' : name; + cm.setOption('mode', mode); + } + }); + var createCircularJumpList = function() { var size = 100; var pointer = -1; @@ -564,8 +626,9 @@ visualBlock: false, lastSelection: null, lastPastedText: null, - sel: { - } + sel: {}, + // Buffer-local/window-local values of vim options. + options: {} }; } return cm.state.vim; @@ -623,6 +686,8 @@ // Add user defined key bindings. exCommandDispatcher.map(lhs, rhs, ctx); }, + // TODO: Expose setOption and getOption as instance methods. Need to decide how to namespace + // them, or somehow make them work with the existing CodeMirror setOption/getOption API. setOption: setOption, getOption: getOption, defineOption: defineOption, @@ -1035,12 +1100,10 @@ break; case 'search': this.processSearch(cm, vim, command); - clearInputState(cm); break; case 'ex': case 'keyToEx': this.processEx(cm, vim, command); - clearInputState(cm); break; default: break; @@ -1133,6 +1196,7 @@ updateSearchQuery(cm, query, ignoreCase, smartCase); } catch (e) { showConfirm(cm, 'Invalid regex: ' + query); + clearInputState(cm); return; } commandDispatcher.processMotion(cm, vim, { @@ -1175,15 +1239,21 @@ } function onPromptKeyDown(e, query, close) { var keyName = CodeMirror.keyName(e); - if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') { + if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[' || + (keyName == 'Backspace' && query == '')) { vimGlobalState.searchHistoryController.pushInput(query); vimGlobalState.searchHistoryController.reset(); updateSearchQuery(cm, originalQuery); clearSearchHighlight(cm); cm.scrollTo(originalScrollPos.left, originalScrollPos.top); CodeMirror.e_stop(e); + clearInputState(cm); close(); cm.focus(); + } else if (keyName == 'Ctrl-U') { + // Ctrl-U clears input. + CodeMirror.e_stop(e); + close(''); } } switch (command.searchArgs.querySrc) { @@ -1244,10 +1314,12 @@ } function onPromptKeyDown(e, input, close) { var keyName = CodeMirror.keyName(e), up; - if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') { + if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[' || + (keyName == 'Backspace' && input == '')) { vimGlobalState.exCommandHistoryController.pushInput(input); vimGlobalState.exCommandHistoryController.reset(); CodeMirror.e_stop(e); + clearInputState(cm); close(); cm.focus(); } @@ -1255,6 +1327,10 @@ up = keyName == 'Up' ? true : false; input = vimGlobalState.exCommandHistoryController.nextMatch(input, up) || ''; close(input); + } else if (keyName == 'Ctrl-U') { + // Ctrl-U clears input. + CodeMirror.e_stop(e); + close(''); } else { if ( keyName != 'Left' && keyName != 'Right' && keyName != 'Ctrl' && keyName != 'Alt' && keyName != 'Shift') vimGlobalState.exCommandHistoryController.reset(); @@ -1284,8 +1360,8 @@ var registerName = inputState.registerName; var sel = vim.sel; // TODO: Make sure cm and vim selections are identical outside visual mode. - var origHead = copyCursor(vim.visualMode ? sel.head: cm.getCursor('head')); - var origAnchor = copyCursor(vim.visualMode ? sel.anchor : cm.getCursor('anchor')); + var origHead = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.head): cm.getCursor('head')); + var origAnchor = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.anchor) : cm.getCursor('anchor')); var oldHead = copyCursor(origHead); var oldAnchor = copyCursor(origAnchor); var newHead, newAnchor; @@ -1851,10 +1927,11 @@ var anchor = ranges[0].anchor, head = ranges[0].head; text = cm.getRange(anchor, head); - if (!isWhiteSpaceString(text)) { + var lastState = vim.lastEditInputState || {}; + if (lastState.motion == "moveByWords" && !isWhiteSpaceString(text)) { // Exclude trailing whitespace if the range is not all whitespace. var match = (/\s+$/).exec(text); - if (match) { + if (match && lastState.motionArgs && lastState.motionArgs.forward) { head = offsetCursor(head, 0, - match[0].length); text = text.slice(0, - match[0].length); } @@ -2628,9 +2705,6 @@ function lineLength(cm, lineNum) { return cm.getLine(lineNum).length; } - function reverse(s){ - return s.split('').reverse().join(''); - } function trim(s) { if (s.trim) { return s.trim(); @@ -2944,59 +3018,38 @@ // Seek to first word or non-whitespace character, depending on if // noSymbol is true. - var textAfterIdx = line.substring(idx); - var firstMatchedChar; - if (noSymbol) { - firstMatchedChar = textAfterIdx.search(/\w/); - } else { - firstMatchedChar = textAfterIdx.search(/\S/); + var test = noSymbol ? wordCharTest[0] : bigWordCharTest [0]; + while (!test(line.charAt(idx))) { + idx++; + if (idx >= line.length) { return null; } } - if (firstMatchedChar == -1) { - return null; - } - idx += firstMatchedChar; - textAfterIdx = line.substring(idx); - var textBeforeIdx = line.substring(0, idx); - var matchRegex; - // Greedy matchers for the "word" we are trying to expand. if (bigWord) { - matchRegex = /^\S+/; + test = bigWordCharTest[0]; } else { - if ((/\w/).test(line.charAt(idx))) { - matchRegex = /^\w+/; - } else { - matchRegex = /^[^\w\s]+/; + test = wordCharTest[0]; + if (!test(line.charAt(idx))) { + test = wordCharTest[1]; } } - var wordAfterRegex = matchRegex.exec(textAfterIdx); - var wordStart = idx; - var wordEnd = idx + wordAfterRegex[0].length; - // TODO: Find a better way to do this. It will be slow on very long lines. - var revTextBeforeIdx = reverse(textBeforeIdx); - var wordBeforeRegex = matchRegex.exec(revTextBeforeIdx); - if (wordBeforeRegex) { - wordStart -= wordBeforeRegex[0].length; - } + var end = idx, start = idx; + while (test(line.charAt(end)) && end < line.length) { end++; } + while (test(line.charAt(start)) && start >= 0) { start--; } + start++; if (inclusive) { - // If present, trim all whitespace after word. - // Otherwise, trim all whitespace before word. - var textAfterWordEnd = line.substring(wordEnd); - var whitespacesAfterWord = textAfterWordEnd.match(/^\s*/)[0].length; - if (whitespacesAfterWord > 0) { - wordEnd += whitespacesAfterWord; - } else { - var revTrim = revTextBeforeIdx.length - wordStart; - var textBeforeWordStart = revTextBeforeIdx.substring(revTrim); - var whitespacesBeforeWord = textBeforeWordStart.match(/^\s*/)[0].length; - wordStart -= whitespacesBeforeWord; + // If present, include all whitespace after word. + // Otherwise, include all whitespace before word, except indentation. + var wordEnd = end; + while (/\s/.test(line.charAt(end)) && end < line.length) { end++; } + if (wordEnd == end) { + var wordStart = start; + while (/\s/.test(line.charAt(start - 1)) && start > 0) { start--; } + if (!start) { start = wordStart; } } } - - return { start: Pos(cur.line, wordStart), - end: Pos(cur.line, wordEnd) }; + return { start: Pos(cur.line, start), end: Pos(cur.line, end) }; } function recordJumpPosition(cm, oldCur, newCur) { @@ -3154,7 +3207,7 @@ var pos = cur.ch; var line = cm.getLine(lineNum); var dir = forward ? 1 : -1; - var regexps = bigWord ? bigWordRegexp : wordRegexp; + var charTests = bigWord ? bigWordCharTest: wordCharTest; if (emptyLineIsWord && line == '') { lineNum += dir; @@ -3174,11 +3227,11 @@ // Find bounds of next word. while (pos != stop) { var foundWord = false; - for (var i = 0; i < regexps.length && !foundWord; ++i) { - if (regexps[i].test(line.charAt(pos))) { + for (var i = 0; i < charTests.length && !foundWord; ++i) { + if (charTests[i](line.charAt(pos))) { wordStart = pos; // Advance to end of word. - while (pos != stop && regexps[i].test(line.charAt(pos))) { + while (pos != stop && charTests[i](line.charAt(pos))) { pos += dir; } wordEnd = pos; @@ -3510,7 +3563,8 @@ function dialog(cm, template, shortText, onClose, options) { if (cm.openDialog) { cm.openDialog(template, onClose, { bottom: true, value: options.value, - onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp }); + onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp, + selectValueOnOpen: false}); } else { onClose(prompt(shortText, '')); @@ -3862,32 +3916,18 @@ return {top: from.line, bottom: to.line}; } - // Ex command handling - // Care must be taken when adding to the default Ex command map. For any - // pair of commands that have a shared prefix, at least one of their - // shortNames must not match the prefix of the other command. - var defaultExCommandMap = [ - { name: 'map' }, - { name: 'imap', shortName: 'im' }, - { name: 'nmap', shortName: 'nm' }, - { name: 'vmap', shortName: 'vm' }, - { name: 'unmap' }, - { name: 'write', shortName: 'w' }, - { name: 'undo', shortName: 'u' }, - { name: 'redo', shortName: 'red' }, - { name: 'set', shortName: 'set' }, - { name: 'sort', shortName: 'sor' }, - { name: 'substitute', shortName: 's', possiblyAsync: true }, - { name: 'nohlsearch', shortName: 'noh' }, - { name: 'delmarks', shortName: 'delm' }, - { name: 'registers', shortName: 'reg', excludeFromCommandHistory: true }, - { name: 'global', shortName: 'g' } - ]; var ExCommandDispatcher = function() { this.buildCommandMap_(); }; ExCommandDispatcher.prototype = { processCommand: function(cm, input, opt_params) { + var that = this; + cm.operation(function () { + cm.curOp.isVimOp = true; + that._processCommand(cm, input, opt_params); + }); + }, + _processCommand: function(cm, input, opt_params) { var vim = cm.state.vim; var commandHistoryRegister = vimGlobalState.registerController.getRegister(':'); var previousCommand = commandHistoryRegister.toString(); @@ -4100,6 +4140,13 @@ }; var exCommands = { + colorscheme: function(cm, params) { + if (!params.args || params.args.length < 1) { + showConfirm(cm, cm.getOption('theme')); + return; + } + cm.setOption('theme', params.args[0]); + }, map: function(cm, params, ctx) { var mapArgs = params.args; if (!mapArgs || mapArgs.length < 2) { @@ -4133,6 +4180,9 @@ }, set: function(cm, params) { var setArgs = params.args; + // Options passed through to the setOption/getOption calls. May be passed in by the + // local/global versions of the set command + var setCfg = params.setCfg || {}; if (!setArgs || setArgs.length < 1) { if (cm) { showConfirm(cm, 'Invalid mapping: ' + params.input); @@ -4156,24 +4206,35 @@ optionName = optionName.substring(2); value = false; } + var optionIsBoolean = options[optionName] && options[optionName].type == 'boolean'; if (optionIsBoolean && value == undefined) { // Calling set with a boolean option sets it to true. value = true; } - if (!optionIsBoolean && !value || forceGet) { - var oldValue = getOption(optionName); - // If no value is provided, then we assume this is a get. + // If no value is provided, then we assume this is a get. + if (!optionIsBoolean && value === undefined || forceGet) { + var oldValue = getOption(optionName, cm, setCfg); if (oldValue === true || oldValue === false) { showConfirm(cm, ' ' + (oldValue ? '' : 'no') + optionName); } else { showConfirm(cm, ' ' + optionName + '=' + oldValue); } } else { - setOption(optionName, value); + setOption(optionName, value, cm, setCfg); } }, - registers: function(cm,params) { + setlocal: function (cm, params) { + // setCfg is passed through to setOption + params.setCfg = {scope: 'local'}; + this.set(cm, params); + }, + setglobal: function (cm, params) { + // setCfg is passed through to setOption + params.setCfg = {scope: 'global'}; + this.set(cm, params); + }, + registers: function(cm, params) { var regArgs = params.args; var registers = vimGlobalState.registerController.registers; var regInfo = '----------Registers----------

    '; @@ -4795,7 +4856,7 @@ } function updateFakeCursor(cm) { var vim = cm.state.vim; - var from = copyCursor(vim.sel.head); + var from = clipCursorToContent(cm, copyCursor(vim.sel.head)); var to = offsetCursor(from, 0, 1); if (vim.fakeCursor) { vim.fakeCursor.clear(); @@ -4806,7 +4867,7 @@ var anchor = cm.getCursor('anchor'); var head = cm.getCursor('head'); // Enter or exit visual mode to match mouse selection. - if (vim.visualMode && cursorEqual(head, anchor) && lineLength(cm, head.line) > head.ch) { + if (vim.visualMode && !cm.somethingSelected()) { exitVisualMode(cm, false); } else if (!vim.visualMode && !vim.insertMode && cm.somethingSelected()) { vim.visualMode = true; diff --git a/codemirror/lib/codemirror.css b/codemirror/lib/codemirror.css index 1902ba4a..ceacd130 100644 --- a/codemirror/lib/codemirror.css +++ b/codemirror/lib/codemirror.css @@ -33,8 +33,7 @@ min-width: 20px; text-align: right; color: #999; - -moz-box-sizing: content-box; - box-sizing: content-box; + white-space: nowrap; } .CodeMirror-guttermarker { color: black; } @@ -127,6 +126,8 @@ div.CodeMirror-overwrite div.CodeMirror-cursor {} .cm-s-default .cm-error {color: #f00;} .cm-invalidchar {color: #f00;} +.CodeMirror-composing { border-bottom: 2px solid; } + /* Default styles for common addons */ div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} @@ -154,14 +155,10 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} height: 100%; outline: none; /* Prevent dragging from highlighting the element */ position: relative; - -moz-box-sizing: content-box; - box-sizing: content-box; } .CodeMirror-sizer { position: relative; border-right: 30px solid transparent; - -moz-box-sizing: content-box; - box-sizing: content-box; } /* The fake, visible scrollbars. Used to force redraw during scrolling @@ -196,8 +193,6 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} .CodeMirror-gutter { white-space: normal; height: 100%; - -moz-box-sizing: content-box; - box-sizing: content-box; display: inline-block; margin-bottom: -30px; /* Hack to make IE7 behave */ @@ -265,6 +260,16 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} outline: none; } +/* Force content-box sizing for the elements where we expect it */ +.CodeMirror-scroll, +.CodeMirror-sizer, +.CodeMirror-gutter, +.CodeMirror-gutters, +.CodeMirror-linenumber { + -moz-box-sizing: content-box; + box-sizing: content-box; +} + .CodeMirror-measure { position: absolute; width: 100%; diff --git a/codemirror/lib/codemirror.js b/codemirror/lib/codemirror.js index 53b35329..ba7ea118 100644 --- a/codemirror/lib/codemirror.js +++ b/codemirror/lib/codemirror.js @@ -82,12 +82,15 @@ keyMaps: [], // stores maps added by addKeyMap overlays: [], // highlighting overlays, as added by addOverlay modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info - overwrite: false, focused: false, + overwrite: false, + delayingBlurEvent: false, + focused: false, suppressEdits: false, // used to disable editing during key handlers when in readOnly mode pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll draggingText: false, highlight: new Delayed(), // stores highlight worker timeout - keySeq: null // Unfinished key sequence + keySeq: null, // Unfinished key sequence + specialChars: null }; var cm = this; @@ -591,7 +594,7 @@ "CodeMirror-linenumber CodeMirror-gutter-elt")); var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; display.lineGutter.style.width = ""; - display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding); + display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1; display.lineNumWidth = display.lineNumInnerWidth + padding; display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; display.lineGutter.style.width = display.lineNumWidth + "px"; @@ -1076,7 +1079,7 @@ // was made out of. var lastCopied = null; - function applyTextInput(cm, inserted, deleted, sel) { + function applyTextInput(cm, inserted, deleted, sel, origin) { var doc = cm.doc; cm.display.shift = false; if (!sel) sel = doc.sel; @@ -1102,7 +1105,7 @@ } var updateInput = cm.curOp.updateInput; var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines, - origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"}; + origin: origin || (cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input")}; makeChange(cm.doc, changeEvent); signalLater(cm, "inputRead", cm, changeEvent); // When an 'electric' character is inserted, immediately trigger a reindent @@ -1111,16 +1114,18 @@ (!i || sel.ranges[i - 1].head.line != range.head.line)) { var mode = cm.getModeAt(range.head); var end = changeEnd(changeEvent); + var indented = false; if (mode.electricChars) { for (var j = 0; j < mode.electricChars.length; j++) if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { - indentLine(cm, end.line, "smart"); + indented = indentLine(cm, end.line, "smart"); break; } } else if (mode.electricInput) { if (mode.electricInput.test(getLine(doc, end.line).text.slice(0, end.ch))) - indentLine(cm, end.line, "smart"); + indented = indentLine(cm, end.line, "smart"); } + if (indented) signalLater(cm, "electricInput", cm, end.line); } } ensureCursorVisible(cm); @@ -1164,6 +1169,7 @@ this.inaccurateSelection = false; // Used to work around IE issue with selection being forgotten when focus moves away from textarea this.hasSelection = false; + this.composing = null; }; function hiddenTextarea() { @@ -1228,6 +1234,8 @@ te.value = lastCopied.join("\n"); selectInput(te); } + } else if (!cm.options.lineWiseCopyCut) { + return; } else { var ranges = copyableRanges(cm); lastCopied = ranges.text; @@ -1254,6 +1262,21 @@ on(display.lineSpace, "selectstart", function(e) { if (!eventInWidget(display, e)) e_preventDefault(e); }); + + on(te, "compositionstart", function() { + var start = cm.getCursor("from"); + input.composing = { + start: start, + range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"}) + }; + }); + on(te, "compositionend", function() { + if (input.composing) { + input.poll(); + input.composing.range.clear(); + input.composing = null; + } + }); }, prepareSelection: function() { @@ -1381,19 +1404,29 @@ return false; } - if (text.charCodeAt(0) == 0x200b && cm.doc.sel == cm.display.selForContextMenu && !prevInput) - prevInput = "\u200b"; + if (cm.doc.sel == cm.display.selForContextMenu) { + var first = text.charCodeAt(0); + if (first == 0x200b && !prevInput) prevInput = "\u200b"; + if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo"); } + } // Find the part of the input that is actually new var same = 0, l = Math.min(prevInput.length, text.length); while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same; var self = this; runInOp(cm, function() { - applyTextInput(cm, text.slice(same), prevInput.length - same); + applyTextInput(cm, text.slice(same), prevInput.length - same, + null, self.composing ? "*compose" : null); // Don't leave long text in the textarea, since it makes further polling slow if (text.length > 1000 || text.indexOf("\n") > -1) input.value = self.prevInput = ""; else self.prevInput = text; + + if (self.composing) { + self.composing.range.clear(); + self.composing.range = cm.markText(self.composing.start, cm.getCursor("to"), + {className: "CodeMirror-composing"}); + } }); return true; }, @@ -1440,7 +1473,9 @@ function prepareSelectAllHack() { if (te.selectionStart != null) { var selected = cm.somethingSelected(); - var extval = te.value = "\u200b" + (selected ? te.value : ""); + var extval = "\u200b" + (selected ? te.value : ""); + te.value = "\u21da"; // Used to catch context-menu undo + te.value = extval; input.prevInput = selected ? "" : "\u200b"; te.selectionStart = 1; te.selectionEnd = extval.length; // Re-set this, in case some other handler touched the @@ -1458,7 +1493,8 @@ if (te.selectionStart != null) { if (!ie || (ie && ie_version < 9)) prepareSelectAllHack(); var i = 0, poll = function() { - if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0) + if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && + te.selectionEnd > 0 && input.prevInput == "\u200b") operation(cm, commands.selectAll)(cm); else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500); else display.input.reset(); @@ -1491,6 +1527,7 @@ this.cm = cm; this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null; this.polling = new Delayed(); + this.gracePeriod = false; } ContentEditableInput.prototype = copyObj({ @@ -1552,6 +1589,8 @@ if (cm.somethingSelected()) { lastCopied = cm.getSelections(); if (e.type == "cut") cm.replaceSelection("", null, "cut"); + } else if (!cm.options.lineWiseCopyCut) { + return; } else { var ranges = copyableRanges(cm); lastCopied = ranges.text; @@ -1625,10 +1664,21 @@ sel.removeAllRanges(); sel.addRange(rng); if (old && sel.anchorNode == null) sel.addRange(old); + else if (gecko) this.startGracePeriod(); } this.rememberSelection(); }, + startGracePeriod: function() { + var input = this; + clearTimeout(this.gracePeriod); + this.gracePeriod = setTimeout(function() { + input.gracePeriod = false; + if (input.selectionChanged()) + input.cm.operation(function() { input.cm.curOp.selectionChanged = true; }); + }, 20); + }, + showMultipleSelections: function(info) { removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors); removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection); @@ -1671,12 +1721,15 @@ this.polling.set(this.cm.options.pollInterval, poll); }, - pollSelection: function() { - if (this.composing) return; + selectionChanged: function() { + var sel = window.getSelection(); + return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || + sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset; + }, - var sel = window.getSelection(), cm = this.cm; - if (sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || - sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset) { + pollSelection: function() { + if (!this.composing && !this.gracePeriod && this.selectionChanged()) { + var sel = window.getSelection(), cm = this.cm; this.rememberSelection(); var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); var head = domToPos(cm, sel.focusNode, sel.focusOffset); @@ -2895,6 +2948,7 @@ updateMaxLine: false, // Set when the widest line needs to be determined anew scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet scrollToPos: null, // Used to scroll to a specific position + focus: false, id: ++nextOpId // Unique ID }; if (operationGroup) { @@ -3012,6 +3066,7 @@ if (cm.state.focused && op.updateInput) cm.display.input.reset(op.typing); + if (op.focus && op.focus == activeElt()) ensureFocus(op.cm); } function endOperation_finish(op) { @@ -3376,15 +3431,11 @@ // Prevent wrapper from ever scrolling on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); - function drag_(e) { - if (!signalDOMEvent(cm, e)) e_stop(e); - } - if (cm.options.dragDrop) { - on(d.scroller, "dragstart", function(e){onDragStart(cm, e);}); - on(d.scroller, "dragenter", drag_); - on(d.scroller, "dragover", drag_); - on(d.scroller, "drop", operation(cm, onDrop)); - } + d.dragFunctions = { + simple: function(e) {if (!signalDOMEvent(cm, e)) e_stop(e);}, + start: function(e){onDragStart(cm, e);}, + drop: operation(cm, onDrop) + }; var inp = d.input.getField(); on(inp, "keyup", function(e) { onKeyUp.call(cm, e); }); @@ -3394,6 +3445,18 @@ on(inp, "blur", bind(onBlur, cm)); } + function dragDropChanged(cm, value, old) { + var wasOn = old && old != CodeMirror.Init; + if (!value != !wasOn) { + var funcs = cm.display.dragFunctions; + var toggle = value ? on : off; + toggle(cm.display.scroller, "dragstart", funcs.start); + toggle(cm.display.scroller, "dragenter", funcs.simple); + toggle(cm.display.scroller, "dragover", funcs.simple); + toggle(cm.display.scroller, "drop", funcs.drop); + } + } + // Called when the window resizes function onResize(cm) { var d = cm.display; @@ -3475,6 +3538,7 @@ break; case 3: if (captureRightClick) onContextMenu(cm, e); + else delayBlurEvent(cm); break; } } @@ -3482,7 +3546,7 @@ var lastClick, lastDoubleClick; function leftButtonDown(cm, e, start) { if (ie) setTimeout(bind(ensureFocus, cm), 0); - else ensureFocus(cm); + else cm.curOp.focus = activeElt(); var now = +new Date, type; if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) { @@ -3507,7 +3571,7 @@ // Start a text drag. When it ends, see if any dragging actually // happen, and treat as a click if it didn't. function leftButtonStartDrag(cm, e, start, modifier) { - var display = cm.display; + var display = cm.display, startTime = +new Date; var dragEnd = operation(cm, function(e2) { if (webkit) display.scroller.draggable = false; cm.state.draggingText = false; @@ -3515,12 +3579,13 @@ off(display.scroller, "drop", dragEnd); if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { e_preventDefault(e2); - if (!modifier) + if (!modifier && +new Date - 200 < startTime) extendSelection(cm.doc, start); - display.input.focus(); - // Work around unexplainable focus problem in IE9 (#2127) - if (ie && ie_version == 9) + // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) + if (webkit || ie && ie_version == 9) setTimeout(function() {document.body.focus(); display.input.focus();}, 20); + else + display.input.focus(); } }); // Let the drag handler handle this. @@ -3546,6 +3611,7 @@ ourRange = new Range(start, start); } else { ourRange = doc.sel.primary(); + ourIndex = doc.sel.primIndex; } if (e.altKey) { @@ -3577,7 +3643,7 @@ ourIndex = ranges.length; setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), {scroll: false, origin: "*mouse"}); - } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single") { + } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single" && !e.shiftKey) { setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0)); startSel = doc.sel; } else { @@ -3640,7 +3706,7 @@ var cur = posFromMouse(cm, e, true, type == "rect"); if (!cur) return; if (cmp(cur, lastPos) != 0) { - ensureFocus(cm); + cm.curOp.focus = activeElt(); extendTo(cur); var visible = visibleLines(display, doc); if (cur.line >= visible.to || cur.line < visible.from) @@ -3743,7 +3809,7 @@ try { var text = e.dataTransfer.getData("Text"); if (text) { - if (cm.state.draggingText && !(mac ? e.metaKey : e.ctrlKey)) + if (cm.state.draggingText && !(mac ? e.altKey : e.ctrlKey)) var selected = cm.listSelections(); setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); if (selected) for (var i = 0; i < selected.length; ++i) @@ -3998,7 +4064,7 @@ var lastStoppedKey = null; function onKeyDown(e) { var cm = this; - ensureFocus(cm); + cm.curOp.focus = activeElt(); if (signalDOMEvent(cm, e)) return; // IE does strange things with escape. if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false; @@ -4050,7 +4116,19 @@ // FOCUS/BLUR EVENTS + function delayBlurEvent(cm) { + cm.state.delayingBlurEvent = true; + setTimeout(function() { + if (cm.state.delayingBlurEvent) { + cm.state.delayingBlurEvent = false; + onBlur(cm); + } + }, 100); + } + function onFocus(cm) { + if (cm.state.delayingBlurEvent) cm.state.delayingBlurEvent = false; + if (cm.options.readOnly == "nocursor") return; if (!cm.state.focused) { signal(cm, "focus", cm); @@ -4068,6 +4146,8 @@ restartBlink(cm); } function onBlur(cm) { + if (cm.state.delayingBlurEvent) return; + if (cm.state.focused) { signal(cm, "blur", cm); cm.state.focused = false; @@ -4572,6 +4652,8 @@ if (indentString != curSpaceString) { replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); + line.stateAfter = null; + return true; } else { // Ensure that, if the cursor was in the whitespace at the start // of the line, it is moved to the end of that space. @@ -4584,7 +4666,6 @@ } } } - line.stateAfter = null; } // Utility for applying a change to a line by handle or number, @@ -4824,7 +4905,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]]); @@ -4874,10 +4955,15 @@ return lineAtHeight(this.doc, height + this.display.viewOffset); }, heightAtLine: function(line, mode) { - var end = false, last = this.doc.first + this.doc.size - 1; - if (line < this.doc.first) line = this.doc.first; - else if (line > last) { line = last; end = true; } - var lineObj = getLine(this.doc, line); + var end = false, lineObj; + if (typeof line == "number") { + var last = this.doc.first + this.doc.size - 1; + if (line < this.doc.first) line = this.doc.first; + else if (line > last) { line = last; end = true; } + lineObj = getLine(this.doc, line); + } else { + lineObj = line; + } return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").top + (end ? this.doc.height - heightAtLine(lineObj) : 0); }, @@ -4906,12 +4992,6 @@ }); }), - addLineWidget: methodOp(function(handle, node, options) { - return addLineWidget(this, handle, node, options); - }), - - removeLineWidget: function(widget) { widget.clear(); }, - lineInfo: function(line) { if (typeof line == "number") { if (!isLine(this.doc, line)) return null; @@ -5186,10 +5266,10 @@ clearCaches(cm); regChange(cm); }, true); - option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val) { - cm.options.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); - cm.refresh(); - }, true); + option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val, old) { + cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); + if (old != CodeMirror.Init) cm.refresh(); + }); option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true); option("electricChars", true); option("inputStyle", mobile ? "contenteditable" : "textarea", function() { @@ -5235,6 +5315,7 @@ option("showCursorWhenSelecting", false, updateSelection, true); option("resetSelectionOnContextMenu", true); + option("lineWiseCopyCut", true); option("readOnly", false, function(cm, val) { if (val == "nocursor") { @@ -5247,7 +5328,7 @@ } }); option("disableInput", false, function(cm, val) {if (!val) cm.display.input.reset();}, true); - option("dragDrop", true); + option("dragDrop", true, dragDropChanged); option("cursorBlinkRate", 530); option("cursorScrollMargin", 0); @@ -6431,10 +6512,10 @@ // Line widgets are block elements displayed above or below a line. - var LineWidget = CodeMirror.LineWidget = function(cm, node, options) { + var LineWidget = CodeMirror.LineWidget = function(doc, node, options) { if (options) for (var opt in options) if (options.hasOwnProperty(opt)) this[opt] = options[opt]; - this.cm = cm; + this.doc = doc; this.node = node; }; eventMixin(LineWidget); @@ -6445,52 +6526,55 @@ } LineWidget.prototype.clear = function() { - var cm = this.cm, ws = this.line.widgets, line = this.line, no = lineNo(line); + var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line); if (no == null || !ws) return; for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); if (!ws.length) line.widgets = null; var height = widgetHeight(this); - runInOp(cm, function() { + updateLineHeight(line, Math.max(0, line.height - height)); + if (cm) runInOp(cm, function() { adjustScrollWhenAboveVisible(cm, line, -height); regLineChange(cm, no, "widget"); - updateLineHeight(line, Math.max(0, line.height - height)); }); }; LineWidget.prototype.changed = function() { - var oldH = this.height, cm = this.cm, line = this.line; + var oldH = this.height, cm = this.doc.cm, line = this.line; this.height = null; var diff = widgetHeight(this) - oldH; if (!diff) return; - runInOp(cm, function() { + updateLineHeight(line, line.height + diff); + if (cm) runInOp(cm, function() { cm.curOp.forceUpdate = true; adjustScrollWhenAboveVisible(cm, line, diff); - updateLineHeight(line, line.height + diff); }); }; function widgetHeight(widget) { if (widget.height != null) return widget.height; + var cm = widget.doc.cm; + if (!cm) return 0; if (!contains(document.body, widget.node)) { var parentStyle = "position: relative;"; if (widget.coverGutter) - parentStyle += "margin-left: -" + widget.cm.display.gutters.offsetWidth + "px;"; + parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;"; if (widget.noHScroll) - parentStyle += "width: " + widget.cm.display.wrapper.clientWidth + "px;"; - removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, parentStyle)); + parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;"; + removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)); } return widget.height = widget.node.offsetHeight; } - function addLineWidget(cm, handle, node, options) { - var widget = new LineWidget(cm, node, options); - if (widget.noHScroll) cm.display.alignWidgets = true; - changeLine(cm.doc, handle, "widget", function(line) { + function addLineWidget(doc, handle, node, options) { + var widget = new LineWidget(doc, node, options); + var cm = doc.cm; + if (cm && widget.noHScroll) cm.display.alignWidgets = true; + changeLine(doc, handle, "widget", function(line) { var widgets = line.widgets || (line.widgets = []); if (widget.insertAt == null) widgets.push(widget); else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); widget.line = line; - if (!lineIsHidden(cm.doc, line)) { - var aboveVisible = heightAtLine(line) < cm.doc.scrollTop; + if (cm && !lineIsHidden(doc, line)) { + var aboveVisible = heightAtLine(line) < doc.scrollTop; updateLineHeight(line, line.height + widgetHeight(widget)); if (aboveVisible) addToScrollPos(cm, null, widget.height); cm.curOp.forceUpdate = true; @@ -6710,7 +6794,9 @@ // is needed on Webkit to be able to get line-level bounding // rectangles for it (in measureChar). var content = elt("span", null, null, webkit ? "padding-right: .1px" : null); - var builder = {pre: elt("pre", [content]), content: content, col: 0, pos: 0, cm: cm}; + var builder = {pre: elt("pre", [content]), content: content, + col: 0, pos: 0, cm: cm, + splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")}; lineView.measure = {}; // Iterate over the logical lines that make up this visual line. @@ -6720,8 +6806,6 @@ builder.addToken = buildToken; // Optionally wire in some hacks into the token-rendering // algorithm, to deal with browser quirks. - if ((ie || webkit) && cm.getOption("lineWrapping")) - builder.addToken = buildTokenSplitSpaces(builder.addToken); if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line))) builder.addToken = buildTokenBadBidi(builder.addToken, order); builder.map = []; @@ -6770,10 +6854,11 @@ // the line map. Takes care to render special characters separately. function buildToken(builder, text, style, startStyle, endStyle, title, css) { if (!text) return; - var special = builder.cm.options.specialChars, mustWrap = false; + var displayText = builder.splitSpaces ? text.replace(/ {3,}/g, splitSpaces) : text; + var special = builder.cm.state.specialChars, mustWrap = false; if (!special.test(text)) { builder.col += text.length; - var content = document.createTextNode(text); + var content = document.createTextNode(displayText); builder.map.push(builder.pos, builder.pos + text.length, content); if (ie && ie_version < 9) mustWrap = true; builder.pos += text.length; @@ -6784,7 +6869,7 @@ var m = special.exec(text); var skipped = m ? m.index - pos : text.length - pos; if (skipped) { - var txt = document.createTextNode(text.slice(pos, pos + skipped)); + var txt = document.createTextNode(displayText.slice(pos, pos + skipped)); if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); else content.appendChild(txt); builder.map.push(builder.pos, builder.pos + skipped, txt); @@ -6821,22 +6906,17 @@ builder.content.appendChild(content); } - function buildTokenSplitSpaces(inner) { - function split(old) { - var out = " "; - for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0"; - out += " "; - return out; - } - return function(builder, text, style, startStyle, endStyle, title) { - inner(builder, text.replace(/ {3,}/g, split), style, startStyle, endStyle, title); - }; + function splitSpaces(old) { + var out = " "; + for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0"; + out += " "; + return out; } // Work around nonsense dimensions being reported for stretches of // right-to-left text. function buildTokenBadBidi(inner, order) { - return function(builder, text, style, startStyle, endStyle, title) { + return function(builder, text, style, startStyle, endStyle, title, css) { style = style ? style + " cm-force-border" : "cm-force-border"; var start = builder.pos, end = start + text.length; for (;;) { @@ -6845,8 +6925,8 @@ var part = order[i]; if (part.to > start && part.from <= start) break; } - if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title); - inner(builder, text.slice(0, part.to - start), style, startStyle, null, title); + if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title, css); + inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css); startStyle = null; text = text.slice(part.to - start); start = part.to; @@ -6888,8 +6968,13 @@ var foundBookmarks = []; for (var j = 0; j < spans.length; ++j) { var sp = spans[j], m = sp.marker; - if (sp.from <= pos && (sp.to == null || sp.to > pos)) { - if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; } + if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { + foundBookmarks.push(m); + } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) { + if (sp.to != null && sp.to != pos && nextChange > sp.to) { + nextChange = sp.to; + spanEndStyle = ""; + } if (m.className) spanStyle += " " + m.className; if (m.css) css = m.css; if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; @@ -6900,12 +6985,12 @@ } else if (sp.from > pos && nextChange > sp.from) { nextChange = sp.from; } - if (m.type == "bookmark" && sp.from == pos && m.widgetNode) foundBookmarks.push(m); } if (collapsed && (collapsed.from || 0) == pos) { buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, collapsed.marker, collapsed.from == null); if (collapsed.to == null) return; + if (collapsed.to == pos) collapsed = false; } if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j) buildCollapsedSpan(builder, 0, foundBookmarks[j]); @@ -7368,13 +7453,19 @@ }); }), + addLineWidget: docMethodOp(function(handle, node, options) { + return addLineWidget(this, handle, node, options); + }), + removeLineWidget: function(widget) { widget.clear(); }, + markText: function(from, to, options) { return markText(this, clipPos(this, from), clipPos(this, to), options, "range"); }, setBookmark: function(pos, options) { var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), insertLeft: options && options.insertLeft, - clearWhenEmpty: false, shared: options && options.shared}; + clearWhenEmpty: false, shared: options && options.shared, + handleMouseEvents: options && options.handleMouseEvents}; pos = clipPos(this, pos); return markText(this, pos, pos, realOpts, "bookmark"); }, @@ -8108,7 +8199,7 @@ return function(){return f.apply(null, args);}; } - var nonASCIISingleCaseWordChar = /[\u00df\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; + var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; var isWordCharBasic = CodeMirror.isWordChar = function(ch) { return /\w/.test(ch) || ch > "\x80" && (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); @@ -8630,6 +8721,8 @@ lst(order).to -= m[0].length; order.push(new BidiSpan(0, len - m[0].length, len)); } + if (order[0].level == 2) + order.unshift(new BidiSpan(1, order[0].to, order[0].to)); if (order[0].level != lst(order).level) order.push(new BidiSpan(order[0].level, len, len)); @@ -8639,7 +8732,7 @@ // THE END - CodeMirror.version = "5.0.0"; + CodeMirror.version = "5.2.1"; return CodeMirror; }); diff --git a/codemirror/mode/css/css.js b/codemirror/mode/css/css.js index 34355aaa..7fc9098f 100644 --- a/codemirror/mode/css/css.js +++ b/codemirror/mode/css/css.js @@ -239,6 +239,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { if (type == "{" || type == "}") return popAndPass(type, stream, state); if (type == ")") return popContext(state); if (type == "(") return pushContext(state, stream, "parens"); + if (type == "interpolation") return pushContext(state, stream, "interpolation"); if (type == "word") wordAsValue(stream); return "parens"; }; @@ -327,7 +328,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) { states.interpolation = function(type, stream, state) { if (type == "}") return popContext(state); if (type == "{" || type == ";") return popAndPass(type, stream, state); - if (type != "variable") override = "error"; + if (type == "word") override = "variable"; + else if (type != "variable") override = "error"; return "interpolation"; }; @@ -749,6 +751,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { } }, "@": function(stream) { + if (stream.eat("{")) return [null, "interpolation"]; if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/, false)) return false; stream.eatWhile(/[\w\\\-]/); if (stream.match(/^\s*:/, false)) diff --git a/codemirror/mode/css/less.html b/codemirror/mode/css/less.html index 6ccb721e..adf7427d 100644 --- a/codemirror/mode/css/less.html +++ b/codemirror/mode/css/less.html @@ -146,7 +146,7 @@ fieldset span button, fieldset span input[type="file"] { }); -

    The LESS mode is a sub-mode of the CSS mode (defined in css.js.

    +

    The LESS mode is a sub-mode of the CSS mode (defined in css.js).

    Parsing/Highlighting Tests: normal, verbose.

    diff --git a/codemirror/mode/css/less_test.js b/codemirror/mode/css/less_test.js index 2ba69984..7b77f584 100644 --- a/codemirror/mode/css/less_test.js +++ b/codemirror/mode/css/less_test.js @@ -48,4 +48,7 @@ " }", " }", "}"); + + + MT("interpolation", ".@{[variable foo]} { [property font-weight]: [atom bold]; }"); })(); diff --git a/codemirror/mode/css/scss.html b/codemirror/mode/css/scss.html index 21f20e0d..f8e4d373 100644 --- a/codemirror/mode/css/scss.html +++ b/codemirror/mode/css/scss.html @@ -150,7 +150,7 @@ code { }); -

    The SCSS mode is a sub-mode of the CSS mode (defined in css.js.

    +

    The SCSS mode is a sub-mode of the CSS mode (defined in css.js).

    Parsing/Highlighting Tests: normal, verbose.

    diff --git a/codemirror/mode/css/scss_test.js b/codemirror/mode/css/scss_test.js index 8dcea9e8..26c226a1 100644 --- a/codemirror/mode/css/scss_test.js +++ b/codemirror/mode/css/scss_test.js @@ -73,7 +73,7 @@ "[tag foo]#{[variable-2 $hello]} { [property color]:[atom #000]; }"); MT('interpolation_error', - "[tag foo]#{[error foo]} { [property color]:[atom #000]; }"); + "[tag foo]#{[variable foo]} { [property color]:[atom #000]; }"); MT("divide_operator", "[tag foo] { [property width]:[number 4] [operator /] [number 2] }"); diff --git a/codemirror/package.json b/codemirror/package.json index b4a9b53f..2ae2e6b7 100644 --- a/codemirror/package.json +++ b/codemirror/package.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version":"5.0.0", + "version":"5.2.1", "main": "lib/codemirror.js", "description": "In-browser code editing made bearable", "licenses": [{"type": "MIT", diff --git a/codemirror/theme/liquibyte.css b/codemirror/theme/liquibyte.css new file mode 100644 index 00000000..a6e070c6 --- /dev/null +++ b/codemirror/theme/liquibyte.css @@ -0,0 +1,95 @@ +.cm-s-liquibyte.CodeMirror { + background-color: #000; + color: #fff; + line-height: 1.2em; + font-size: 1em; +} +.CodeMirror-focused .cm-matchhighlight { + text-decoration: underline; + text-decoration-color: #0f0; + text-decoration-style: wavy; +} +.cm-trailingspace { + text-decoration: line-through; + text-decoration-color: #f00; + text-decoration-style: dotted; +} +.cm-tab { + text-decoration: line-through; + text-decoration-color: #404040; + text-decoration-style: dotted; +} +.cm-s-liquibyte .CodeMirror-gutters { background-color: #262626; border-right: 1px solid #505050; padding-right: 0.8em; } +.cm-s-liquibyte .CodeMirror-gutter-elt div{ font-size: 1.2em; } +.cm-s-liquibyte .CodeMirror-guttermarker { } +.cm-s-liquibyte .CodeMirror-guttermarker-subtle { } +.cm-s-liquibyte .CodeMirror-linenumber { color: #606060; padding-left: 0;} +.cm-s-liquibyte .CodeMirror-cursor { border-left: 1px solid #eee !important; } + +.cm-s-liquibyte span.cm-comment { color: #008000; } +.cm-s-liquibyte span.cm-def { color: #ffaf40; font-weight: bold; } +.cm-s-liquibyte span.cm-keyword { color: #c080ff; font-weight: bold; } +.cm-s-liquibyte span.cm-builtin { color: #ffaf40; font-weight: bold; } +.cm-s-liquibyte span.cm-variable { color: #5967ff; font-weight: bold; } +.cm-s-liquibyte span.cm-string { color: #ff8000; } +.cm-s-liquibyte span.cm-number { color: #0f0; font-weight: bold; } +.cm-s-liquibyte span.cm-atom { color: #bf3030; font-weight: bold; } + +.cm-s-liquibyte span.cm-variable-2 { color: #007f7f; font-weight: bold; } +.cm-s-liquibyte span.cm-variable-3 { color: #c080ff; font-weight: bold; } +.cm-s-liquibyte span.cm-property { color: #999; font-weight: bold; } +.cm-s-liquibyte span.cm-operator { color: #fff; } + +.cm-s-liquibyte span.cm-meta { color: #0f0; } +.cm-s-liquibyte span.cm-qualifier { color: #fff700; font-weight: bold; } +.cm-s-liquibyte span.cm-bracket { color: #cc7; } +.cm-s-liquibyte span.cm-tag { color: #ff0; font-weight: bold; } +.cm-s-liquibyte span.cm-attribute { color: #c080ff; font-weight: bold; } +.cm-s-liquibyte span.cm-error { color: #f00; } + +.cm-s-liquibyte .CodeMirror-selected { background-color: rgba(255, 0, 0, 0.25) !important; } + +.cm-s-liquibyte span.cm-compilation { background-color: rgba(255, 255, 255, 0.12); } + +.cm-s-liquibyte .CodeMirror-activeline-background {background-color: rgba(0, 255, 0, 0.15) !important;} + +/* Default styles for common addons */ +div.CodeMirror span.CodeMirror-matchingbracket { color: #0f0; font-weight: bold; } +div.CodeMirror span.CodeMirror-nonmatchingbracket { color: #f00; font-weight: bold; } +.CodeMirror-matchingtag { background-color: rgba(150, 255, 0, .3); } +/* Scrollbars */ +/* Simple */ +div.CodeMirror-simplescroll-horizontal div:hover, div.CodeMirror-simplescroll-vertical div:hover { + background-color: rgba(80, 80, 80, .7); +} +div.CodeMirror-simplescroll-horizontal div, div.CodeMirror-simplescroll-vertical div { + background-color: rgba(80, 80, 80, .3); + border: 1px solid #404040; + border-radius: 5px; +} +div.CodeMirror-simplescroll-vertical div { + border-top: 1px solid #404040; + border-bottom: 1px solid #404040; +} +div.CodeMirror-simplescroll-horizontal div { + border-left: 1px solid #404040; + border-right: 1px solid #404040; +} +div.CodeMirror-simplescroll-vertical { + background-color: #262626; +} +div.CodeMirror-simplescroll-horizontal { + background-color: #262626; + border-top: 1px solid #404040; +} +/* Overlay */ +div.CodeMirror-overlayscroll-horizontal div, div.CodeMirror-overlayscroll-vertical div { + background-color: #404040; + border-radius: 5px; +} +div.CodeMirror-overlayscroll-vertical div { + border: 1px solid #404040; +} +div.CodeMirror-overlayscroll-horizontal div { + border: 1px solid #404040; +} diff --git a/codemirror/theme/mdn-like.css b/codemirror/theme/mdn-like.css index 93293c01..9c73dc2f 100644 --- a/codemirror/theme/mdn-like.css +++ b/codemirror/theme/mdn-like.css @@ -13,7 +13,7 @@ .cm-s-mdn-like.CodeMirror ::-moz-selection { background: #cfc; } .cm-s-mdn-like .CodeMirror-gutters { background: #f8f8f8; border-left: 6px solid rgba(0,83,159,0.65); color: #333; } -.cm-s-mdn-like .CodeMirror-linenumber { color: #aaa; margin-left: 3px; } +.cm-s-mdn-like .CodeMirror-linenumber { color: #aaa; padding-left: 8px; } div.cm-s-mdn-like .CodeMirror-cursor { border-left: 2px solid #222; } .cm-s-mdn-like .cm-keyword { color: #6262FF; } diff --git a/codemirror/theme/monokai.css b/codemirror/theme/monokai.css index 6dfcc73c..d70bb86f 100644 --- a/codemirror/theme/monokai.css +++ b/codemirror/theme/monokai.css @@ -18,7 +18,7 @@ .cm-s-monokai span.cm-keyword {color: #f92672;} .cm-s-monokai span.cm-string {color: #e6db74;} -.cm-s-monokai span.cm-variable {color: #a6e22e;} +.cm-s-monokai span.cm-variable {color: #f8f8f2;} .cm-s-monokai span.cm-variable-2 {color: #9effff;} .cm-s-monokai span.cm-def {color: #fd971f;} .cm-s-monokai span.cm-bracket {color: #f8f8f2;} diff --git a/codemirror/theme/solarized.css b/codemirror/theme/solarized.css index 4a10b7c0..9db39515 100644 --- a/codemirror/theme/solarized.css +++ b/codemirror/theme/solarized.css @@ -53,7 +53,7 @@ http://ethanschoonover.com/solarized/img/solarized-palette.png .cm-s-solarized .cm-number { color: #d33682; } .cm-s-solarized .cm-def { color: #2aa198; } -.cm-s-solarized .cm-variable { color: #268bd2; } +.cm-s-solarized .cm-variable { color: #839496; } .cm-s-solarized .cm-variable-2 { color: #b58900; } .cm-s-solarized .cm-variable-3 { color: #6c71c4; }