Panel Demo
- - - -The panel
-addon allows you to display panels above or below an editor. Click the
-links in the previous paragraph to add panels to the editor.
diff --git a/AUTHORS b/AUTHORS index a4f2b94b79..b9bf72103c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -25,6 +25,7 @@ Alexandre Bique alexey-k Alex Piggott Aliaksei Chapyzhenka +Amin Shali Amsul amuntean Amy @@ -81,10 +82,13 @@ 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 @@ -106,6 +110,7 @@ Danny Yoo darealshinji Darius Roberts Dave Myers +David Barnett David Mignot David Pathakjee David Vázquez @@ -182,6 +187,7 @@ ilvalle Ingo Richter Irakli Gozalishvili Ivan Kurnosov +Ivoah Jacob Lee Jakob Miland Jakub Vrana @@ -213,6 +219,7 @@ John Connor John Lees-Miller John Snelson John Van Der Loo +Jonas Döbertin Jonathan Malmaud jongalloway Jon Malmaud @@ -223,6 +230,7 @@ Joshua Newman Josh Watzman jots jsoojeon +ju1ius Juan Benavides Romero Jucovschi Constantin Juho Vuori @@ -250,6 +258,8 @@ Leonid Khachaturov Leon Sorokin Leonya Khachaturov Liam Newman +Libo Cannici +LloydMilligan LM lochel Lorenzo Stoakes @@ -274,6 +284,7 @@ Marko Bonaci Martin Balek Martín Gaitán Martin Hasoň +Martin Hunt Mason Malone Mateusz Paprocki Mathias Bynens @@ -292,6 +303,7 @@ Max Xiantu mbarkhau Metatheos Micah Dubinko +Michael Grey Michael Lehenbauer Michael Zhou Mighty Guava @@ -308,6 +320,7 @@ misfo mloginov Moritz Schwörer mps +ms mtaran-google Narciso Jaramillo Nathan Williams @@ -319,6 +332,7 @@ nguillaumin Ng Zhi An Nicholas Bollweg Nicholas Bollweg (Nick) +Nick Kreeger Nick Small Niels van Groningen nightwing @@ -333,6 +347,7 @@ pablo Page Panupong Pasupat paris +Paris Patil Arpith Patrick Stoica Patrick Strawderman diff --git a/addon/display/panel.js b/addon/display/panel.js index 22c0453e8f..ba29484d6c 100644 --- a/addon/display/panel.js +++ b/addon/display/panel.js @@ -10,13 +10,31 @@ mod(CodeMirror); })(function(CodeMirror) { CodeMirror.defineExtension("addPanel", function(node, options) { + options = options || {}; + if (!this.state.panels) initPanels(this); var info = this.state.panels; - if (options && options.position == "bottom") - info.wrapper.appendChild(node); - else - info.wrapper.insertBefore(node, info.wrapper.firstChild); + var wrapper = info.wrapper; + var cmWrapper = this.getWrapperElement(); + + if (options.after instanceof Panel && !options.after.cleared) { + wrapper.insertBefore(node, options.before.node.nextSibling); + } else if (options.before instanceof Panel && !options.before.cleared) { + wrapper.insertBefore(node, options.before.node); + } else if (options.replace instanceof Panel && !options.replace.cleared) { + wrapper.insertBefore(node, options.replace.node); + options.replace.clear(); + } else if (options.position == "bottom") { + wrapper.appendChild(node); + } else if (options.position == "before-bottom") { + wrapper.insertBefore(node, cmWrapper.nextSibling); + } else if (options.position == "after-top") { + wrapper.insertBefore(node, cmWrapper); + } else { + wrapper.insertBefore(node, wrapper.firstChild); + } + var height = (options && options.height) || node.offsetHeight; this._setSize(null, info.heightLeft -= height); info.panels++; diff --git a/addon/edit/continuelist.js b/addon/edit/continuelist.js index ca8d26751a..86de49c538 100644 --- a/addon/edit/continuelist.js +++ b/addon/edit/continuelist.js @@ -19,23 +19,23 @@ if (cm.getOption("disableInput")) return CodeMirror.Pass; var ranges = cm.listSelections(), replacements = []; for (var i = 0; i < ranges.length; i++) { - var pos = ranges[i].head, match; + var pos = ranges[i].head; var eolState = cm.getStateAfter(pos.line); var inList = eolState.list !== false; - var inQuote = eolState.quote !== false; + var inQuote = eolState.quote !== 0; - if (!ranges[i].empty() || (!inList && !inQuote) || !(match = cm.getLine(pos.line).match(listRE))) { + var line = cm.getLine(pos.line), match = listRE.exec(line); + if (!ranges[i].empty() || (!inList && !inQuote) || !match) { cm.execCommand("newlineAndIndent"); return; } - if (cm.getLine(pos.line).match(emptyListRE)) { + if (emptyListRE.test(line)) { cm.replaceRange("", { line: pos.line, ch: 0 }, { line: pos.line, ch: pos.ch + 1 }); replacements[i] = "\n"; - } else { var indent = match[1], after = match[4]; var bullet = unorderedListRE.test(match[2]) || match[2].indexOf(">") >= 0 diff --git a/addon/hint/show-hint.js b/addon/hint/show-hint.js index f544619428..539181fdb7 100644 --- a/addon/hint/show-hint.js +++ b/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(); }); 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); }, @@ -87,61 +87,51 @@ this.showWidget(data); }, - 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"); + cursorActivity: function() { + if (this.debounce) { + cancelAnimationFrame(this.debounce); + this.debounce = 0; } - 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); + 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(); } + }, - function clearDebounce() { - if (debounce) { - cancelAnimationFrame(debounce); - debounce = 0; - } + update: function() { + 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), myTick); + } else { + var myTick = ++this.tick, self = this; + this.options.hint(this.cm, function(data) { + if (self.tick == myTick) self.finishUpdate(data); + }, this.options); } + }, - 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(); - } + finishUpdate: function(data) { + this.data = data; + var picked = this.widget && this.widget.picked; + 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); } - this.cm.on("cursorActivity", activity); - this.onClose = done; + }, + + showWidget: function(data) { + this.data = data; + this.widget = new Widget(this, data); + CodeMirror.signal(data, "shown"); }, buildOptions: function(options) { @@ -206,6 +196,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 +311,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/addon/lint/javascript-lint.js b/addon/lint/javascript-lint.js index 3d65ba695b..d4f2ae9a1f 100644 --- a/addon/lint/javascript-lint.js +++ b/addon/lint/javascript-lint.js @@ -23,7 +23,7 @@ function validator(text, options) { if (!window.JSHINT) return []; - JSHINT(text, options); + JSHINT(text, options, options.globals); var errors = JSHINT.data().errors, result = []; if (errors) parseErrors(errors, result); return result; diff --git a/addon/mode/multiplex.js b/addon/mode/multiplex.js index 6a95b323c1..fe48c7fbd5 100644 --- a/addon/mode/multiplex.js +++ b/addon/mode/multiplex.js @@ -14,12 +14,14 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { // Others should be {open, close, mode [, delimStyle] [, innerStyle]} objects var others = Array.prototype.slice.call(arguments, 1); - var n_others = others.length; - function indexOf(string, pattern, from) { - if (typeof pattern == "string") return string.indexOf(pattern, from); + function indexOf(string, pattern, from, returnEnd) { + if (typeof pattern == "string") { + var found = string.indexOf(pattern, from); + return returnEnd && found > -1 ? found + pattern.length : found; + } var m = pattern.exec(from ? string.slice(from) : string); - return m ? m.index + from : -1; + return m ? m.index + from + (returnEnd ? m[0].length : 0) : -1; } return { @@ -42,11 +44,11 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { token: function(stream, state) { if (!state.innerActive) { var cutOff = Infinity, oldContent = stream.string; - for (var i = 0; i < n_others; ++i) { + for (var i = 0; i < others.length; ++i) { var other = others[i]; var found = indexOf(oldContent, other.open, stream.pos); if (found == stream.pos) { - stream.match(other.open); + if (!other.parseDelimiters) stream.match(other.open); state.innerActive = other; state.inner = CodeMirror.startState(other.mode, outer.indent ? outer.indent(state.outer, "") : 0); return other.delimStyle; @@ -64,8 +66,8 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { state.innerActive = state.inner = null; return this.token(stream, state); } - var found = curInner.close ? indexOf(oldContent, curInner.close, stream.pos) : -1; - if (found == stream.pos) { + var found = curInner.close ? indexOf(oldContent, curInner.close, stream.pos, curInner.parseDelimiters) : -1; + if (found == stream.pos && !curInner.parseDelimiters) { stream.match(curInner.close); state.innerActive = state.inner = null; return curInner.delimStyle; @@ -74,6 +76,9 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { var innerToken = curInner.mode.token(stream, state.inner); if (found > -1) stream.string = oldContent; + if (found == stream.pos && curInner.parseDelimiters) + state.innerActive = state.inner = null; + if (curInner.innerStyle) { if (innerToken) innerToken = innerToken + ' ' + curInner.innerStyle; else innerToken = curInner.innerStyle; @@ -95,7 +100,7 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { mode.blankLine(state.innerActive ? state.inner : state.outer); } if (!state.innerActive) { - for (var i = 0; i < n_others; ++i) { + for (var i = 0; i < others.length; ++i) { var other = others[i]; if (other.open === "\n") { state.innerActive = other; diff --git a/addon/scroll/annotatescrollbar.js b/addon/scroll/annotatescrollbar.js index 54aeacf271..e62a45acf2 100644 --- a/addon/scroll/annotatescrollbar.js +++ b/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/addon/search/matchesonscrollbar.js b/addon/search/matchesonscrollbar.js index dbd67a4a5d..8d19228971 100644 --- a/addon/search/matchesonscrollbar.js +++ b/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/addon/search/search.js b/addon/search/search.js index 0251067a73..1c8ca3c245 100644 --- a/addon/search/search.js +++ b/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().lastQuery; + dialog(cm, replaceQueryDialog, "Replace:", query, function(query) { if (!query) return; query = parseQuery(query); dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) { diff --git a/addon/search/searchcursor.js b/addon/search/searchcursor.js index 55c108b5a3..97088bf1cf 100644 --- a/addon/search/searchcursor.js +++ b/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/bower.json b/bower.json index 334447e56d..84808c8bc3 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version":"5.1.0", + "version":"5.2.0", "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/demo/panel.html b/demo/panel.html index 7f4bbefca6..b3b0b7ca6b 100644 --- a/demo/panel.html +++ b/demo/panel.html @@ -7,17 +7,36 @@ + +
The panel
-addon allows you to display panels above or below an editor. Click the
-links in the previous paragraph to add panels to the editor.
Version: