Press ctrl-space to activate autocompletion. The
+completion uses
+the anyword-hint.js
+module, which simply looks at nearby words in the buffer and completes
+to those.
Demonstration of
+ using linked documents
+ to provide a split view on a document, and
+ using swapDoc
+ to use a single editor to display multiple documents.
On changes to the content of the above editor, a (crude) script
+tries to auto-detect the language used, and switches the editor to
+either JavaScript or Scheme mode based on that.
The emacs keybindings are enabled by
+including keymap/emacs.js and setting
+the keyMap option to "emacs". Because
+CodeMirror's internal API is quite different from Emacs, they are only
+a loose approximation of actual emacs bindings, though.
+
+
Also note that a lot of browsers disallow certain keys from being
+captured. For example, Chrome blocks both Ctrl-W and Ctrl-N, with the
+result that idiomatic use of Emacs keys will constantly close your tab
+or open a new window.
Demonstration of
+ the fullscreen
+ addon. Press F11 when cursor is in the editor to
+ toggle full screen editing. Esc can also be used
+ to exit full screen editing.
Demonstration of
+the hardwrap addon.
+The above editor has its change event hooked up to
+the wrapParagraphsInRange method, so that the paragraphs
+are reflown as you are typing.
The merge
+addon provides an interface for displaying and merging diffs,
+either two-way
+or three-way.
+The left (or center) pane is editable, and the differences with the
+other pane(s) are optionally shown live as you edit
+it. In the two-way configuration, there are also options to pad changed
+sections to align them, and to collapse unchanged
+stretches of text.
Demonstration of a multiplexing mode, which, at certain
+ boundary strings, switches to one or more inner modes. The out
+ (HTML) mode does not get fed the content of the <<
+ >> blocks. See
+ the manual and
+ the source for more
+ information.
Demonstration of a mode that parses HTML, highlighting
+ the Mustache templating
+ directives inside of it by using the code
+ in overlay.js. View
+ source to see the 15 lines of code needed to accomplish this.
The placeholder
+ plug-in adds an option placeholder that can be set to
+ make text appear in the editor when it is empty and not focused.
+ If the source textarea has a placeholder attribute,
+ it will automatically be inherited.
This demo does the same thing as
+ the HTML5 completion demo, but
+ loads its dependencies
+ with Require.js, rather than
+ explicitly. Press ctrl-space to activate
+ completion.
By setting an editor's height style
+to auto and giving
+the viewportMargin
+a value of Infinity, CodeMirror can be made to
+automatically resize to fit its content.
Running a CodeMirror mode outside of the editor.
+ The CodeMirror.runMode function, defined
+ in addon/runmode/runmode.js takes the following arguments:
If this is a function, it will be called for each token with
+ two arguments, the token's text and the token's style class (may
+ be null for unstyled tokens). If it is a DOM node,
+ the tokens will be converted to span elements as in
+ an editor, and inserted into the node
+ (through innerHTML).
The mode/simple
+addon allows CodeMirror modes to be specified using a relatively simple
+declarative format. This format is not as powerful as writing code
+directly against the mode
+interface, but is a lot easier to get started with, and
+sufficiently expressive for many simple language modes.
+
+
This interface is still in flux. It is unlikely to be scrapped or
+overhauled completely, so do start writing code against it, but
+details might change as it stabilizes, and you might have to tweak
+your code when upgrading.
+
+
Simple modes (loosely based on
+the Common
+JavaScript Syntax Highlighting Specification, which never took
+off), are state machines, where each state has a number of rules that
+match tokens. A rule describes a type of token that may occur in the
+current state, and possibly a transition to another state caused by
+that token.
+
+
The CodeMirror.defineSimpleMode(name, states) method
+takes a mode name and an object that describes the mode's states. The
+editor below shows an example of such a mode (and is itself
+highlighted by the mode shown in it).
+
+
+
+
Each state is an array of rules. A rule may have the following properties:
+
+
+
regex: string | RegExp
+
The regular expression that matches the token. May be a string
+ or a regex object. When a regex, the ignoreCase flag
+ will be taken into account when matching the token. This regex
+ should only capture groups when the token property is
+ an array.
+
token: string | null
+
An optional token style. Multiple styles can be specified by
+ separating them with dots or spaces. When the regex for
+ this rule captures groups, it must capture all of the
+ string (since JS provides no way to find out where a group matched),
+ and this property must hold an array of token styles that has one
+ style for each matched group.
+
sol: boolean
+
When true, this token will only match at the start of the line.
+ (The ^ regexp marker doesn't work as you'd expect in
+ this context because of limitations in JavaScript's RegExp
+ API.)
+
next: string
+
When a next property is present, the mode will
+ transfer to the state named by the property when the token is
+ encountered.
+
push: string
+
Like next, but instead replacing the current state
+ by the new state, the current state is kept on a stack, and can be
+ returned to with the pop directive.
+
pop: bool
+
When true, and there is another state on the state stack, will
+ cause the mode to pop that state off the stack and transition to
+ it.
+
mode: {spec, end, persistent}
+
Can be used to embed another mode inside a mode. When present,
+ must hold an object with a spec property that describes
+ the embedded mode, and an optional end end property
+ that specifies the regexp that will end the extent of the mode. When
+ a persistent property is set (and true), the nested
+ mode's state will be preserved between occurrences of the mode.
+
indent: bool
+
When true, this token changes the indentation to be one unit
+ more than the current line's indentation.
+
dedent: bool
+
When true, this token will pop one scope off the indentation
+ stack.
+
dedentIfLineStart: bool
+
If a token has its dedent property set, it will, by
+ default, cause lines where it appears at the start to be dedented.
+ Set this property to false to prevent that behavior.
+
+
+
The meta property of the states object is special, and
+will not be interpreted as a state. Instead, properties set on it will
+be set on the mode, which is useful for properties
+like lineComment,
+which sets the comment style for a mode. The simple mode addon also
+recognizes a few such properties:
+
+
+
dontIndentStates: array<string>
+
An array of states in which the mode's auto-indentation should
+ not take effect. Usually used for multi-line comment and string
+ states.
The simplescrollbars addon defines two
+styles of non-native scrollbars: "simple" and "overlay" (click to try), which can be passed to
+the scrollbarStyle option. These implement
+the scrollbar using DOM elements, allowing more control over
+its appearance.
+
+
+
diff --git a/www/code/codemirror-5.13.2/demo/spanaffectswrapping_shim.html b/www/code/codemirror-5.13.2/demo/spanaffectswrapping_shim.html
new file mode 100644
index 000000000..879d99b60
--- /dev/null
+++ b/www/code/codemirror-5.13.2/demo/spanaffectswrapping_shim.html
@@ -0,0 +1,85 @@
+
+
+CodeMirror: Automatically derive odd wrapping behavior for your browser
+
+
+
+
Automatically derive odd wrapping behavior for your browser
+
+
+
This is a hack to automatically derive
+ a spanAffectsWrapping regexp for a browser. See the
+ comments above that variable
+ in lib/codemirror.js
+ for some more details.
Note: The CodeMirror vim bindings do not have an
+active maintainer. That means that if you report bugs in it, they are
+likely to go unanswered. It also means that if you want to help, you
+are very welcome to look
+at the
+open issues and see which ones you can solve.
+
+
+
Key buffer:
+
+
The vim keybindings are enabled by including keymap/vim.js and setting the
+keyMap option to vim.
+
+
Features
+
+
+
All common motions and operators, including text objects
For the full list of key mappings and Ex commands, refer to the
+defaultKeymap and defaultExCommandMap at the
+top of keymap/vim.js.
+
+
Note that while the vim mode tries to emulate the most useful
+features of vim as faithfully as possible, it does not strive to
+become a complete vim implementation
This demo runs JSHint over the code
+in the editor (which is the script used on this page), and
+inserts line widgets to
+display the warnings that JSHint comes up with.
Press ctrl-space, or type a '<' character to
+ activate autocompletion. This demo defines a simple schema that
+ guides completion. The schema can be customized—see
+ the manual.
+
+
Development of the xml-hint addon was kindly
+ sponsored
+ by www.xperiment.mobi.
+
+
+
diff --git a/www/code/codemirror-5.13.2/doc/activebookmark.js b/www/code/codemirror-5.13.2/doc/activebookmark.js
new file mode 100644
index 000000000..407282d02
--- /dev/null
+++ b/www/code/codemirror-5.13.2/doc/activebookmark.js
@@ -0,0 +1,57 @@
+// Kludge in HTML5 tag recognition in IE8
+document.createElement("section");
+document.createElement("article");
+
+(function() {
+ if (!window.addEventListener) return;
+ var pending = false, prevVal = null;
+
+ function updateSoon() {
+ if (!pending) {
+ pending = true;
+ setTimeout(update, 250);
+ }
+ }
+
+ function update() {
+ pending = false;
+ var marks = document.getElementById("nav").getElementsByTagName("a"), found;
+ for (var i = 0; i < marks.length; ++i) {
+ var mark = marks[i], m;
+ if (mark.getAttribute("data-default")) {
+ if (found == null) found = i;
+ } else if (m = mark.href.match(/#(.*)/)) {
+ var ref = document.getElementById(m[1]);
+ if (ref && ref.getBoundingClientRect().top < 50)
+ found = i;
+ }
+ }
+ if (found != null && found != prevVal) {
+ prevVal = found;
+ var lis = document.getElementById("nav").getElementsByTagName("li");
+ for (var i = 0; i < lis.length; ++i) lis[i].className = "";
+ for (var i = 0; i < marks.length; ++i) {
+ if (found == i) {
+ marks[i].className = "active";
+ for (var n = marks[i]; n; n = n.parentNode)
+ if (n.nodeName == "LI") n.className = "active";
+ } else {
+ marks[i].className = "";
+ }
+ }
+ }
+ }
+
+ window.addEventListener("scroll", updateSoon);
+ window.addEventListener("load", updateSoon);
+ window.addEventListener("hashchange", function() {
+ setTimeout(function() {
+ var hash = document.location.hash, found = null, m;
+ var marks = document.getElementById("nav").getElementsByTagName("a");
+ for (var i = 0; i < marks.length; i++)
+ if ((m = marks[i].href.match(/(#.*)/)) && m[1] == hash) { found = i; break; }
+ if (found != null) for (var i = 0; i < marks.length; i++)
+ marks[i].className = i == found ? "active" : "";
+ }, 300);
+ });
+})();
diff --git a/www/code/codemirror-5.13.2/doc/compress.html b/www/code/codemirror-5.13.2/doc/compress.html
new file mode 100644
index 000000000..47d689429
--- /dev/null
+++ b/www/code/codemirror-5.13.2/doc/compress.html
@@ -0,0 +1,346 @@
+
+
+CodeMirror: Compression Helper
+
+
+
+
+
+
+
To optimize loading CodeMirror, especially when including a
+ bunch of different modes, it is recommended that you combine and
+ minify (and preferably also gzip) the scripts. This page makes
+ those first two steps very easy. Simply select the version and
+ scripts you need in the form below, and
+ click Compress to download the minified script
+ file.
(Re-) Implementing A Syntax-Highlighting Editor in JavaScript
+
+
+ Topic: JavaScript, code editor implementation
+ Author: Marijn Haverbeke
+ Date: March 2nd 2011 (updated November 13th 2011)
+
+
+
Caution: this text was written briefly after
+version 2 was initially written. It no longer (even including the
+update at the bottom) fully represents the current implementation. I'm
+leaving it here as a historic document. For more up-to-date
+information, look at the entries
+tagged cm-internals
+on my blog.
+
+
This is a followup to
+my Brutal Odyssey to the
+Dark Side of the DOM Tree story. That one describes the
+mind-bending process of implementing (what would become) CodeMirror 1.
+This one describes the internals of CodeMirror 2, a complete rewrite
+and rethink of the old code base. I wanted to give this piece another
+Hunter Thompson copycat subtitle, but somehow that would be out of
+place—the process this time around was one of straightforward
+engineering, requiring no serious mind-bending whatsoever.
+
+
So, what is wrong with CodeMirror 1? I'd estimate, by mailing list
+activity and general search-engine presence, that it has been
+integrated into about a thousand systems by now. The most prominent
+one, since a few weeks,
+being Google
+code's project hosting. It works, and it's being used widely.
+
+
Still, I did not start replacing it because I was bored. CodeMirror
+1 was heavily reliant on designMode
+or contentEditable (depending on the browser). Neither of
+these are well specified (HTML5 tries
+to specify
+their basics), and, more importantly, they tend to be one of the more
+obscure and buggy areas of browser functionality—CodeMirror, by using
+this functionality in a non-typical way, was constantly running up
+against browser bugs. WebKit wouldn't show an empty line at the end of
+the document, and in some releases would suddenly get unbearably slow.
+Firefox would show the cursor in the wrong place. Internet Explorer
+would insist on linkifying everything that looked like a URL or email
+address, a behaviour that can't be turned off. Some bugs I managed to
+work around (which was often a frustrating, painful process), others,
+such as the Firefox cursor placement, I gave up on, and had to tell
+user after user that they were known problems, but not something I
+could help.
+
+
Also, there is the fact that designMode (which seemed
+to be less buggy than contentEditable in Webkit and
+Firefox, and was thus used by CodeMirror 1 in those browsers) requires
+a frame. Frames are another tricky area. It takes some effort to
+prevent getting tripped up by domain restrictions, they don't
+initialize synchronously, behave strangely in response to the back
+button, and, on several browsers, can't be moved around the DOM
+without having them re-initialize. They did provide a very nice way to
+namespace the library, though—CodeMirror 1 could freely pollute the
+namespace inside the frame.
+
+
Finally, working with an editable document means working with
+selection in arbitrary DOM structures. Internet Explorer (8 and
+before) has an utterly different (and awkward) selection API than all
+of the other browsers, and even among the different implementations of
+document.selection, details about how exactly a selection
+is represented vary quite a bit. Add to that the fact that Opera's
+selection support tended to be very buggy until recently, and you can
+imagine why CodeMirror 1 contains 700 lines of selection-handling
+code.
+
+
And that brings us to the main issue with the CodeMirror 1
+code base: The proportion of browser-bug-workarounds to real
+application code was getting dangerously high. By building on top of a
+few dodgy features, I put the system in a vulnerable position—any
+incompatibility and bugginess in these features, I had to paper over
+with my own code. Not only did I have to do some serious stunt-work to
+get it to work on older browsers (as detailed in the
+previous story), things
+also kept breaking in newly released versions, requiring me to come up
+with new scary hacks in order to keep up. This was starting
+to lose its appeal.
+
+
+
General Approach
+
+
What CodeMirror 2 does is try to sidestep most of the hairy hacks
+that came up in version 1. I owe a lot to the
+ACE editor for inspiration on how to
+approach this.
+
+
I absolutely did not want to be completely reliant on key events to
+generate my input. Every JavaScript programmer knows that key event
+information is horrible and incomplete. Some people (most awesomely
+Mihai Bazon with Ymacs) have been able
+to build more or less functioning editors by directly reading key
+events, but it takes a lot of work (the kind of never-ending, fragile
+work I described earlier), and will never be able to properly support
+things like multi-keystoke international character
+input. [see below for caveat]
+
+
So what I do is focus a hidden textarea, and let the browser
+believe that the user is typing into that. What we show to the user is
+a DOM structure we built to represent his document. If this is updated
+quickly enough, and shows some kind of believable cursor, it feels
+like a real text-input control.
+
+
Another big win is that this DOM representation does not have to
+span the whole document. Some CodeMirror 1 users insisted that they
+needed to put a 30 thousand line XML document into CodeMirror. Putting
+all that into the DOM takes a while, especially since, for some
+reason, an editable DOM tree is slower than a normal one on most
+browsers. If we have full control over what we show, we must only
+ensure that the visible part of the document has been added, and can
+do the rest only when needed. (Fortunately, the onscroll
+event works almost the same on all browsers, and lends itself well to
+displaying things only as they are scrolled into view.)
+
+
+
Input
+
+
ACE uses its hidden textarea only as a text input shim, and does
+all cursor movement and things like text deletion itself by directly
+handling key events. CodeMirror's way is to let the browser do its
+thing as much as possible, and not, for example, define its own set of
+key bindings. One way to do this would have been to have the whole
+document inside the hidden textarea, and after each key event update
+the display DOM to reflect what's in that textarea.
+
+
That'd be simple, but it is not realistic. For even medium-sized
+document the editor would be constantly munging huge strings, and get
+terribly slow. What CodeMirror 2 does is put the current selection,
+along with an extra line on the top and on the bottom, into the
+textarea.
+
+
This means that the arrow keys (and their ctrl-variations), home,
+end, etcetera, do not have to be handled specially. We just read the
+cursor position in the textarea, and update our cursor to match it.
+Also, copy and paste work pretty much for free, and people get their
+native key bindings, without any special work on my part. For example,
+I have emacs key bindings configured for Chrome and Firefox. There is
+no way for a script to detect this. [no longer the case]
+
+
Of course, since only a small part of the document sits in the
+textarea, keys like page up and ctrl-end won't do the right thing.
+CodeMirror is catching those events and handling them itself.
+
+
+
Selection
+
+
Getting and setting the selection range of a textarea in modern
+browsers is trivial—you just use the selectionStart
+and selectionEnd properties. On IE you have to do some
+insane stuff with temporary ranges and compensating for the fact that
+moving the selection by a 'character' will treat \r\n as a single
+character, but even there it is possible to build functions that
+reliably set and get the selection range.
+
+
But consider this typical case: When I'm somewhere in my document,
+press shift, and press the up arrow, something gets selected. Then, if
+I, still holding shift, press the up arrow again, the top of my
+selection is adjusted. The selection remembers where its head
+and its anchor are, and moves the head when we shift-move.
+This is a generally accepted property of selections, and done right by
+every editing component built in the past twenty years.
+
+
But not something that the browser selection APIs expose.
+
+
Great. So when someone creates an 'upside-down' selection, the next
+time CodeMirror has to update the textarea, it'll re-create the
+selection as an 'upside-up' selection, with the anchor at the top, and
+the next cursor motion will behave in an unexpected way—our second
+up-arrow press in the example above will not do anything, since it is
+interpreted in exactly the same way as the first.
+
+
No problem. We'll just, ehm, detect that the selection is
+upside-down (you can tell by the way it was created), and then, when
+an upside-down selection is present, and a cursor-moving key is
+pressed in combination with shift, we quickly collapse the selection
+in the textarea to its start, allow the key to take effect, and then
+combine its new head with its old anchor to get the real
+selection.
+
+
In short, scary hacks could not be avoided entirely in CodeMirror
+2.
+
+
And, the observant reader might ask, how do you even know that a
+key combo is a cursor-moving combo, if you claim you support any
+native key bindings? Well, we don't, but we can learn. The editor
+keeps a set known cursor-movement combos (initialized to the
+predictable defaults), and updates this set when it observes that
+pressing a certain key had (only) the effect of moving the cursor.
+This, of course, doesn't work if the first time the key is used was
+for extending an inverted selection, but it works most of the
+time.
+
+
+
Intelligent Updating
+
+
One thing that always comes up when you have a complicated internal
+state that's reflected in some user-visible external representation
+(in this case, the displayed code and the textarea's content) is
+keeping the two in sync. The naive way is to just update the display
+every time you change your state, but this is not only error prone
+(you'll forget), it also easily leads to duplicate work on big,
+composite operations. Then you start passing around flags indicating
+whether the display should be updated in an attempt to be efficient
+again and, well, at that point you might as well give up completely.
+
+
I did go down that road, but then switched to a much simpler model:
+simply keep track of all the things that have been changed during an
+action, and then, only at the end, use this information to update the
+user-visible display.
+
+
CodeMirror uses a concept of operations, which start by
+calling a specific set-up function that clears the state and end by
+calling another function that reads this state and does the required
+updating. Most event handlers, and all the user-visible methods that
+change state are wrapped like this. There's a method
+called operation that accepts a function, and returns
+another function that wraps the given function as an operation.
+
+
It's trivial to extend this (as CodeMirror does) to detect nesting,
+and, when an operation is started inside an operation, simply
+increment the nesting count, and only do the updating when this count
+reaches zero again.
+
+
If we have a set of changed ranges and know the currently shown
+range, we can (with some awkward code to deal with the fact that
+changes can add and remove lines, so we're dealing with a changing
+coordinate system) construct a map of the ranges that were left
+intact. We can then compare this map with the part of the document
+that's currently visible (based on scroll offset and editor height) to
+determine whether something needs to be updated.
+
+
CodeMirror uses two update algorithms—a full refresh, where it just
+discards the whole part of the DOM that contains the edited text and
+rebuilds it, and a patch algorithm, where it uses the information
+about changed and intact ranges to update only the out-of-date parts
+of the DOM. When more than 30 percent (which is the current heuristic,
+might change) of the lines need to be updated, the full refresh is
+chosen (since it's faster to do than painstakingly finding and
+updating all the changed lines), in the other case it does the
+patching (so that, if you scroll a line or select another character,
+the whole screen doesn't have to be
+re-rendered). [the full-refresh
+algorithm was dropped, it wasn't really faster than the patching
+one]
+
+
All updating uses innerHTML rather than direct DOM
+manipulation, since that still seems to be by far the fastest way to
+build documents. There's a per-line function that combines the
+highlighting, marking, and
+selection info for that line into a snippet of HTML. The patch updater
+uses this to reset individual lines, the refresh updater builds an
+HTML chunk for the whole visible document at once, and then uses a
+single innerHTML update to do the refresh.
+
+
+
Parsers can be Simple
+
+
When I wrote CodeMirror 1, I
+thought interruptable
+parsers were a hugely scary and complicated thing, and I used a
+bunch of heavyweight abstractions to keep this supposed complexity
+under control: parsers
+were iterators
+that consumed input from another iterator, and used funny
+closure-resetting tricks to copy and resume themselves.
+
+
This made for a rather nice system, in that parsers formed strictly
+separate modules, and could be composed in predictable ways.
+Unfortunately, it was quite slow (stacking three or four iterators on
+top of each other), and extremely intimidating to people not used to a
+functional programming style.
+
+
With a few small changes, however, we can keep all those
+advantages, but simplify the API and make the whole thing less
+indirect and inefficient. CodeMirror
+2's mode API uses explicit state
+objects, and makes the parser/tokenizer a function that simply takes a
+state and a character stream abstraction, advances the stream one
+token, and returns the way the token should be styled. This state may
+be copied, optionally in a mode-defined way, in order to be able to
+continue a parse at a given point. Even someone who's never touched a
+lambda in his life can understand this approach. Additionally, far
+fewer objects are allocated in the course of parsing now.
+
+
The biggest speedup comes from the fact that the parsing no longer
+has to touch the DOM though. In CodeMirror 1, on an older browser, you
+could see the parser work its way through the document,
+managing some twenty lines in each 50-millisecond time slice it got. It
+was reading its input from the DOM, and updating the DOM as it went
+along, which any experienced JavaScript programmer will immediately
+spot as a recipe for slowness. In CodeMirror 2, the parser usually
+finishes the whole document in a single 100-millisecond time slice—it
+manages some 1500 lines during that time on Chrome. All it has to do
+is munge strings, so there is no real reason for it to be slow
+anymore.
+
+
+
What Gives?
+
+
Given all this, what can you expect from CodeMirror 2?
+
+
+
+
Small. the base library is
+some 45k when minified
+now, 17k when gzipped. It's smaller than
+its own logo.
+
+
Lightweight. CodeMirror 2 initializes very
+quickly, and does almost no work when it is not focused. This means
+you can treat it almost like a textarea, have multiple instances on a
+page without trouble.
+
+
Huge document support. Since highlighting is
+really fast, and no DOM structure is being built for non-visible
+content, you don't have to worry about locking up your browser when a
+user enters a megabyte-sized document.
+
+
Extended API. Some things kept coming up in the
+mailing list, such as marking pieces of text or lines, which were
+extremely hard to do with CodeMirror 1. The new version has proper
+support for these built in.
+
+
Tab support. Tabs inside editable documents were,
+for some reason, a no-go. At least six different people announced they
+were going to add tab support to CodeMirror 1, none survived (I mean,
+none delivered a working version). CodeMirror 2 no longer removes tabs
+from your document.
+
+
Sane styling.iframe nodes aren't
+really known for respecting document flow. Now that an editor instance
+is a plain div element, it is much easier to size it to
+fit the surrounding elements. You don't even have to make it scroll if
+you do not want to.
+
+
+
+
On the downside, a CodeMirror 2 instance is not a native
+editable component. Though it does its best to emulate such a
+component as much as possible, there is functionality that browsers
+just do not allow us to hook into. Doing select-all from the context
+menu, for example, is not currently detected by CodeMirror.
+
+
[Updates from November 13th 2011] Recently, I've made
+some changes to the codebase that cause some of the text above to no
+longer be current. I've left the text intact, but added markers at the
+passages that are now inaccurate. The new situation is described
+below.
+
+
+
Content Representation
+
+
The original implementation of CodeMirror 2 represented the
+document as a flat array of line objects. This worked well—splicing
+arrays will require the part of the array after the splice to be
+moved, but this is basically just a simple memmove of a
+bunch of pointers, so it is cheap even for huge documents.
+
+
However, I recently added line wrapping and code folding (line
+collapsing, basically). Once lines start taking up a non-constant
+amount of vertical space, looking up a line by vertical position
+(which is needed when someone clicks the document, and to determine
+the visible part of the document during scrolling) can only be done
+with a linear scan through the whole array, summing up line heights as
+you go. Seeing how I've been going out of my way to make big documents
+fast, this is not acceptable.
+
+
The new representation is based on a B-tree. The leaves of the tree
+contain arrays of line objects, with a fixed minimum and maximum size,
+and the non-leaf nodes simply hold arrays of child nodes. Each node
+stores both the amount of lines that live below them and the vertical
+space taken up by these lines. This allows the tree to be indexed both
+by line number and by vertical position, and all access has
+logarithmic complexity in relation to the document size.
+
+
I gave line objects and tree nodes parent pointers, to the node
+above them. When a line has to update its height, it can simply walk
+these pointers to the top of the tree, adding or subtracting the
+difference in height from each node it encounters. The parent pointers
+also make it cheaper (in complexity terms, the difference is probably
+tiny in normal-sized documents) to find the current line number when
+given a line object. In the old approach, the whole document array had
+to be searched. Now, we can just walk up the tree and count the sizes
+of the nodes coming before us at each level.
+
+
I chose B-trees, not regular binary trees, mostly because they
+allow for very fast bulk insertions and deletions. When there is a big
+change to a document, it typically involves adding, deleting, or
+replacing a chunk of subsequent lines. In a regular balanced tree, all
+these inserts or deletes would have to be done separately, which could
+be really expensive. In a B-tree, to insert a chunk, you just walk
+down the tree once to find where it should go, insert them all in one
+shot, and then break up the node if needed. This breaking up might
+involve breaking up nodes further up, but only requires a single pass
+back up the tree. For deletion, I'm somewhat lax in keeping things
+balanced—I just collapse nodes into a leaf when their child count goes
+below a given number. This means that there are some weird editing
+patterns that may result in a seriously unbalanced tree, but even such
+an unbalanced tree will perform well, unless you spend a day making
+strangely repeating edits to a really big document.
+
+
+
Keymaps
+
+
Above, I claimed that directly catching key
+events for things like cursor movement is impractical because it
+requires some browser-specific kludges. I then proceeded to explain
+some awful hacks that were needed to make it
+possible for the selection changes to be detected through the
+textarea. In fact, the second hack is about as bad as the first.
+
+
On top of that, in the presence of user-configurable tab sizes and
+collapsed and wrapped lines, lining up cursor movement in the textarea
+with what's visible on the screen becomes a nightmare. Thus, I've
+decided to move to a model where the textarea's selection is no longer
+depended on.
+
+
So I moved to a model where all cursor movement is handled by my
+own code. This adds support for a goal column, proper interaction of
+cursor movement with collapsed lines, and makes it possible for
+vertical movement to move through wrapped lines properly, instead of
+just treating them like non-wrapped lines.
+
+
The key event handlers now translate the key event into a string,
+something like Ctrl-Home or Shift-Cmd-R, and
+use that string to look up an action to perform. To make keybinding
+customizable, this lookup goes through
+a table, using a scheme that
+allows such tables to be chained together (for example, the default
+Mac bindings fall through to a table named 'emacsy', which defines
+basic Emacs-style bindings like Ctrl-F, and which is also
+used by the custom Emacs bindings).
+
+
A new
+option extraKeys
+allows ad-hoc keybindings to be defined in a much nicer way than what
+was possible with the
+old onKeyEvent
+callback. You simply provide an object mapping key identifiers to
+functions, instead of painstakingly looking at raw key events.
+
+
Built-in commands map to strings, rather than functions, for
+example "goLineUp" is the default action bound to the up
+arrow key. This allows new keymaps to refer to them without
+duplicating any code. New commands can be defined by assigning to
+the CodeMirror.commands object, which maps such commands
+to functions.
+
+
The hidden textarea now only holds the current selection, with no
+extra characters around it. This has a nice advantage: polling for
+input becomes much, much faster. If there's a big selection, this text
+does not have to be read from the textarea every time—when we poll,
+just noticing that something is still selected is enough to tell us
+that no new text was typed.
+
+
The reason that cheap polling is important is that many browsers do
+not fire useful events on IME (input method engine) input, which is
+the thing where people inputting a language like Japanese or Chinese
+use multiple keystrokes to create a character or sequence of
+characters. Most modern browsers fire input when the
+composing is finished, but many don't fire anything when the character
+is updated during composition. So we poll, whenever the
+editor is focused, to provide immediate updates of the display.
+ User manual and reference guide
+ version 5.13.2
+
+
+
CodeMirror is a code-editor component that can be embedded in
+ Web pages. The core library provides only the editor
+ component, no accompanying buttons, auto-completion, or other IDE
+ functionality. It does provide a rich API on top of which such
+ functionality can be straightforwardly implemented. See
+ the addons included in the distribution,
+ and the list
+ of externally hosted addons, for reusable
+ implementations of extra features.
+
+
CodeMirror works with language-specific modes. Modes are
+ JavaScript programs that help color (and optionally indent) text
+ written in a given language. The distribution comes with a number
+ of modes (see the mode/
+ directory), and it isn't hard to write new
+ ones for other languages.
+
+
+
+
Basic Usage
+
+
The easiest way to use CodeMirror is to simply load the script
+ and style sheet found under lib/ in the distribution,
+ plus a mode script from one of the mode/ directories.
+ (See the compression helper for an
+ easy way to combine scripts.) For example:
Having done this, an editor instance can be created like
+ this:
+
+
var myCodeMirror = CodeMirror(document.body);
+
+
The editor will be appended to the document body, will start
+ empty, and will use the mode that we loaded. To have more control
+ over the new editor, a configuration object can be passed
+ to CodeMirror as a second
+ argument:
This will initialize the editor with a piece of code already in
+ it, and explicitly tell it to use the JavaScript mode (which is
+ useful when multiple modes are loaded).
+ See below for a full discussion of the
+ configuration options that CodeMirror accepts.
+
+
In cases where you don't want to append the editor to an
+ element, and need more control over the way it is inserted, the
+ first argument to the CodeMirror function can also
+ be a function that, when given a DOM element, inserts it into the
+ document somewhere. This could be used to, for example, replace a
+ textarea with a real editor:
However, for this use case, which is a common way to use
+ CodeMirror, the library provides a much more powerful
+ shortcut:
+
+
var myCodeMirror = CodeMirror.fromTextArea(myTextArea);
+
+
This will, among other things, ensure that the textarea's value
+ is updated with the editor's contents when the form (if it is part
+ of a form) is submitted. See the API
+ reference for a full description of this method.
+
+
Module loaders
+
+
The files in the CodeMirror distribution contain shims for
+ loading them (and their dependencies) in AMD or CommonJS
+ environments. If the variables exports
+ and module exist and have type object, CommonJS-style
+ require will be used. If not, but there is a
+ function define with an amd property
+ present, AMD-style (RequireJS) will be used.
+
+
It is possible to
+ use Browserify or similar
+ tools to statically build modules using CodeMirror. Alternatively,
+ use RequireJS to dynamically
+ load dependencies at runtime. Both of these approaches have the
+ advantage that they don't use the global namespace and can, thus,
+ do things like load multiple versions of CodeMirror alongside each
+ other.
+
+
Here's a simple example of using RequireJS to load CodeMirror:
It will automatically load the modes that the mixed HTML mode
+ depends on (XML, JavaScript, and CSS). Do not use
+ RequireJS' paths option to configure the path to
+ CodeMirror, since it will break loading submodules through
+ relative paths. Use
+ the packages
+ configuration option instead, as in:
Both the CodeMirror
+ function and its fromTextArea method take as second
+ (optional) argument an object containing configuration options.
+ Any option not supplied like this will be taken
+ from CodeMirror.defaults, an
+ object containing the default options. You can update this object
+ to change the defaults on your page.
+
+
Options are not checked in any way, so setting bogus option
+ values is bound to lead to odd errors.
+
+
These are the supported options:
+
+
+
value: string|CodeMirror.Doc
+
The starting value of the editor. Can be a string, or
+ a document object.
+
+
mode: string|object
+
The mode to use. When not given, this will default to the
+ first mode that was loaded. It may be a string, which either
+ simply names the mode or is
+ a MIME type
+ associated with the mode. Alternatively, it may be an object
+ containing configuration options for the mode, with
+ a name property that names the mode (for
+ example {name: "javascript", json: true}). The demo
+ pages for each mode contain information about what configuration
+ parameters the mode supports. You can ask CodeMirror which modes
+ and MIME types have been defined by inspecting
+ the CodeMirror.modes
+ and CodeMirror.mimeModes objects. The first maps
+ mode names to their constructors, and the second maps MIME types
+ to mode specs.
+
+
lineSeparator: string|null
+
Explicitly set the line separator for the editor. By default
+ (value null), the document will be split on CRLFs
+ as well as lone CRs and LFs, and a single LF will be used as
+ line separator in all output (such
+ as getValue). When a
+ specific string is given, lines will only be split on that
+ string, and output will, by default, use that same
+ separator.
+
+
theme: string
+
The theme to style the editor with. You must make sure the
+ CSS file defining the corresponding .cm-s-[name]
+ styles is loaded (see
+ the theme directory in the
+ distribution). The default is "default", for which
+ colors are included in codemirror.css. It is
+ possible to use multiple theming classes at once—for
+ example "foo bar" will assign both
+ the cm-s-foo and the cm-s-bar classes
+ to the editor.
+
+
indentUnit: integer
+
How many spaces a block (whatever that means in the edited
+ language) should be indented. The default is 2.
+
+
smartIndent: boolean
+
Whether to use the context-sensitive indentation that the
+ mode provides (or just indent the same as the line before).
+ Defaults to true.
+
+
tabSize: integer
+
The width of a tab character. Defaults to 4.
+
+
indentWithTabs: boolean
+
Whether, when indenting, the first N*tabSize
+ spaces should be replaced by N tabs. Default is false.
+
+
electricChars: boolean
+
Configures whether the editor should re-indent the current
+ line when a character is typed that might change its proper
+ indentation (only works if the mode supports indentation).
+ Default is true.
+
+
specialChars: RegExp
+
A regular expression used to determine which characters
+ should be replaced by a
+ special placeholder.
+ Mostly useful for non-printing special characters. The default
+ is /[\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/.
+
specialCharPlaceholder: function(char) → Element
+
A function that, given a special character identified by
+ the specialChars
+ option, produces a DOM node that is used to represent the
+ character. By default, a red dot (•)
+ is shown, with a title tooltip to indicate the character code.
+
+
rtlMoveVisually: boolean
+
Determines whether horizontal cursor movement through
+ right-to-left (Arabic, Hebrew) text is visual (pressing the left
+ arrow moves the cursor left) or logical (pressing the left arrow
+ moves to the next lower index in the string, which is visually
+ right in right-to-left text). The default is false
+ on Windows, and true on other platforms.
+
+
keyMap: string
+
Configures the key map to use. The default
+ is "default", which is the only key map defined
+ in codemirror.js itself. Extra key maps are found in
+ the key map directory. See
+ the section on key maps for more
+ information.
+
+
extraKeys: object
+
Can be used to specify extra key bindings for the editor,
+ alongside the ones defined
+ by keyMap. Should be
+ either null, or a valid key map value.
+
+
lineWrapping: boolean
+
Whether CodeMirror should scroll or wrap for long lines.
+ Defaults to false (scroll).
+
+
lineNumbers: boolean
+
Whether to show line numbers to the left of the editor.
+
+
firstLineNumber: integer
+
At which number to start counting lines. Default is 1.
A function used to format line numbers. The function is
+ passed the line number, and should return a string that will be
+ shown in the gutter.
+
+
gutters: array<string>
+
Can be used to add extra gutters (beyond or instead of the
+ line number gutter). Should be an array of CSS class names, each
+ of which defines a width (and optionally a
+ background), and which will be used to draw the background of
+ the gutters. May include
+ the CodeMirror-linenumbers class, in order to
+ explicitly set the position of the line number gutter (it will
+ default to be to the right of all other gutters). These class
+ names are the keys passed
+ to setGutterMarker.
+
+
fixedGutter: boolean
+
Determines whether the gutter scrolls along with the content
+ horizontally (false) or whether it stays fixed during horizontal
+ scrolling (true, the default).
+
+
scrollbarStyle: string
+
Chooses a scrollbar implementation. The default
+ is "native", showing native scrollbars. The core
+ library also provides the "null" style, which
+ completely hides the
+ scrollbars. Addons can
+ implement additional scrollbar models.
+
+
coverGutterNextToScrollbar: boolean
+
When fixedGutter
+ is on, and there is a horizontal scrollbar, by default the
+ gutter will be visible to the left of this scrollbar. If this
+ option is set to true, it will be covered by an element with
+ class CodeMirror-gutter-filler.
+
+
inputStyle: string
+
Selects the way CodeMirror handles input and focus. The core
+ library defines the "textarea"
+ and "contenteditable" input models. On mobile
+ browsers, the default is "contenteditable". On
+ desktop browsers, the default is "textarea".
+ Support for IME and screen readers is better in
+ the "contenteditable" model. The intention is to
+ make it the default on modern desktop browsers in the
+ future.
+
+
readOnly: boolean|string
+
This disables editing of the editor content by the user. If
+ the special value "nocursor" is given (instead of
+ simply true), focusing of the editor is also
+ disallowed.
+
+
showCursorWhenSelecting: boolean
+
Whether the cursor should be drawn when a selection is
+ active. Defaults to false.
+
+
lineWiseCopyCut: boolean
+
When enabled, which is the default, doing copy or cut when
+ there is no selection will copy or cut the whole lines that have
+ cursors on them.
+
+
undoDepth: integer
+
The maximum number of undo levels that the editor stores.
+ Note that this includes selection change events. Defaults to
+ 200.
+
+
historyEventDelay: integer
+
The period of inactivity (in milliseconds) that will cause a
+ new history event to be started when typing or deleting.
+ Defaults to 1250.
+
+
tabindex: integer
+
The tab
+ index to assign to the editor. If not given, no tab index
+ will be assigned.
+
+
autofocus: boolean
+
Can be used to make CodeMirror focus itself on
+ initialization. Defaults to off.
+ When fromTextArea is
+ used, and no explicit value is given for this option, it will be
+ set to true when either the source textarea is focused, or it
+ has an autofocus attribute and no other element is
+ focused.
+
+
+
Below this a few more specialized, low-level options are
+ listed. These are only useful in very specific situations, you
+ might want to skip them the first time you read this manual.
+
+
+
dragDrop: boolean
+
Controls whether drag-and-drop is enabled. On by default.
+
+
allowDropFileTypes: array<string>
+
When set (default is null) only files whose
+ type is in the array can be dropped into the editor. The strings
+ should be MIME types, and will be checked against
+ the type
+ of the File object as reported by the browser.
+
+
cursorBlinkRate: number
+
Half-period in milliseconds used for cursor blinking. The default blink
+ rate is 530ms. By setting this to zero, blinking can be disabled. A
+ negative value hides the cursor entirely.
+
+
cursorScrollMargin: number
+
How much extra space to always keep above and below the
+ cursor when approaching the top or bottom of the visible view in
+ a scrollable document. Default is 0.
+
+
cursorHeight: number
+
Determines the height of the cursor. Default is 1, meaning
+ it spans the whole height of the line. For some fonts (and by
+ some tastes) a smaller height (for example 0.85),
+ which causes the cursor to not reach all the way to the bottom
+ of the line, looks better
+
+
resetSelectionOnContextMenu: boolean
+
Controls whether, when the context menu is opened with a
+ click outside of the current selection, the cursor is moved to
+ the point of the click. Defaults to true.
+
+
workTime, workDelay: number
+
Highlighting is done by a pseudo background-thread that will
+ work for workTime milliseconds, and then use
+ timeout to sleep for workDelay milliseconds. The
+ defaults are 200 and 300, you can change these options to make
+ the highlighting more or less aggressive.
+
+
pollInterval: number
+
Indicates how quickly CodeMirror should poll its input
+ textarea for changes (when focused). Most input is captured by
+ events, but some things, like IME input on some browsers, don't
+ generate events that allow CodeMirror to properly detect it.
+ Thus, it polls. Default is 100 milliseconds.
+
+
flattenSpans: boolean
+
By default, CodeMirror will combine adjacent tokens into a
+ single span if they have the same class. This will result in a
+ simpler DOM tree, and thus perform better. With some kinds of
+ styling (such as rounded corners), this will change the way the
+ document looks. You can set this option to false to disable this
+ behavior.
+
+
addModeClass: boolean
+
When enabled (off by default), an extra CSS class will be
+ added to each token, indicating the
+ (inner) mode that produced it, prefixed
+ with "cm-m-". For example, tokens from the XML mode
+ will get the cm-m-xml class.
+
+
maxHighlightLength: number
+
When highlighting long lines, in order to stay responsive,
+ the editor will give up and simply style the rest of the line as
+ plain text when it reaches a certain position. The default is
+ 10 000. You can set this to Infinity to turn off
+ this behavior.
+
+
viewportMargin: integer
+
Specifies the amount of lines that are rendered above and
+ below the part of the document that's currently scrolled into
+ view. This affects the amount of updates needed when scrolling,
+ and the amount of work that such an update does. You should
+ usually leave it at its default, 10. Can be set
+ to Infinity to make sure the whole document is
+ always rendered, and thus the browser's text search works on it.
+ This will have bad effects on performance of big
+ documents.
+
+
+
+
+
Events
+
+
Various CodeMirror-related objects emit events, which allow
+ client code to react to various situations. Handlers for such
+ events can be registered with the on
+ and off methods on the objects
+ that the event fires on. To fire your own events,
+ use CodeMirror.signal(target, name, args...),
+ where target is a non-DOM-node object.
+
+
An editor instance fires the following events.
+ The instance argument always refers to the editor
+ itself.
Fires every time the content of the editor is changed.
+ The changeObj is a {from, to, text, removed,
+ origin} object containing information about the changes
+ that occurred as second argument. from
+ and to are the positions (in the pre-change
+ coordinate system) where the change started and ended (for
+ example, it might be {ch:0, line:18} if the
+ position is at the beginning of line #19). text is
+ an array of strings representing the text that replaced the
+ changed range (split by line). removed is the text
+ that used to be between from and to,
+ which is overwritten by this change. This event is
+ fired before the end of
+ an operation, before the DOM updates
+ happen.
Like the "change"
+ event, but batched per operation,
+ passing an array containing all the changes that happened in the
+ operation. This event is fired after the operation finished, and
+ display changes it makes will trigger a new operation.
This event is fired before a change is applied, and its
+ handler may choose to modify or cancel the change.
+ The changeObj object
+ has from, to, and text
+ properties, as with
+ the "change" event. It
+ also has a cancel() method, which can be called to
+ cancel the change, and, if the change isn't
+ coming from an undo or redo event, an update(from, to,
+ text) method, which may be used to modify the change.
+ Undo or redo changes can't be modified, because they hold some
+ metainformation for restoring old marked ranges that is only
+ valid for that specific change. All three arguments
+ to update are optional, and can be left off to
+ leave the existing value for that field
+ intact. Note: you may not do anything from
+ a "beforeChange" handler that would cause changes
+ to the document or its visualization. Doing so will, since this
+ handler is called directly from the bowels of the CodeMirror
+ implementation, probably cause the editor to become
+ corrupted.
+
+
"cursorActivity" (instance: CodeMirror)
+
Will be fired when the cursor or selection moves, or any
+ change is made to the editor content.
Fired after a key is handled through a
+ key map. name is the name of the handled key (for
+ example "Ctrl-X" or "'q'"),
+ and event is the DOM keydown
+ or keypress event.
This event is fired before the selection is moved. Its
+ handler may inspect the set of selection ranges, present as an
+ array of {anchor, head} objects in
+ the ranges property of the obj
+ argument, and optionally change them by calling
+ the update method on this object, passing an array
+ of ranges in the same format. The object also contains
+ an origin property holding the origin string passed
+ to the selection-changing method, if any. Handlers for this
+ event have the same restriction
+ as "beforeChange"
+ handlers — they should not do anything to directly update the
+ state of the editor.
+
+
"viewportChange" (instance: CodeMirror, from: number, to: number)
+
Fires whenever the view port of
+ the editor changes (due to scrolling, editing, or any other
+ factor). The from and to arguments
+ give the new start and end of the viewport.
+
+
"swapDoc" (instance: CodeMirror, oldDoc: Doc)
+
This is signalled when the editor's document is replaced
+ using the swapDoc
+ method.
Fires when the editor gutter (the line-number area) is
+ clicked. Will pass the editor instance as first argument, the
+ (zero-based) number of the line that was clicked as second
+ argument, the CSS class of the gutter that was clicked as third
+ argument, and the raw mousedown event object as
+ fourth argument.
Fires when the editor gutter (the line-number area)
+ receives a contextmenu event. Will pass the editor
+ instance as first argument, the (zero-based) number of the line
+ that was clicked as second argument, the CSS class of the
+ gutter that was clicked as third argument, and the raw
+ contextmenu mouse event object as fourth argument.
+ You can preventDefault the event, to signal that
+ CodeMirror should do no further handling.
Fires when the editor tries to scroll its cursor into view.
+ Can be hooked into to take care of additional scrollable
+ containers around the editor. When the event object has
+ its preventDefault method called, CodeMirror will
+ not itself try to scroll the window.
+
+
"update" (instance: CodeMirror)
+
Will be fired whenever CodeMirror updates its DOM display.
Fired whenever a line is (re-)rendered to the DOM. Fired
+ right after the DOM element is built, before it is
+ added to the document. The handler may mess with the style of
+ the resulting element, or add event handlers, but
+ should not try to change the state of the editor.
Fired when CodeMirror is handling a DOM event of this type.
+ You can preventDefault the event, or give it a
+ truthy codemirrorIgnore property, to signal that
+ CodeMirror should do no further handling.
+
+
+
Document objects (instances
+ of CodeMirror.Doc) emit the
+ following events:
+
+
+
"change" (doc: CodeMirror.Doc, changeObj: object)
+
Fired whenever a change occurs to the
+ document. changeObj has a similar type as the
+ object passed to the
+ editor's "change"
+ event.
Line handles (as returned by, for
+ example, getLineHandle)
+ support these events:
+
+
+
"delete" ()
+
Will be fired when the line object is deleted. A line object
+ is associated with the start of the line. Mostly useful
+ when you need to find out when your gutter
+ markers on a given line are removed.
+
"change" (line: LineHandle, changeObj: object)
+
Fires when the line's text content is changed in any way
+ (but the line is not deleted outright). The change
+ object is similar to the one passed
+ to change event on the editor
+ object.
+
+
+
Marked range handles (CodeMirror.TextMarker), as returned
+ by markText
+ and setBookmark, emit the
+ following events:
+
+
+
"beforeCursorEnter" ()
+
Fired when the cursor enters the marked range. From this
+ event handler, the editor state may be inspected
+ but not modified, with the exception that the range on
+ which the event fires may be cleared.
+
"clear" (from: {line, ch}, to: {line, ch})
+
Fired when the range is cleared, either through cursor
+ movement in combination
+ with clearOnEnter
+ or through a call to its clear() method. Will only
+ be fired once per handle. Note that deleting the range through
+ text editing does not fire this event, because an undo action
+ might bring the range back into existence. from
+ and to give the part of the document that the range
+ spanned when it was cleared.
+
"hide" ()
+
Fired when the last part of the marker is removed from the
+ document by editing operations.
+
"unhide" ()
+
Fired when, after the marker was removed by editing, a undo
+ operation brought the marker back.
+
+
+
Line widgets (CodeMirror.LineWidget), returned
+ by addLineWidget, fire
+ these events:
+
+
+
"redraw" ()
+
Fired whenever the editor re-adds the widget to the DOM.
+ This will happen once right after the widget is added (if it is
+ scrolled into view), and then again whenever it is scrolled out
+ of view and back in again, or when changes to the editor options
+ or the line the widget is on require the widget to be
+ redrawn.
+
+
+
+
+
Key Maps
+
+
Key maps are ways to associate keys with functionality. A key map
+ is an object mapping strings that identify the keys to functions
+ that implement their functionality.
+
+
The CodeMirror distributions comes
+ with Emacs, Vim,
+ and Sublime Text-style keymaps.
+
+
Keys are identified either by name or by character.
+ The CodeMirror.keyNames object defines names for
+ common keys and associates them with their key codes. Examples of
+ names defined here are Enter, F5,
+ and Q. These can be prefixed
+ with Shift-, Cmd-, Ctrl-,
+ and Alt- to specify a modifier. So for
+ example, Shift-Ctrl-Space would be a valid key
+ identifier.
+
+
Common example: map the Tab key to insert spaces instead of a tab
+ character.
Alternatively, a character can be specified directly by
+ surrounding it in single quotes, for example '$'
+ or 'q'. Due to limitations in the way browsers fire
+ key events, these may not be prefixed with modifiers.
+
+
Multi-stroke key bindings can be specified
+ by separating the key names by spaces in the property name, for
+ example Ctrl-X Ctrl-V. When a map contains
+ multi-stoke bindings or keys with modifiers that are not specified
+ in the default order (Shift-Cmd-Ctrl-Alt), you must
+ call CodeMirror.normalizeKeyMap on it before it can
+ be used. This function takes a keymap and modifies it to normalize
+ modifier order and properly recognize multi-stroke bindings. It
+ will return the keymap itself.
+
+
The CodeMirror.keyMap object associates key maps
+ with names. User code and key map definitions can assign extra
+ properties to this object. Anywhere where a key map is expected, a
+ string can be given, which will be looked up in this object. It
+ also contains the "default" key map holding the
+ default bindings.
+
+
The values of properties in key maps can be either functions of
+ a single argument (the CodeMirror instance), strings, or
+ false. Strings refer
+ to commands, which are described below. If
+ the property is set to false, CodeMirror leaves
+ handling of the key up to the browser. A key handler function may
+ return CodeMirror.Pass to indicate that it has
+ decided not to handle the key, and other handlers (or the default
+ behavior) should be given a turn.
+
+
Keys mapped to command names that start with the
+ characters "go" or to functions that have a
+ truthy motion property (which should be used for
+ cursor-movement actions) will be fired even when an
+ extra Shift modifier is present (i.e. "Up":
+ "goLineUp" matches both up and shift-up). This is used to
+ easily implement shift-selection.
+
+
Key maps can defer to each other by defining
+ a fallthrough property. This indicates that when a
+ key is not found in the map itself, one or more other maps should
+ be searched. It can hold either a single key map or an array of
+ key maps.
+
+
When a key map needs to set something up when it becomes
+ active, or tear something down when deactivated, it can
+ contain attach and/or detach properties,
+ which should hold functions that take the editor instance and the
+ next or previous keymap. Note that this only works for the
+ top-level keymap, not for fallthrough
+ maps or maps added
+ with extraKeys
+ or addKeyMap.
+
+
+
+
Commands
+
+
Commands are parameter-less actions that can be performed on an
+ editor. Their main use is for key bindings. Commands are defined by
+ adding properties to the CodeMirror.commands object.
+ A number of common commands are defined by the library itself,
+ most of them used by the default key bindings. The value of a
+ command property must be a function of one argument (an editor
+ instance).
+
+
Some of the commands below are referenced in the default
+ key map, but not defined by the core library. These are intended to
+ be defined by user code or addons.
+
+
Commands can also be run with
+ the execCommand
+ method.
+
+
+
selectAllCtrl-A (PC), Cmd-A (Mac)
+
Select the whole content of the editor.
+
+
singleSelectionEsc
+
When multiple selections are present, this deselects all but
+ the primary selection.
+
+
killLineCtrl-K (Mac)
+
Emacs-style line killing. Deletes the part of the line after
+ the cursor. If that consists only of whitespace, the newline at
+ the end of the line is also deleted.
+
+
deleteLineCtrl-D (PC), Cmd-D (Mac)
+
Deletes the whole line under the cursor, including newline at the end.
+
+
delLineLeft
+
Delete the part of the line before the cursor.
+
+
delWrappedLineLeftCmd-Backspace (Mac)
+
Delete the part of the line from the left side of the visual line the cursor is on to the cursor.
+
+
delWrappedLineRightCmd-Delete (Mac)
+
Delete the part of the line from the cursor to the right side of the visual line the cursor is on.
+
+
undoCtrl-Z (PC), Cmd-Z (Mac)
+
Undo the last change.
+
+
redoCtrl-Y (PC), Shift-Cmd-Z (Mac), Cmd-Y (Mac)
+
Redo the last undone change.
+
+
undoSelectionCtrl-U (PC), Cmd-U (Mac)
+
Undo the last change to the selection, or if there are no
+ selection-only changes at the top of the history, undo the last
+ change.
+
+
redoSelectionAlt-U (PC), Shift-Cmd-U (Mac)
+
Redo the last change to the selection, or the last text change if
+ no selection changes remain.
Move to the start of the text on the line, or if we are
+ already there, to the actual start of the line (including
+ whitespace).
+
+
goLineEndAlt-Right (PC), Ctrl-E (Mac)
+
Move the cursor to the end of the line.
+
+
goLineRightCmd-Right (Mac)
+
Move the cursor to the right side of the visual line it is on.
+
+
goLineLeftCmd-Left (Mac)
+
Move the cursor to the left side of the visual line it is on. If
+ this line is wrapped, that may not be the start of the line.
+
+
goLineLeftSmart
+
Move the cursor to the left side of the visual line it is
+ on. If that takes it to the start of the line, behave
+ like goLineStartSmart.
+
+
goLineUpUp, Ctrl-P (Mac)
+
Move the cursor up one line.
+
+
goLineDownDown, Ctrl-N (Mac)
+
Move down one line.
+
+
goPageUpPageUp, Shift-Ctrl-V (Mac)
+
Move the cursor up one screen, and scroll up by the same distance.
+
+
goPageDownPageDown, Ctrl-V (Mac)
+
Move the cursor down one screen, and scroll down by the same distance.
+
+
goCharLeftLeft, Ctrl-B (Mac)
+
Move the cursor one character left, going to the previous line
+ when hitting the start of line.
+
+
goCharRightRight, Ctrl-F (Mac)
+
Move the cursor one character right, going to the next line
+ when hitting the end of line.
+
+
goColumnLeft
+
Move the cursor one character left, but don't cross line boundaries.
+
+
goColumnRight
+
Move the cursor one character right, don't cross line boundaries.
+
+
goWordLeftAlt-B (Mac)
+
Move the cursor to the start of the previous word.
+
+
goWordRightAlt-F (Mac)
+
Move the cursor to the end of the next word.
+
+
goGroupLeftCtrl-Left (PC), Alt-Left (Mac)
+
Move to the left of the group before the cursor. A group is
+ a stretch of word characters, a stretch of punctuation
+ characters, a newline, or a stretch of more than one
+ whitespace character.
+
+
goGroupRightCtrl-Right (PC), Alt-Right (Mac)
+
Move to the right of the group after the cursor
+ (see above).
+
+
delCharBeforeShift-Backspace, Ctrl-H (Mac)
+
Delete the character before the cursor.
+
+
delCharAfterDelete, Ctrl-D (Mac)
+
Delete the character after the cursor.
+
+
delWordBeforeAlt-Backspace (Mac)
+
Delete up to the start of the word before the cursor.
+
+
delWordAfterAlt-D (Mac)
+
Delete up to the end of the word after the cursor.
Not defined by the core library, but defined in
+ the search addon (or custom client
+ addons).
+
+
+
+
+
+
+
Customized Styling
+
+
Up to a certain extent, CodeMirror's look can be changed by
+ modifying style sheet files. The style sheets supplied by modes
+ simply provide the colors for that mode, and can be adapted in a
+ very straightforward way. To style the editor itself, it is
+ possible to alter or override the styles defined
+ in codemirror.css.
+
+
Some care must be taken there, since a lot of the rules in this
+ file are necessary to have CodeMirror function properly. Adjusting
+ colors should be safe, of course, and with some care a lot of
+ other things can be changed as well. The CSS classes defined in
+ this file serve the following roles:
+
+
+
CodeMirror
+
The outer element of the editor. This should be used for the
+ editor width, height, borders and positioning. Can also be used
+ to set styles that should hold for everything inside the editor
+ (such as font and font size), or to set a background. Setting
+ this class' height style to auto will
+ make the editor resize to fit its
+ content (it is recommended to also set
+ the viewportMargin
+ option to Infinity when doing this.
+
+
CodeMirror-focused
+
Whenever the editor is focused, the top element gets this
+ class. This is used to hide the cursor and give the selection a
+ different color when the editor is not focused.
+
+
CodeMirror-gutters
+
This is the backdrop for all gutters. Use it to set the
+ default gutter background color, and optionally add a border on
+ the right of the gutters.
+
+
CodeMirror-linenumbers
+
Use this for giving a background or width to the line number
+ gutter.
+
+
CodeMirror-linenumber
+
Used to style the actual individual line numbers. These
+ won't be children of the CodeMirror-linenumbers
+ (plural) element, but rather will be absolutely positioned to
+ overlay it. Use this to set alignment and text properties for
+ the line numbers.
+
+
CodeMirror-lines
+
The visible lines. This is where you specify vertical
+ padding for the editor content.
+
+
CodeMirror-cursor
+
The cursor is a block element that is absolutely positioned.
+ You can make it look whichever way you want.
+
+
CodeMirror-selected
+
The selection is represented by span elements
+ with this class.
These are used to style matched (or unmatched) brackets.
+
+
+
If your page's style sheets do funky things to
+ all div or pre elements (you probably
+ shouldn't do that), you'll have to define rules to cancel these
+ effects out again for elements under the CodeMirror
+ class.
+
+
Themes are also simply CSS files, which define colors for
+ various syntactic elements. See the files in
+ the theme directory.
+
+
+
+
Programming API
+
+
A lot of CodeMirror features are only available through its
+ API. Thus, you need to write code (or
+ use addons) if you want to expose them to
+ your users.
+
+
Whenever points in the document are represented, the API uses
+ objects with line and ch properties.
+ Both are zero-based. CodeMirror makes sure to 'clip' any positions
+ passed by client code so that they fit inside the document, so you
+ shouldn't worry too much about sanitizing your coordinates. If you
+ give ch a value of null, or don't
+ specify it, it will be replaced with the length of the specified
+ line.
+
+
Methods prefixed with doc. can, unless otherwise
+ specified, be called both on CodeMirror (editor)
+ instances and CodeMirror.Doc instances. Methods
+ prefixed with cm. are only available
+ on CodeMirror instances.
+
+
Constructor
+
+
Constructing an editor instance is done with
+ the CodeMirror(place: Element|fn(Element),
+ ?option: object) constructor. If the place
+ argument is a DOM element, the editor will be appended to it. If
+ it is a function, it will be called, and is expected to place the
+ editor into the document. options may be an element
+ mapping option names to values. The options
+ that it doesn't explicitly specify (or all options, if it is not
+ passed) will be taken
+ from CodeMirror.defaults.
+
+
Note that the options object passed to the constructor will be
+ mutated when the instance's options
+ are changed, so you shouldn't share such
+ objects between instances.
Get the current editor content. You can pass it an optional
+ argument to specify the string to be used to separate lines
+ (defaults to "\n").
+
doc.setValue(content: string)
+
Set the editor content.
+
+
doc.getRange(from: {line, ch}, to: {line, ch}, ?separator: string) → string
+
Get the text between the given points in the editor, which
+ should be {line, ch} objects. An optional third
+ argument can be given to indicate the line separator string to
+ use (defaults to "\n").
+
doc.replaceRange(replacement: string, from: {line, ch}, to: {line, ch}, ?origin: string)
+
Replace the part of the document between from
+ and to with the given string. from
+ and to must be {line, ch}
+ objects. to can be left off to simply insert the
+ string at position from. When origin
+ is given, it will be passed on
+ to "change" events, and
+ its first letter will be used to determine whether this change
+ can be merged with previous history events, in the way described
+ for selection origins.
+
+
doc.getLine(n: integer) → string
+
Get the content of line n.
+
+
doc.lineCount() → integer
+
Get the number of lines in the editor.
+
doc.firstLine() → integer
+
Get the first line of the editor. This will
+ usually be zero but for linked sub-views,
+ or documents instantiated with a non-zero
+ first line, it might return other values.
+
doc.lastLine() → integer
+
Get the last line of the editor. This will
+ usually be doc.lineCount() - 1,
+ but for linked sub-views,
+ it might return other values.
+
+
doc.getLineHandle(num: integer) → LineHandle
+
Fetches the line handle for the given line number.
+
doc.getLineNumber(handle: LineHandle) → integer
+
Given a line handle, returns the current position of that
+ line (or null when it is no longer in the
+ document).
Iterate over the whole document, or if start
+ and end line numbers are given, the range
+ from start up to (not including) end,
+ and call f for each line, passing the line handle.
+ This is a faster way to visit a range of line handlers than
+ calling getLineHandle
+ for each of them. Note that line handles have
+ a text property containing the line's content (as a
+ string).
+
+
doc.markClean()
+
Set the editor content as 'clean', a flag that it will
+ retain until it is edited, and which will be set again when such
+ an edit is undone again. Useful to track whether the content
+ needs to be saved. This function is deprecated in favor
+ of changeGeneration,
+ which allows multiple subsystems to track different notions of
+ cleanness without interfering.
Returns a number that can later be passed
+ to isClean to test whether
+ any edits were made (and not undone) in the meantime.
+ If closeEvent is true, the current history event
+ will be ‘closed’, meaning it can't be combined with further
+ changes (rapid typing or deleting events are typically
+ combined).
+
doc.isClean(?generation: integer) → boolean
+
Returns whether the document is currently clean — not
+ modified since initialization or the last call
+ to markClean if no
+ argument is passed, or since the matching call
+ to changeGeneration
+ if a generation value is given.
+
+
+
Cursor and selection methods
+
+
+
doc.getSelection(?lineSep: string) → string
+
Get the currently selected code. Optionally pass a line
+ separator to put between the lines in the output. When multiple
+ selections are present, they are concatenated with instances
+ of lineSep in between.
+
doc.getSelections(?lineSep: string) → string
+
Returns an array containing a string for each selection,
+ representing the content of the selections.
Replace the selection(s) with the given string. By default,
+ the new selection ends up after the inserted text. The
+ optional select argument can be used to change
+ this—passing "around" will cause the new text to be
+ selected, passing "start" will collapse the
+ selection to the start of the inserted text.
The length of the given array should be the same as the
+ number of active selections. Replaces the content of the
+ selections with the strings in the array.
+ The select argument works the same as
+ in replaceSelection.
+
+
doc.getCursor(?start: string) → {line, ch}
+
Retrieve one end of the primary
+ selection. start is an optional string indicating
+ which end of the selection to return. It may
+ be "from", "to", "head"
+ (the side of the selection that moves when you press
+ shift+arrow), or "anchor" (the fixed side of the
+ selection). Omitting the argument is the same as
+ passing "head". A {line, ch} object
+ will be returned.
+
doc.listSelections() → array<{anchor, head}>
+
Retrieves a list of all current selections. These will
+ always be sorted, and never overlap (overlapping selections are
+ merged). Each object in the array contains anchor
+ and head properties referring to {line,
+ ch} objects.
Set the cursor position. You can either pass a
+ single {line, ch} object, or the line and the
+ character as two separate parameters. Will replace all
+ selections with a single, empty selection at the given position.
+ The supported options are the same as for setSelection.
Set a single selection range. anchor
+ and head should be {line, ch}
+ objects. head defaults to anchor when
+ not given. These options are supported:
+
+
scroll: boolean
+
Determines whether the selection head should be scrolled
+ into view. Defaults to true.
+
origin: string
+
Determines whether the selection history event may be
+ merged with the previous one. When an origin starts with the
+ character +, and the last recorded selection had
+ the same origin and was similar (close
+ in time, both
+ collapsed or both non-collapsed), the new one will replace the
+ old one. When it starts with *, it will always
+ replace the previous event (if that had the same origin).
+ Built-in motion uses the "+move" origin. User input uses the "+input" origin.
+
bias: number
+
Determine the direction into which the selection endpoints
+ should be adjusted when they fall inside
+ an atomic range. Can be either -1
+ (backward) or 1 (forward). When not given, the bias will be
+ based on the relative position of the old selection—the editor
+ will try to move further away from that, to prevent getting
+ stuck.
Sets a new set of selections. There must be at least one
+ selection in the given array. When primary is a
+ number, it determines which selection is the primary one. When
+ it is not given, the primary index is taken from the previous
+ selection, or set to the last range if the previous selection
+ had less ranges than the new one. Supports the same options
+ as setSelection.
Similar
+ to setSelection, but
+ will, if shift is held or
+ the extending flag is set, move the
+ head of the selection while leaving the anchor at its current
+ place. to is optional, and can be passed to ensure
+ a region (for example a word or paragraph) will end up selected
+ (in addition to whatever lies between that region and the
+ current anchor). When multiple selections are present, all but
+ the primary selection will be dropped by this method.
+ Supports the same options as setSelection.
Applies the given function to all existing selections, and
+ calls extendSelections
+ on the result.
+
doc.setExtending(value: boolean)
+
Sets or clears the 'extending' flag, which acts similar to
+ the shift key, in that it will cause cursor movement and calls
+ to extendSelection
+ to leave the selection anchor in place.
Used to find the target position for horizontal cursor
+ motion. start is a {line, ch}
+ object, amount an integer (may be negative),
+ and unit one of the
+ string "char", "column",
+ or "word". Will return a position that is produced
+ by moving amount times the distance specified
+ by unit. When visually is true, motion
+ in right-to-left text will be visual rather than logical. When
+ the motion was clipped by hitting the end or start of the
+ document, the returned value will have a hitSide
+ property set to true.
Similar to findPosH,
+ but used for vertical motion. unit may
+ be "line" or "page". The other
+ arguments and the returned value have the same interpretation as
+ they have in findPosH.
Returns the start and end of the 'word' (the stretch of
+ letters, whitespace, or punctuation) at the given position.
+
+
+
Configuration methods
+
+
+
cm.setOption(option: string, value: any)
+
Change the configuration of the editor. option
+ should the name of an option,
+ and value should be a valid value for that
+ option.
+
cm.getOption(option: string) → any
+
Retrieves the current value of the given option for this
+ editor instance.
+
+
cm.addKeyMap(map: object, bottom: boolean)
+
Attach an additional key map to the
+ editor. This is mostly useful for addons that need to register
+ some key handlers without trampling on
+ the extraKeys
+ option. Maps added in this way have a higher precedence than
+ the extraKeys
+ and keyMap options,
+ and between them, the maps added earlier have a lower precedence
+ than those added later, unless the bottom argument
+ was passed, in which case they end up below other key maps added
+ with this method.
+
cm.removeKeyMap(map: object)
+
Disable a keymap added
+ with addKeyMap. Either
+ pass in the key map object itself, or a string, which will be
+ compared against the name property of the active
+ key maps.
Enable a highlighting overlay. This is a stateless mini-mode
+ that can be used to add extra highlighting. For example,
+ the search addon uses it to
+ highlight the term that's currently being
+ searched. mode can be a mode
+ spec or a mode object (an object with
+ a token method).
+ The options parameter is optional. If given, it
+ should be an object. Currently, only the opaque
+ option is recognized. This defaults to off, but can be given to
+ allow the overlay styling, when not null, to
+ override the styling of the base mode entirely, instead of the
+ two being applied together.
+
cm.removeOverlay(mode: string|object)
+
Pass this the exact value passed for the mode
+ parameter to addOverlay,
+ or a string that corresponds to the name property of
+ that value, to remove an overlay again.
+
+
cm.on(type: string, func: (...args))
+
Register an event handler for the given event type (a
+ string) on the editor instance. There is also
+ a CodeMirror.on(object, type, func) version
+ that allows registering of events on any object.
+
cm.off(type: string, func: (...args))
+
Remove an event handler on the editor instance. An
+ equivalent CodeMirror.off(object, type,
+ func) also exists.
+
+
+
Document management methods
+
+
Each editor is associated with an instance
+ of CodeMirror.Doc, its document. A document
+ represents the editor content, plus a selection, an undo history,
+ and a mode. A document can only be
+ associated with a single editor at a time. You can create new
+ documents by calling the CodeMirror.Doc(text, mode,
+ firstLineNumber) constructor. The last two arguments are
+ optional and can be used to set a mode for the document and make
+ it start at a line number other than 0, respectively.
+
+
+
cm.getDoc() → Doc
+
Retrieve the currently active document from an editor.
+
doc.getEditor() → CodeMirror
+
Retrieve the editor associated with a document. May
+ return null.
+
+
cm.swapDoc(doc: CodeMirror.Doc) → Doc
+
Attach a new document to the editor. Returns the old
+ document, which is now no longer associated with an editor.
+
+
doc.copy(copyHistory: boolean) → Doc
+
Create an identical copy of the given doc.
+ When copyHistory is true, the history will also be
+ copied. Can not be called directly on an editor.
+
+
doc.linkedDoc(options: object) → Doc
+
Create a new document that's linked to the target document.
+ Linked documents will stay in sync (changes to one are also
+ applied to the other) until unlinked.
+ These are the options that are supported:
+
+
sharedHist: boolean
+
When turned on, the linked copy will share an undo
+ history with the original. Thus, something done in one of
+ the two can be undone in the other, and vice versa.
+
from: integer
+
to: integer
+
Can be given to make the new document a subview of the
+ original. Subviews only show a given range of lines. Note
+ that line coordinates inside the subview will be consistent
+ with those of the parent, so that for example a subview
+ starting at line 10 will refer to its first line as line 10,
+ not 0.
+
mode: string|object
+
By default, the new document inherits the mode of the
+ parent. This option can be set to
+ a mode spec to give it a
+ different mode.
+
+
doc.unlinkDoc(doc: CodeMirror.Doc)
+
Break the link between two documents. After calling this,
+ changes will no longer propagate between the documents, and, if
+ they had a shared history, the history will become
+ separate.
Will call the given function for all documents linked to the
+ target document. It will be passed two arguments, the linked document
+ and a boolean indicating whether that document shares history
+ with the target.
Returns an object with {undo, redo} properties,
+ both of which hold integers, indicating the amount of stored
+ undo and redo operations.
+
doc.clearHistory()
+
Clears the editor's undo history.
+
doc.getHistory() → object
+
Get a (JSON-serializable) representation of the undo history.
+
doc.setHistory(history: object)
+
Replace the editor's undo history with the one provided,
+ which must be a value as returned
+ by getHistory. Note that
+ this will have entirely undefined results if the editor content
+ isn't also the same as it was when getHistory was
+ called.
+
+
+
Text-marking methods
+
+
+
doc.markText(from: {line, ch}, to: {line, ch}, ?options: object) → TextMarker
+
Can be used to mark a range of text with a specific CSS
+ class name. from and to should
+ be {line, ch} objects. The options
+ parameter is optional. When given, it should be an object that
+ may contain the following configuration options:
+
+
className: string
+
Assigns a CSS class to the marked stretch of text.
+
inclusiveLeft: boolean
+
Determines whether
+ text inserted on the left of the marker will end up inside
+ or outside of it.
+
inclusiveRight: boolean
+
Like inclusiveLeft,
+ but for the right side.
+
atomic: boolean
+
Atomic ranges act as a single unit when cursor movement is
+ concerned—i.e. it is impossible to place the cursor inside of
+ them. In atomic ranges, inclusiveLeft
+ and inclusiveRight have a different meaning—they
+ will prevent the cursor from being placed respectively
+ directly before and directly after the range.
+
collapsed: boolean
+
Collapsed ranges do not show up in the display. Setting a
+ range to be collapsed will automatically make it atomic.
+
clearOnEnter: boolean
+
When enabled, will cause the mark to clear itself whenever
+ the cursor enters its range. This is mostly useful for
+ text-replacement widgets that need to 'snap open' when the
+ user tries to edit them. The
+ "clear" event
+ fired on the range handle can be used to be notified when this
+ happens.
+
clearWhenEmpty: boolean
+
Determines whether the mark is automatically cleared when
+ it becomes empty. Default is true.
+
replacedWith: Element
+
Use a given node to display this range. Implies both
+ collapsed and atomic. The given DOM node must be an
+ inline element (as opposed to a block element).
+
handleMouseEvents: boolean
+
When replacedWith is given, this determines
+ whether the editor will capture mouse and drag events
+ occurring in this widget. Default is false—the events will be
+ left alone for the default browser handler, or specific
+ handlers on the widget, to capture.
+
readOnly: boolean
+
A read-only span can, as long as it is not cleared, not be
+ modified except by
+ calling setValue to reset
+ the whole document. Note: adding a read-only span
+ currently clears the undo history of the editor, because
+ existing undo events being partially nullified by read-only
+ spans would corrupt the history (in the current
+ implementation).
+
addToHistory: boolean
+
When set to true (default is false), adding this marker
+ will create an event in the undo history that can be
+ individually undone (clearing the marker).
+
startStyle: string
Can be used to specify
+ an extra CSS class to be applied to the leftmost span that
+ is part of the marker.
+
endStyle: string
Equivalent
+ to startStyle, but for the rightmost span.
+
css: string
+
A string of CSS to be applied to the covered text. For example "color: #fe3".
+
title:
+ string
When given, will give the nodes created
+ for this span a HTML title attribute with the
+ given value.
+
shared: boolean
When the
+ target document is linked to other
+ documents, you can set shared to true to make the
+ marker appear in all documents. By default, a marker appears
+ only in its target document.
+
+ The method will return an object that represents the marker
+ (with constructor CodeMirror.TextMarker), which
+ exposes three methods:
+ clear(), to remove the mark,
+ find(), which returns
+ a {from, to} object (both holding document
+ positions), indicating the current position of the marked range,
+ or undefined if the marker is no longer in the
+ document, and finally changed(),
+ which you can call if you've done something that might change
+ the size of the marker (for example changing the content of
+ a replacedWith
+ node), and want to cheaply update the display.
Inserts a bookmark, a handle that follows the text around it
+ as it is being edited, at the given position. A bookmark has two
+ methods find() and clear(). The first
+ returns the current position of the bookmark, if it is still in
+ the document, and the second explicitly removes the bookmark.
+ The options argument is optional. If given, the following
+ properties are recognized:
+
+
widget: Element
Can be used to display a DOM
+ node at the current location of the bookmark (analogous to
+ the replacedWith
+ option to markText).
+
insertLeft: boolean
By default, text typed
+ when the cursor is on top of the bookmark will end up to the
+ right of the bookmark. Set this option to true to make it go
+ to the left instead.
Sets the gutter marker for the given gutter (identified by
+ its CSS class, see
+ the gutters option)
+ to the given value. Value can be either null, to
+ clear the marker, or a DOM element, to set it. The DOM element
+ will be shown in the specified gutter next to the specified
+ line.
+
+
cm.clearGutter(gutterID: string)
+
Remove all gutter markers in
+ the gutter with the given ID.
Set a CSS class name for the given line. line
+ can be a number or a line handle. where determines
+ to which element this class should be applied, can can be one
+ of "text" (the text element, which lies in front of
+ the selection), "background" (a background element
+ that will be behind the selection), "gutter" (the
+ line's gutter space), or "wrap" (the wrapper node
+ that wraps all of the line's elements, including gutter
+ elements). class should be the name of the class to
+ apply.
Remove a CSS class from a line. line can be a
+ line handle or number. where should be one
+ of "text", "background",
+ or "wrap"
+ (see addLineClass). class
+ can be left off to remove all classes for the specified node, or
+ be a string to remove only a specific class.
+
+
cm.lineInfo(line: integer|LineHandle) → object
+
Returns the line number, text content, and marker status of
+ the given line, which can be either a number or a line handle.
+ The returned object has the structure {line, handle, text,
+ gutterMarkers, textClass, bgClass, wrapClass, widgets},
+ where gutterMarkers is an object mapping gutter IDs
+ to marker elements, and widgets is an array
+ of line widgets attached to this
+ line, and the various class properties refer to classes added
+ with addLineClass.
Puts node, which should be an absolutely
+ positioned DOM node, into the editor, positioned right below the
+ given {line, ch} position.
+ When scrollIntoView is true, the editor will ensure
+ that the entire node is visible (if possible). To remove the
+ widget again, simply use DOM methods (move it somewhere else, or
+ call removeChild on its parent).
Adds a line widget, an element shown below a line, spanning
+ the whole of the editor's width, and moving the lines below it
+ downwards. line should be either an integer or a
+ line handle, and node should be a DOM node, which
+ will be displayed below the given line. options,
+ when given, should be an object that configures the behavior of
+ the widget. The following options are supported (all default to
+ false):
+
+
coverGutter: boolean
+
Whether the widget should cover the gutter.
+
noHScroll: boolean
+
Whether the widget should stay fixed in the face of
+ horizontal scrolling.
+
above: boolean
+
Causes the widget to be placed above instead of below
+ the text of the line.
+
handleMouseEvents: boolean
+
Determines whether the editor will capture mouse and
+ drag events occurring in this widget. Default is false—the
+ events will be left alone for the default browser handler,
+ or specific handlers on the widget, to capture.
+
insertAt: integer
+
By default, the widget is added below other widgets for
+ the line. This option can be used to place it at a different
+ position (zero for the top, N to put it after the Nth other
+ widget). Note that this only has effect once, when the
+ widget is created.
+
+ Note that the widget node will become a descendant of nodes with
+ CodeMirror-specific CSS classes, and those classes might in some
+ cases affect it. This method returns an object that represents
+ the widget placement. It'll have a line property
+ pointing at the line handle that it is associated with, and the following methods:
+
+
clear()
Removes the widget.
+
changed()
Call
+ this if you made some change to the widget's DOM node that
+ might affect its height. It'll force CodeMirror to update
+ the height of the line that contains the widget.
Programmatically set the size of the editor (overriding the
+ applicable CSS
+ rules). width and height
+ can be either numbers (interpreted as pixels) or CSS units
+ ("100%", for example). You can
+ pass null for either of them to indicate that that
+ dimension should not be changed.
+
+
cm.scrollTo(x: number, y: number)
+
Scroll the editor to a given (pixel) position. Both
+ arguments may be left as null
+ or undefined to have no effect.
Get an {left, top, width, height, clientWidth,
+ clientHeight} object that represents the current scroll
+ position, the size of the scrollable area, and the size of the
+ visible area (minus scrollbars).
Scrolls the given position into view. what may
+ be null to scroll the cursor into view,
+ a {line, ch} position to scroll a character into
+ view, a {left, top, right, bottom} pixel range (in
+ editor-local coordinates), or a range {from, to}
+ containing either two character positions or two pixel squares.
+ The margin parameter is optional. When given, it
+ indicates the amount of vertical pixels around the given area
+ that should be made visible as well.
Returns an {left, top, bottom} object
+ containing the coordinates of the cursor position.
+ If mode is "local", they will be
+ relative to the top-left corner of the editable document. If it
+ is "page" or not given, they are relative to the
+ top-left corner of the page. If mode
+ is "window", the coordinates are relative to the
+ top-left corner of the currently visible (scrolled)
+ window. where can be a boolean indicating whether
+ you want the start (true) or the end
+ (false) of the selection, or, if a {line,
+ ch} object is given, it specifies the precise position at
+ which you want to measure.
Returns the position and dimensions of an arbitrary
+ character. pos should be a {line, ch}
+ object. This differs from cursorCoords in that
+ it'll give the size of the whole character, rather than just the
+ position that the cursor would have when it would sit at that
+ position.
Given an {left, top} object, returns
+ the {line, ch} position that corresponds to it. The
+ optional mode parameter determines relative to what
+ the coordinates are interpreted. It may
+ be "window", "page" (the default),
+ or "local".
+
cm.lineAtHeight(height: number, ?mode: string) → number
+
Computes the line at the given pixel
+ height. mode can be one of the same strings
+ that coordsChar
+ accepts.
+
cm.heightAtLine(line: integer|LineHandle, ?mode: string) → number
+
Computes the height of the top of a line, in the coordinate
+ system specified by mode
+ (see coordsChar), which
+ defaults to "page". When a line below the bottom of
+ the document is specified, the returned value is the bottom of
+ the last line in the document.
+
cm.defaultTextHeight() → number
+
Returns the line height of the default font for the editor.
+
cm.defaultCharWidth() → number
+
Returns the pixel width of an 'x' in the default font for
+ the editor. (Note that for non-monospace fonts, this is mostly
+ useless, and even for monospace fonts, non-ascii characters
+ might have a different width).
+
+
cm.getViewport() → {from: number, to: number}
+
Returns a {from, to} object indicating the
+ start (inclusive) and end (exclusive) of the currently rendered
+ part of the document. In big documents, when most content is
+ scrolled out of view, CodeMirror will only render the visible
+ part, and a margin around it. See also
+ the viewportChange
+ event.
+
+
cm.refresh()
+
If your code does something to change the size of the editor
+ element (window resizes are already listened for), or unhides
+ it, you should probably follow up by calling this method to
+ ensure CodeMirror is still looking as intended. See also
+ the autorefresh addon.
+
+
+
Mode, state, and token-related methods
+
+
When writing language-aware functionality, it can often be
+ useful to hook into the knowledge that the CodeMirror language
+ mode has. See the section on modes for a
+ more detailed description of how these work.
+
+
+
doc.getMode() → object
+
Gets the (outer) mode object for the editor. Note that this
+ is distinct from getOption("mode"), which gives you
+ the mode specification, rather than the resolved, instantiated
+ mode object.
+
+
cm.getModeAt(pos: {line, ch}) → object
+
Gets the inner mode at a given position. This will return
+ the same as getMode for
+ simple modes, but will return an inner mode for nesting modes
+ (such as htmlmixed).
Retrieves information about the token the current mode found
+ before the given position (a {line, ch} object). The
+ returned object has the following properties:
+
+
start
The character (on the given line) at which the token starts.
+
end
The character at which the token ends.
+
string
The token's string.
+
type
The token type the mode assigned
+ to the token, such as "keyword"
+ or "comment" (may also be null).
+
state
The mode's state at the end of this token.
+
+ If precise is true, the token will be guaranteed to be accurate based on recent edits. If false or
+ not specified, the token will use cached state information, which will be faster but might not be accurate if
+ edits were recently made and highlighting has not yet completed.
+
This is similar
+ to getTokenAt, but
+ collects all tokens for a given line into an array. It is much
+ cheaper than repeatedly calling getTokenAt, which
+ re-parses the part of the line before the token for every call.
+
+
cm.getTokenTypeAt(pos: {line, ch}) → string
+
This is a (much) cheaper version
+ of getTokenAt useful for
+ when you just need the type of the token at a given position,
+ and no other information. Will return null for
+ unstyled tokens, and a string, potentially containing multiple
+ space-separated style names, otherwise.
Fetch the set of applicable helper values for the given
+ position. Helpers provide a way to look up functionality
+ appropriate for a mode. The type argument provides
+ the helper namespace (see
+ registerHelper), in
+ which the values will be looked up. When the mode itself has a
+ property that corresponds to the type, that
+ directly determines the keys that are used to look up the helper
+ values (it may be either a single string, or an array of
+ strings). Failing that, the mode's helperType
+ property and finally the mode's name are used.
+
For example, the JavaScript mode has a
+ property fold containing "brace". When
+ the brace-fold addon is loaded, that defines a
+ helper named brace in the fold
+ namespace. This is then used by
+ the foldcode addon to
+ figure out that it can use that folding function to fold
+ JavaScript code.
+
When any 'global'
+ helpers are defined for the given namespace, their predicates
+ are called on the current mode and editor, and all those that
+ declare they are applicable will also be added to the array that
+ is returned.
Returns the mode's parser state, if any, at the end of the
+ given line number. If no line number is given, the state at the
+ end of the document is returned. This can be useful for storing
+ parsing errors in the state, or getting other kinds of
+ contextual information for a line. precise is defined
+ as in getTokenAt().
+
+
+
Miscellaneous methods
+
+
+
cm.operation(func: () → any) → any
+
CodeMirror internally buffers changes and only updates its
+ DOM structure after it has finished performing some operation.
+ If you need to perform a lot of operations on a CodeMirror
+ instance, you can call this method with a function argument. It
+ will call the function, buffering up all changes, and only doing
+ the expensive update after the function returns. This can be a
+ lot faster. The return value from this method will be the return
+ value of your function.
Adjust the indentation of the given line. The second
+ argument (which defaults to "smart") may be one of:
+
+
"prev"
+
Base indentation on the indentation of the previous line.
+
"smart"
+
Use the mode's smart indentation if available, behave
+ like "prev" otherwise.
+
"add"
+
Increase the indentation of the line by
+ one indent unit.
+
"subtract"
+
Reduce the indentation of the line.
+
<integer>
+
Add (positive number) or reduce (negative number) the
+ indentation by the given amount of spaces.
+
+
+
cm.toggleOverwrite(?value: boolean)
+
Switches between overwrite and normal insert mode (when not
+ given an argument), or sets the overwrite mode to a specific
+ state (when given an argument).
+
+
cm.isReadOnly() → boolean
+
Tells you whether the editor's content can be edited by the
+ user.
+
+
doc.lineSeparator()
+
Returns the preferred line separator string for this
+ document, as per the option
+ by the same name. When that option is null, the
+ string "\n" is returned.
+
+
cm.execCommand(name: string)
+
Runs the command with the given name on the editor.
+
+
doc.posFromIndex(index: integer) → {line, ch}
+
Calculates and returns a {line, ch} object for a
+ zero-based index who's value is relative to the start of the
+ editor's text. If the index is out of range of the text then
+ the returned object is clipped to start or end of the text
+ respectively.
Returns the input field for the editor. Will be a textarea
+ or an editable div, depending on the value of
+ the inputStyle
+ option.
+
cm.getWrapperElement() → Element
+
Returns the DOM node that represents the editor, and
+ controls its size. Remove this from your tree to delete an
+ editor instance.
+
cm.getScrollerElement() → Element
+
Returns the DOM node that is responsible for the scrolling
+ of the editor.
+
cm.getGutterElement() → Element
+
Fetches the DOM node that contains the editor gutters.
+
+
+
Static properties
+
The CodeMirror object itself provides
+ several useful properties.
+
+
+
CodeMirror.version: string
+
It contains a string that indicates the version of the
+ library. This is a triple of
+ integers "major.minor.patch",
+ where patch is zero for releases, and something
+ else (usually one) for dev snapshots.
+ The method provides another way to initialize an editor. It
+ takes a textarea DOM node as first argument and an optional
+ configuration object as second. It will replace the textarea
+ with a CodeMirror instance, and wire up the form of that
+ textarea (if any) to make sure the editor contents are put
+ into the textarea when the form is submitted. The text in the
+ textarea will provide the content for the editor. A CodeMirror
+ instance created this way has three additional methods:
+
+
cm.save()
+
Copy the content of the editor into the textarea.
+
+
cm.toTextArea()
+
Remove the editor, and restore the original textarea (with
+ the editor's current content).
+
+
cm.getTextArea() → TextAreaElement
+
Returns the textarea that the instance was based on.
+
+
+
+
CodeMirror.defaults: object
+
An object containing default values for
+ all options. You can assign to its
+ properties to modify defaults (though this won't affect editors
+ that have already been created).
If you want to define extra methods in terms of the
+ CodeMirror API, it is possible to
+ use defineExtension. This will cause the given
+ value (usually a method) to be added to all CodeMirror instances
+ created from then on.
Similarly, defineOption can be used to define new options for
+ CodeMirror. The updateFunc will be called with the
+ editor instance and the new value when an editor is initialized,
+ and whenever the option is modified
+ through setOption.
+
+
CodeMirror.defineInitHook(func: function)
+
If your extension just needs to run some
+ code whenever a CodeMirror instance is initialized,
+ use CodeMirror.defineInitHook. Give it a function as
+ its only argument, and from then on, that function will be called
+ (with the instance as argument) whenever a new CodeMirror instance
+ is initialized.
Registers a helper value with the given name in
+ the given namespace (type). This is used to define
+ functionality that may be looked up by mode. Will create (if it
+ doesn't already exist) a property on the CodeMirror
+ object for the given type, pointing to an object
+ that maps names to values. I.e. after
+ doing CodeMirror.registerHelper("hint", "foo",
+ myFoo), the value CodeMirror.hint.foo will
+ point to myFoo.
Acts
+ like registerHelper,
+ but also registers this helper as 'global', meaning that it will
+ be included by getHelpers
+ whenever the given predicate returns true when
+ called with the local mode and editor.
+
+
CodeMirror.Pos(line: integer, ?ch: integer)
+
A constructor for the {line, ch} objects that
+ are used to represent positions in editor documents.
+
+
CodeMirror.changeEnd(change: object) → {line, ch}
+
Utility function that computes an end position from a change
+ (an object with from, to,
+ and text properties, as passed to
+ various event handlers). The
+ returned position will be the end of the changed
+ range, after the change is applied.
+
+
+
+
+
Addons
+
+
The addon directory in the distribution contains a
+ number of reusable components that implement extra editor
+ functionality (on top of extension functions
+ like defineOption, defineExtension,
+ and registerHelper). In
+ brief, they are:
Provides a very simple way to query users for text input.
+ Adds the openDialog(template, callback, options) →
+ closeFunction method to CodeMirror instances,
+ which can be called with an HTML fragment or a detached DOM
+ node that provides the prompt (should include an input
+ or button tag), and a callback function that is called
+ when the user presses enter. It returns a function closeFunction
+ which, if called, will close the dialog immediately.
+ openDialog takes the following options:
+
+
closeOnEnter:
+
If true, the dialog will be closed when the user presses
+ enter in the input. Defaults to true.
+
onKeyDown:
+
An event handler of the signature (event, value, closeFunction)
+ that will be called whenever keydown fires in the
+ dialog's input. If your callback returns true,
+ the dialog will not do any further processing of the event.
+
onKeyUp:
+
Same as onKeyDown but for the
+ keyup event.
+
onInput:
+
Same as onKeyDown but for the
+ input event.
+
onClose:
+
A callback of the signature (dialogInstance)
+ that will be called after the dialog has been closed and
+ removed from the DOM. No return value.
+
+
+
Also adds an openNotification(template, options) →
+ closeFunction function that simply shows an HTML
+ fragment as a notification at the top of the editor. It takes a
+ single option: duration, the amount of time after
+ which the notification will be automatically closed. If
+ duration is zero, the dialog will not be closed automatically.
Adds the getSearchCursor(query, start, caseFold) →
+ cursor method to CodeMirror instances, which can be used
+ to implement search/replace functionality. query
+ can be a regular expression or a string (only strings will match
+ across lines—if they contain newlines). start
+ provides the starting position of the search. It can be
+ a {line, ch} object, or can be left off to default
+ to the start of the document. caseFold is only
+ relevant when matching a string. It will cause the search to be
+ case-insensitive. A search cursor has the following methods:
+
+
findNext() → boolean
+
findPrevious() → boolean
+
Search forward or backward from the current position.
+ The return value indicates whether a match was found. If
+ matching a regular expression, the return value will be the
+ array returned by the match method, in case you
+ want to extract matched groups.
+
from() → {line, ch}
+
to() → {line, ch}
+
These are only valid when the last call
+ to findNext or findPrevious did
+ not return false. They will return {line, ch}
+ objects pointing at the start and end of the match.
+
replace(text: string, ?origin: string)
+
Replaces the currently found match with the given text
+ and adjusts the cursor position to reflect the
+ replacement.
Implements the search commands. CodeMirror has keys bound to
+ these by default, but will not do anything with them unless an
+ implementation is provided. Depends
+ on searchcursor.js, and will make use
+ of openDialog when
+ available to make prompting for search queries less ugly.
Implements a jumpToLine command and binding Alt-G to it.
+ Accepts linenumber, +/-linenumber, line:char,
+ scroll% and :linenumber formats.
+ This will make use of openDialog
+ when available to make prompting for line number neater.
Adds a showMatchesOnScrollbar method to editor
+ instances, which should be given a query (string or regular
+ expression), optionally a case-fold flag (only applicable for
+ strings), and optionally a class name (defaults
+ to CodeMirror-search-match) as arguments. When
+ called, matches of the given query will be displayed on the
+ editor's vertical scrollbar. The method returns an object with
+ a clear method that can be called to remove the
+ matches. Depends on
+ the annotatescrollbar
+ addon, and
+ the matchesonscrollbar.css
+ file provides a default (transparent yellowish) definition of
+ the CSS class applied to the matches. Note that the matches are
+ only perfectly aligned if your scrollbar does not have buttons
+ at the top and bottom. You can use
+ the simplescrollbar
+ addon to make sure of this. If this addon is loaded,
+ the search addon will
+ automatically use it.
Defines an option matchBrackets which, when set
+ to true, causes matching brackets to be highlighted whenever the
+ cursor is next to them. It also adds a
+ method matchBrackets that forces this to happen
+ once, and a method findMatchingBracket that can be
+ used to run the bracket-finding algorithm that this uses
+ internally.
Defines an option autoCloseBrackets that will
+ auto-close brackets and quotes when typed. By default, it'll
+ auto-close ()[]{}''"", but you can pass it a string
+ similar to that (containing pairs of matching characters), or an
+ object with pairs and
+ optionally explode properties to customize
+ it. explode should be a similar string that gives
+ the pairs of characters that, when enter is pressed between
+ them, should have the second character also moved to its own
+ line. Demo here.
Defines an option matchTags that, when enabled,
+ will cause the tags around the cursor to be highlighted (using
+ the CodeMirror-matchingtag class). Also
+ defines
+ a commandtoMatchingTag,
+ which you can bind a key to in order to jump to the tag matching
+ the one under the cursor. Depends on
+ the addon/fold/xml-fold.js
+ addon. Demo here.
Adds an option showTrailingSpace which, when
+ enabled, adds the CSS class cm-trailingspace to
+ stretches of whitespace at the end of lines.
+ The demo has a nice
+ squiggly underline style for this class.
Defines an autoCloseTags option that will
+ auto-close XML tags when '>' or '/'
+ is typed, and
+ a closeTagcommand that
+ closes the nearest open tag. Depends on
+ the fold/xml-fold.js addon. See
+ the demo.
Markdown specific. Defines
+ a "newlineAndIndentContinueMarkdownList"command
+ that can be bound to enter to automatically
+ insert the leading characters for continuing a list. See
+ the Markdown mode
+ demo.
Helps with code folding. Adds a foldCode method
+ to editor instances, which will try to do a code fold starting
+ at the given line, or unfold the fold that is already present.
+ The method takes as first argument the position that should be
+ folded (may be a line number or
+ a Pos), and as second optional
+ argument either a range-finder function, or an options object,
+ supporting the following properties:
+
+
rangeFinder: fn(CodeMirror, Pos)
+
The function that is used to find
+ foldable ranges. If this is not directly passed, it will
+ default to CodeMirror.fold.auto, which
+ uses getHelpers with
+ a "fold" type to find folding functions
+ appropriate for the local mode. There are files in
+ the addon/fold/
+ directory providing CodeMirror.fold.brace, which
+ finds blocks in brace languages (JavaScript, C, Java,
+ etc), CodeMirror.fold.indent, for languages where
+ indentation determines block structure (Python, Haskell),
+ and CodeMirror.fold.xml, for XML-style languages,
+ and CodeMirror.fold.comment, for folding comment
+ blocks.
+
widget: string|Element
+
The widget to show for folded ranges. Can be either a
+ string, in which case it'll become a span with
+ class CodeMirror-foldmarker, or a DOM node.
+
scanUp: boolean
+
When true (default is false), the addon will try to find
+ foldable ranges on the lines above the current one if there
+ isn't an eligible one on the given line.
+
minFoldSize: integer
+
The minimum amount of lines that a fold should span to be
+ accepted. Defaults to 0, which also allows single-line
+ folds.
Provides an option foldGutter, which can be
+ used to create a gutter with markers indicating the blocks that
+ can be folded. Create a gutter using
+ the gutters option,
+ giving it the class CodeMirror-foldgutter or
+ something else if you configure the addon to use a different
+ class, and this addon will show markers next to folded and
+ foldable blocks, and handle clicks in this gutter. Note that
+ CSS styles should be applied to make the gutter, and the fold
+ markers within it, visible. A default set of CSS styles are
+ available in:
+
+ addon/fold/foldgutter.css
+ .
+ The option
+ can be either set to true, or an object containing
+ the following optional option fields:
+
+
gutter: string
+
The CSS class of the gutter. Defaults
+ to "CodeMirror-foldgutter". You will have to
+ style this yourself to give it a width (and possibly a
+ background). See the default gutter style rules above.
+
indicatorOpen: string | Element
+
A CSS class or DOM element to be used as the marker for
+ open, foldable blocks. Defaults
+ to "CodeMirror-foldgutter-open".
+
indicatorFolded: string | Element
+
A CSS class or DOM element to be used as the marker for
+ folded blocks. Defaults to "CodeMirror-foldgutter-folded".
+
rangeFinder: fn(CodeMirror, Pos)
+
The range-finder function to use when determining whether
+ something can be folded. When not
+ given, CodeMirror.fold.auto
+ will be used as default.
+
+ The foldOptions editor option can be set to an
+ object to provide an editor-wide default configuration.
+ Demo here.
Can be used to run a CodeMirror mode over text without
+ actually opening an editor instance.
+ See the demo for an example.
+ There are alternate versions of the file available for
+ running stand-alone
+ (without including all of CodeMirror) and
+ for running under
+ node.js (see bin/source-highlight for an example of using the latter).
Provides a convenient way to syntax-highlight code snippets
+ in a webpage. Depends on
+ the runmode addon (or
+ its standalone variant). Provides
+ a CodeMirror.colorize function that can be called
+ with an array (or other array-ish collection) of DOM nodes that
+ represent the code snippets. By default, it'll get
+ all pre tags. Will read the data-lang
+ attribute of these nodes to figure out their language, and
+ syntax-color their content using the relevant CodeMirror mode
+ (you'll have to load the scripts for the relevant modes
+ yourself). A second argument may be provided to give a default
+ mode, used when no language attribute is found for a node. Used
+ in this manual to highlight example code.
Mode combinator that can be used to extend a mode with an
+ 'overlay' — a secondary mode is run over the stream, along with
+ the base mode, and can color specific pieces of text without
+ interfering with the base mode.
+ Defines CodeMirror.overlayMode, which is used to
+ create such a mode. See this
+ demo for a detailed example.
Mode combinator that can be used to easily 'multiplex'
+ between several modes.
+ Defines CodeMirror.multiplexingMode which, when
+ given as first argument a mode object, and as other arguments
+ any number of {open, close, mode [, delimStyle, innerStyle, parseDelimiters]}
+ objects, will return a mode object that starts parsing using the
+ mode passed as first argument, but will switch to another mode
+ as soon as it encounters a string that occurs in one of
+ the open fields of the passed objects. When in a
+ sub-mode, it will go back to the top mode again when
+ the close string is encountered.
+ Pass "\n" for open or close
+ if you want to switch on a blank line.
+
When delimStyle is specified, it will be the token
+ style returned for the delimiter tokens (as well as
+ [delimStyle]-open on the opening token and
+ [delimStyle]-close on the closing token).
+
When innerStyle is specified, it will be the token
+ style added for each inner mode token.
+
When parseDelimiters is true, the content of
+ the delimiters will also be passed to the inner mode.
+ (And delimStyle is ignored.)
The outer
+ mode will not see the content between the delimiters.
+ See this demo for an
+ example.
Provides a framework for showing autocompletion hints.
+ Defines editor.showHint, which takes an optional
+ options object, and pops up a widget that allows the user to
+ select a completion. Finding hints is done with a hinting
+ functions (the hint option), which is a function
+ that take an editor instance and options object, and return
+ a {list, from, to} object, where list
+ is an array of strings or objects (the completions),
+ and from and to give the start and end
+ of the token that is being completed as {line, ch}
+ objects. An optional selectedHint property (an
+ integer) can be added to the completion object to control the
+ initially selected hint.
+
If no hinting function is given, the addon will
+ use CodeMirror.hint.auto, which
+ calls getHelpers with
+ the "hint" type to find applicable hinting
+ functions, and tries them one by one. If that fails, it looks
+ for a "hintWords" helper to fetch a list of
+ completable words for the mode, and
+ uses CodeMirror.hint.fromList to complete from
+ those.
+
When completions aren't simple strings, they should be
+ objects with the following properties:
+
+
text: string
+
The completion text. This is the only required
+ property.
+
displayText: string
+
The text that should be displayed in the menu.
+
className: string
+
A CSS class name to apply to the completion's line in the
+ menu.
+
render: fn(Element, self, data)
+
A method used to create the DOM structure for showing the
+ completion by appending it to its first argument.
+
hint: fn(CodeMirror, self, data)
+
A method used to actually apply the completion, instead of
+ the default behavior.
+
from: {line, ch}
+
Optional from position that will be used by pick() instead
+ of the global one passed with the full list of completions.
+
to: {line, ch}
+
Optional to position that will be used by pick() instead
+ of the global one passed with the full list of completions.
+
+ The plugin understands the following options (the options object
+ will also be passed along to the hinting function, which may
+ understand additional options):
+
+
hint: function
+
A hinting function, as specified above. It is possible to
+ set the async property on a hinting function to
+ true, in which case it will be called with
+ arguments (cm, callback, ?options), and the
+ completion interface will only be popped up when the hinting
+ function calls the callback, passing it the object holding the
+ completions. By default, hinting only works when there is no
+ selection. You can give a hinting function
+ a supportsSelection property with a truthy value
+ to indicate that it supports selections.
+
completeSingle: boolean
+
Determines whether, when only a single completion is
+ available, it is completed without showing the dialog.
+ Defaults to true.
+
alignWithWord: boolean
+
Whether the pop-up should be horizontally aligned with the
+ start of the word (true, default), or with the cursor (false).
+
closeOnUnfocus: boolean
+
When enabled (which is the default), the pop-up will close
+ when the editor is unfocused.
+
customKeys: keymap
+
Allows you to provide a custom key map of keys to be active
+ when the pop-up is active. The handlers will be called with an
+ extra argument, a handle to the completion menu, which
+ has moveFocus(n), setFocus(n), pick(),
+ and close() methods (see the source for details),
+ that can be used to change the focused element, pick the
+ current element or close the menu. Additionally menuSize()
+ can give you access to the size of the current dropdown menu,
+ length give you the number of available completions, and
+ data give you full access to the completion returned by the
+ hinting function.
+
extraKeys: keymap
+
Like customKeys above, but the bindings will
+ be added to the set of default bindings, instead of replacing
+ them.
+
+ The following events will be fired on the completions object
+ during completion:
+
+
"shown" ()
+
Fired when the pop-up is shown.
+
"select" (completion, Element)
+
Fired when a completion is selected. Passed the completion
+ value (string or object) and the DOM node that represents it
+ in the menu.
+
"pick" (completion)
+
Fired when a completion is picked. Passed the completion value
+ (string or object).
+
"close" ()
+
Fired when the completion is finished.
+
+ This addon depends on styles
+ from addon/hint/show-hint.css. Check
+ out the demo for an
+ example.
Defines a simple hinting function for JavaScript
+ (CodeMirror.hint.javascript) and CoffeeScript
+ (CodeMirror.hint.coffeescript) code. This will
+ simply use the JavaScript environment that the editor runs in as
+ a source of information about objects and their properties.
Defines CodeMirror.hint.xml, which produces
+ hints for XML tagnames, attribute names, and attribute values,
+ guided by a schemaInfo option (a property of the
+ second argument passed to the hinting function, or the third
+ argument passed to CodeMirror.showHint). The
+ schema info should be an object mapping tag names to information
+ about these tags, with optionally a "!top" property
+ containing a list of the names of valid top-level tags. The
+ values of the properties should be objects with optional
+ properties children (an array of valid child
+ element names, omit to simply allow all tags to appear)
+ and attrs (an object mapping attribute names
+ to null for free-form attributes, and an array of
+ valid values for restricted
+ attributes). Demo
+ here.
Provides schema info to
+ the xml-hint addon for HTML
+ documents. Defines a schema
+ object CodeMirror.htmlSchema that you can pass to
+ as a schemaInfo option, and
+ a CodeMirror.hint.html hinting function that
+ automatically calls CodeMirror.hint.xml with this
+ schema data. See
+ the demo.
A very simple hinting function
+ (CodeMirror.hint.anyword) that simply looks for
+ words in the nearby code and completes to those. Takes two
+ optional options, word, a regular expression that
+ matches words (sequences of one or more character),
+ and range, which defines how many lines the addon
+ should scan when completing (defaults to 500).
A simple SQL hinter. Defines CodeMirror.hint.sql.
+ Takes two optional options, tables, a object with
+ table names as keys and array of respective column names as values,
+ and defaultTable, a string corresponding to a
+ table name in tables for autocompletion.
Adds a highlightSelectionMatches option that
+ can be enabled to highlight all instances of a currently
+ selected word. Can be set either to true or to an object
+ containing the following options: minChars, for the
+ minimum amount of selected characters that triggers a highlight
+ (default 2), style, for the style to be used to
+ highlight the matches (default "matchhighlight",
+ which will correspond to CSS
+ class cm-matchhighlight),
+ and showToken which can be set to true
+ or to a regexp matching the characters that make up a word. When
+ enabled, it causes the current word to be highlighted when
+ nothing is selected (defaults to off).
+ Demo here.
Defines an interface component for showing linting warnings,
+ with pluggable warning sources
+ (see html-lint.js,
+ json-lint.js,
+ javascript-lint.js,
+ coffeescript-lint.js,
+ and css-lint.js
+ in the same directory). Defines a lint option that
+ can be set to an annotation source (for
+ example CodeMirror.lint.javascript), to an options
+ object (in which case the getAnnotations field is
+ used as annotation source), or simply to true. When
+ no annotation source is
+ specified, getHelper with
+ type "lint" is used to find an annotation function.
+ An annotation source function should, when given a document
+ string, an options object, and an editor instance, return an
+ array of {message, severity, from, to} objects
+ representing problems. When the function has
+ an async property with a truthy value, it will be
+ called with an additional second argument, which is a callback
+ to pass the array to. By default, the linter will run
+ (debounced) whenever the document is changed. You can pass
+ a lintOnChange: false option to disable that.
+ Depends on addon/lint/lint.css. A demo can be
+ found here.
Causes the selected text to be marked with the CSS class
+ CodeMirror-selectedtext when the styleSelectedText option
+ is enabled. Useful to change the colour of the selection (in addition to the background),
+ like in this demo.
Defines a styleActiveLine option that, when enabled,
+ gives the wrapper of the active line the class CodeMirror-activeline,
+ adds a background with the class CodeMirror-activeline-background,
+ and adds the class CodeMirror-activeline-gutter to the
+ line's gutter space is enabled. See the
+ demo.
Defines a selectionPointer option which you can
+ use to control the mouse cursor appearance when hovering over
+ the selection. It can be set to a string,
+ like "pointer", or to true, in which case
+ the "default" (arrow) cursor will be used. You can
+ see a demo here.
Defines a CodeMirror.requireMode(modename,
+ callback) function that will try to load a given mode and
+ call the callback when it succeeded. You'll have to
+ set CodeMirror.modeURL to a string that mode paths
+ can be constructed from, for
+ example "mode/%N/%N.js"—the %N's will
+ be replaced with the mode name. Also
+ defines CodeMirror.autoLoadMode(instance, mode),
+ which will ensure the given mode is loaded and cause the given
+ editor instance to refresh its mode when the loading
+ succeeded. See the demo.
Provides meta-information about all the modes in the
+ distribution in a single file.
+ Defines CodeMirror.modeInfo, an array of objects
+ with {name, mime, mode} properties,
+ where name is the human-readable
+ name, mime the MIME type, and mode the
+ name of the mode file that defines this MIME. There are optional
+ properties mimes, which holds an array of MIME
+ types for modes with multiple MIMEs associated,
+ and ext, which holds an array of file extensions
+ associated with this mode. Four convenience
+ functions, CodeMirror.findModeByMIME,
+ CodeMirror.findModeByExtension,
+ CodeMirror.findModeByFileName
+ and CodeMirror.findModeByName are provided, which
+ return such an object given a MIME, extension, file name or mode name
+ string. Note that, for historical reasons, this file resides in the
+ top-level mode directory, not
+ under addon. Demo.
Adds a continueComments option, which sets whether the
+ editor will make the next line continue a comment when you press Enter
+ inside a comment block. Can be set to a boolean to enable/disable this
+ functionality. Set to a string, it will continue comments using a custom
+ shortcut. Set to an object, it will use the key property for
+ a custom shortcut and the boolean continueLineComment
+ property to determine whether single-line comments should be continued
+ (defaulting to true).
Adds a placeholder option that can be used to
+ make content appear in the editor when it is empty and not
+ focused. It can hold either a string or a DOM node. Also gives
+ the editor a CodeMirror-empty CSS class whenever it
+ doesn't contain any text.
+ See the demo.
Defines an option fullScreen that, when set
+ to true, will make the editor full-screen (as in,
+ taking up the whole browser window). Depends
+ on fullscreen.css. Demo
+ here.
This addon can be useful when initializing an editor in a
+ hidden DOM node, in cases where it is difficult to
+ call refresh when the editor
+ becomes visible. It defines an option autoRefresh
+ which you can set to true to ensure that, if the editor wasn't
+ visible on initialization, it will be refreshed the first time
+ it becomes visible. This is done by polling every 250
+ milliseconds (you can pass a value like {delay:
+ 500} as the option value to configure this). Note that
+ this addon will only refresh the editor once when it
+ first becomes visible, and won't take care of further restyling
+ and resizing.
Defines two additional scrollbar
+ models, "simple" and "overlay"
+ (see demo) that can
+ be selected with
+ the scrollbarStyle
+ option. Depends
+ on simplescrollbars.css,
+ which can be further overridden to style your own
+ scrollbars.
Provides functionality for showing markers on the scrollbar
+ to call out certain parts of the document. Adds a
+ method annotateScrollbar to editor instances that
+ can be called, with a CSS class name as argument, to create a
+ set of annotations. The method returns an object
+ whose update method can be called with an array
+ of {from: Pos, to: Pos} objects marking the ranges
+ to be highlighted. To detach the annotations, call the
+ object's clear method.
Adds a rulers option, which can be used to show
+ one or more vertical rulers in the editor. The option, if
+ defined, should be given an array of {column [, className,
+ color, lineStyle, width]} objects or numbers (which
+ indicate a column). The ruler will be displayed at the column
+ indicated by the number or the column property.
+ The className property can be used to assign a
+ custom style to a ruler. Demo
+ here.
Defines an addPanel method for CodeMirror
+ instances, which places a DOM node above or below an editor, and
+ shrinks the editor to make room for the node. The method takes
+ as first argument as DOM node, and as second an optional options
+ object. The Panel object returned by this method
+ has a clear method that is used to remove the
+ panel, and a changed method that can be used to
+ notify the addon when the size of the panel's DOM node has
+ changed.
+ The method accepts the following options:
+
+
position : string
+
Controls the position of the newly added panel. The
+ following values are recognized:
+
+
top (default)
+
Adds the panel at the very top.
+
after-top
+
Adds the panel at the bottom of the top panels.
+
bottom
+
Adds the panel at the very bottom.
+
before-bottom
+
Adds the panel at the top of the bottom panels.
+
+
+
before : Panel
+
The new panel will be added before the given panel.
+
after : Panel
+
The new panel will be added after the given panel.
+
replace : Panel
+
The new panel will replace the given panel.
+
+ When using the after, before or replace options,
+ if the panel doesn't exists or has been removed,
+ the value of the position option will be used as a fallback.
+
+ A demo of the addon is available here.
+
Addon to perform hard line wrapping/breaking for paragraphs
+ of text. Adds these methods to editor instances:
+
+
wrapParagraph(?pos: {line, ch}, ?options: object)
+
Wraps the paragraph at the given position.
+ If pos is not given, it defaults to the cursor
+ position.
+
wrapRange(from: {line, ch}, to: {line, ch}, ?options: object)
+
Wraps the given range as one big paragraph.
+
wrapParagraphsInRange(from: {line, ch}, to: {line, ch}, ?options: object)
+
Wraps the paragraphs in (and overlapping with) the
+ given range individually.
+
+ The following options are recognized:
+
+
paragraphStart, paragraphEnd: RegExp
+
Blank lines are always considered paragraph boundaries.
+ These options can be used to specify a pattern that causes
+ lines to be considered the start or end of a paragraph.
+
column: number
+
The column to wrap at. Defaults to 80.
+
wrapOn: RegExp
+
A regular expression that matches only those
+ two-character strings that allow wrapping. By default, the
+ addon wraps on whitespace and after dash characters.
+
killTrailingSpace: boolean
+
Whether trailing space caused by wrapping should be
+ preserved, or deleted. Defaults to true.
Implements an interface for merging changes, using either a
+ 2-way or a 3-way view. The CodeMirror.MergeView
+ constructor takes arguments similar to
+ the CodeMirror
+ constructor, first a node to append the interface to, and then
+ an options object. Options are passed through to the editors
+ inside the view. These extra options are recognized:
+
+
origLeft and origRight: string
+
If given these provide original versions of the
+ document, which will be shown to the left and right of the
+ editor in non-editable CodeMirror instances. The merge
+ interface will highlight changes between the editable
+ document and the original(s). To create a 2-way (as opposed
+ to 3-way) merge view, provide only one of them.
+
revertButtons: boolean
+
Determines whether buttons that allow the user to revert
+ changes are shown. Defaults to true.
+
connect: string
+
Sets the style used to connect changed chunks of code.
+ By default, connectors are drawn. When this is set
+ to "align", the smaller chunk is padded to
+ align with the bigger chunk instead.
+
collapseIdentical: boolean|number
+
When true (default is false), stretches of unchanged
+ text will be collapsed. When a number is given, this
+ indicates the amount of lines to leave visible around such
+ stretches (which defaults to 2).
+
allowEditingOriginals: boolean
+
Determines whether the original editor allows editing.
+ Defaults to false.
+
showDifferences: boolean
+
When true (the default), changed pieces of text are
+ highlighted.
+
+ The addon also defines commands "goNextDiff"
+ and "goPrevDiff" to quickly jump to the next
+ changed chunk. Demo
+ here.
Provides integration with
+ the Tern JavaScript analysis
+ engine, for completion, definition finding, and minor
+ refactoring help. See the demo
+ for a very simple integration. For more involved scenarios, see
+ the comments at the top of
+ the addon and the
+ implementation of the
+ (multi-file) demonstration
+ on the Tern website.
+
+
+
+
+
Writing CodeMirror Modes
+
+
Modes typically consist of a single JavaScript file. This file
+ defines, in the simplest case, a lexer (tokenizer) for your
+ language—a function that takes a character stream as input,
+ advances it past a token, and returns a style for that token. More
+ advanced modes can also handle indentation for the language.
+
+
This section describes the low-level mode interface. Many modes
+ are written directly against this, since it offers a lot of
+ control, but for a quick mode definition, you might want to use
+ the simple mode addon.
+
+
The mode script should
+ call CodeMirror.defineMode to
+ register itself with CodeMirror. This function takes two
+ arguments. The first should be the name of the mode, for which you
+ should use a lowercase string, preferably one that is also the
+ name of the files that define the mode (i.e. "xml" is
+ defined in xml.js). The second argument should be a
+ function that, given a CodeMirror configuration object (the thing
+ passed to the CodeMirror function) and an optional
+ mode configuration object (as in
+ the mode option), returns
+ a mode object.
+
+
Typically, you should use this second argument
+ to defineMode as your module scope function (modes
+ should not leak anything into the global scope!), i.e. write your
+ whole mode inside this function.
+
+
The main responsibility of a mode script is parsing
+ the content of the editor. Depending on the language and the
+ amount of functionality desired, this can be done in really easy
+ or extremely complicated ways. Some parsers can be stateless,
+ meaning that they look at one element (token) of the code
+ at a time, with no memory of what came before. Most, however, will
+ need to remember something. This is done by using a state
+ object, which is an object that is always passed when
+ reading a token, and which can be mutated by the tokenizer.
+
+
Modes that use a state must define
+ a startState method on their mode
+ object. This is a function of no arguments that produces a state
+ object to be used at the start of a document.
+
+
The most important part of a mode object is
+ its token(stream, state) method. All
+ modes must define this method. It should read one token from the
+ stream it is given as an argument, optionally update its state,
+ and return a style string, or null for tokens that do
+ not have to be styled. For your styles, you are encouraged to use
+ the 'standard' names defined in the themes (without
+ the cm- prefix). If that fails, it is also possible
+ to come up with your own and write your own CSS theme file.
+
+
A typical token string would
+ be "variable" or "comment". Multiple
+ styles can be returned (separated by spaces), for
+ example "string error" for a thing that looks like a
+ string but is invalid somehow (say, missing its closing quote).
+ When a style is prefixed by "line-"
+ or "line-background-", the style will be applied to
+ the whole line, analogous to what
+ the addLineClass method
+ does—styling the "text" in the simple case, and
+ the "background" element
+ when "line-background-" is prefixed.
+
+
The stream object that's passed
+ to token encapsulates a line of code (tokens may
+ never span lines) and our current position in that line. It has
+ the following API:
+
+
+
eol() → boolean
+
Returns true only if the stream is at the end of the
+ line.
+
sol() → boolean
+
Returns true only if the stream is at the start of the
+ line.
+
+
peek() → string
+
Returns the next character in the stream without advancing
+ it. Will return a null at the end of the
+ line.
+
next() → string
+
Returns the next character in the stream and advances it.
+ Also returns null when no more characters are
+ available.
match can be a character, a regular expression,
+ or a function that takes a character and returns a boolean. If
+ the next character in the stream 'matches' the given argument,
+ it is consumed and returned. Otherwise, undefined
+ is returned.
Repeatedly calls eat with the given argument,
+ until it fails. Returns true if any characters were eaten.
+
eatSpace() → boolean
+
Shortcut for eatWhile when matching
+ white-space.
+
skipToEnd()
+
Moves the position to the end of the line.
+
skipTo(ch: string) → boolean
+
Skips to the next occurrence of the given character, if
+ found on the current line (doesn't advance the stream if the
+ character does not occur on the line). Returns true if the
+ character was found.
Act like a
+ multi-character eat—if consume is true
+ or not given—or a look-ahead that doesn't update the stream
+ position—if it is false. pattern can be either a
+ string or a regular expression starting with ^.
+ When it is a string, caseFold can be set to true to
+ make the match case-insensitive. When successfully matching a
+ regular expression, the returned value will be the array
+ returned by match, in case you need to extract
+ matched groups.
+
+
backUp(n: integer)
+
Backs up the stream n characters. Backing it up
+ further than the start of the current token will cause things to
+ break, so be careful.
+
column() → integer
+
Returns the column (taking into account tabs) at which the
+ current token starts.
+
indentation() → integer
+
Tells you how far the current line has been indented, in
+ spaces. Corrects for tab characters.
+
+
current() → string
+
Get the string between the start of the current token and
+ the current stream position.
+
+
+
By default, blank lines are simply skipped when
+ tokenizing a document. For languages that have significant blank
+ lines, you can define
+ a blankLine(state) method on your
+ mode that will get called whenever a blank line is passed over, so
+ that it can update the parser state.
+
+
Because state object are mutated, and CodeMirror
+ needs to keep valid versions of a state around so that it can
+ restart a parse at any line, copies must be made of state objects.
+ The default algorithm used is that a new state object is created,
+ which gets all the properties of the old object. Any properties
+ which hold arrays get a copy of these arrays (since arrays tend to
+ be used as mutable stacks). When this is not correct, for example
+ because a mode mutates non-array properties of its state object, a
+ mode object should define
+ a copyState method, which is given a
+ state and should return a safe copy of that state.
+
+
If you want your mode to provide smart indentation
+ (through the indentLine
+ method and the indentAuto
+ and newlineAndIndent commands, to which keys can be
+ bound), you must define
+ an indent(state, textAfter) method
+ on your mode object.
+
+
The indentation method should inspect the given state object,
+ and optionally the textAfter string, which contains
+ the text on the line that is being indented, and return an
+ integer, the amount of spaces to indent. It should usually take
+ the indentUnit
+ option into account. An indentation method may
+ return CodeMirror.Pass to indicate that it
+ could not come up with a precise indentation.
+
+
To work well with
+ the commenting addon, a mode may
+ define lineComment (string that
+ starts a line
+ comment), blockCommentStart, blockCommentEnd
+ (strings that start and end block comments),
+ and blockCommentLead (a string to put at the start of
+ continued lines in a block comment). All of these are
+ optional.
+
+
Finally, a mode may define either
+ an electricChars or an electricInput
+ property, which are used to automatically reindent the line when
+ certain patterns are typed and
+ the electricChars
+ option is enabled. electricChars may be a string, and
+ will trigger a reindent whenever one of the characters in that
+ string are typed. Often, it is more appropriate to
+ use electricInput, which should hold a regular
+ expression, and will trigger indentation when the part of the
+ line before the cursor matches the expression. It should
+ usually end with a $ character, so that it only
+ matches when the indentation-changing pattern was just typed, not when something was
+ typed after the pattern.
+
+
So, to summarize, a mode must provide
+ a token method, and it may
+ provide startState, copyState,
+ and indent methods. For an example of a trivial mode,
+ see the diff mode, for a more
+ involved example, see the C-like
+ mode.
+
+
Sometimes, it is useful for modes to nest—to have one
+ mode delegate work to another mode. An example of this kind of
+ mode is the mixed-mode HTML
+ mode. To implement such nesting, it is usually necessary to
+ create mode objects and copy states yourself. To create a mode
+ object, there are CodeMirror.getMode(options,
+ parserConfig), where the first argument is a configuration
+ object as passed to the mode constructor function, and the second
+ argument is a mode specification as in
+ the mode option. To copy a
+ state object, call CodeMirror.copyState(mode, state),
+ where mode is the mode that created the given
+ state.
+
+
In a nested mode, it is recommended to add an
+ extra method, innerMode which, given
+ a state object, returns a {state, mode} object with
+ the inner mode and its state for the current position. These are
+ used by utility scripts such as the tag
+ closer to get context information. Use
+ the CodeMirror.innerMode helper function to, starting
+ from a mode and a state, recursively walk down to the innermost
+ mode and state.
+
+
To make indentation work properly in a nested parser, it is
+ advisable to give the startState method of modes that
+ are intended to be nested an optional argument that provides the
+ base indentation for the block of code. The JavaScript and CSS
+ parser do this, for example, to allow JavaScript and CSS code
+ inside the mixed-mode HTML mode to be properly indented.
+
+
It is possible, and encouraged, to associate
+ your mode, or a certain configuration of your mode, with
+ a MIME type. For
+ example, the JavaScript mode associates itself
+ with text/javascript, and its JSON variant
+ with application/json. To do this,
+ call CodeMirror.defineMIME(mime,
+ modeSpec), where modeSpec can be a string or
+ object specifying a mode, as in
+ the mode option.
+
+
If a mode specification wants to add some properties to the
+ resulting mode object, typically for use
+ with getHelpers, it may
+ contain a modeProps property, which holds an object.
+ This object's properties will be copied to the actual mode
+ object.
+
+
Sometimes, it is useful to add or override mode
+ object properties from external code.
+ The CodeMirror.extendMode function
+ can be used to add properties to mode objects produced for a
+ specific mode. Its first argument is the name of the mode, its
+ second an object that specifies the properties that should be
+ added. This is mostly useful to add utilities that can later be
+ looked up through getMode.
+
+
+
+
VIM Mode API
+
+
CodeMirror has a robust VIM mode that attempts to faithfully
+ emulate VIM's most useful features. It can be enabled by
+ including keymap/vim.js
+ and setting the keyMap option to
+ "vim".
+
+
Configuration
+
+
VIM mode accepts configuration options for customizing
+ behavior at run time. These methods can be called at any time
+ and will affect all existing CodeMirror instances unless
+ specified otherwise. The methods are exposed on the
+ CodeMirror.Vim object.
Sets the value of a VIM option. name should
+ be the name of an option. If cfg.scope is not set
+ and cm is provided, then sets the global and
+ instance values of the option. Otherwise, sets either the
+ global or instance value of the option depending on whether
+ cfg.scope is global or
+ local.
Gets the current value of a VIM option. If
+ cfg.scope is not set and cm is
+ provided, then gets the instance value of the option, falling
+ back to the global value if not set. If cfg.scope is provided, then gets the global or
+ local value without checking the other.
+
+
map(lhs: string, rhs: string, ?context: string)
+
Maps a key sequence to another key sequence. Implements
+ VIM's :map command. To map ; to : in VIM would be
+ :map ; :. That would translate to
+ CodeMirror.Vim.map(';', ':');.
+ The context can be normal,
+ visual, or insert, which correspond
+ to :nmap, :vmap, and
+ :imap
+ respectively.
Maps a key sequence to a motion,
+ operator, or action type command.
+ The args object is passed through to the command when it is
+ invoked by the provided key sequence.
+ extras.context can be normal,
+ visual, or insert, to map the key
+ sequence only in the corresponding mode.
+ extras.isEdit is applicable only to actions,
+ determining whether it is recorded for replay for the
+ . single-repeat command.
+
+
+
Extending VIM
+
+
CodeMirror's VIM mode implements a large subset of VIM's core
+ editing functionality. But since there's always more to be
+ desired, there is a set of APIs for extending VIM's
+ functionality. As with the configuration API, the methods are
+ exposed on CodeMirror.Vim and may
+ be called at any time.
Defines a VIM style option and makes it available to the
+ :set command. Type can be boolean or
+ string, used for validation and by
+ :set to determine which syntax to accept. If a
+ callback is passed in, VIM does not store the value of the
+ option itself, but instead uses the callback as a setter/getter. If the
+ first argument to the callback is undefined, then the
+ callback should return the value of the option. Otherwise, it should set
+ instead. Since VIM options have global and instance values, whether a
+ CodeMirror instance is passed in denotes whether the global
+ or local value should be used. Consequently, it's possible for the
+ callback to be called twice for a single setOption or
+ getOption call. Note that right now, VIM does not support
+ defining buffer-local options that do not have global values. If an
+ option should not have a global value, either always ignore the
+ cm parameter in the callback, or always pass in a
+ cfg.scope to setOption and
+ getOption.
Defines a motion command for VIM. The motion should return
+ the desired result position of the cursor. head
+ is the current position of the cursor. It can differ from
+ cm.getCursor('head') if VIM is in visual mode.
+ motionArgs is the object passed into
+ mapCommand().
Defines an operator command, similar to
+ defineMotion. ranges is the range
+ of text the operator should operate on. If the cursor should
+ be set to a certain position after the operation finishes, it
+ can return a cursor object.
Defines an action command, similar to
+ defineMotion. Action commands
+ can have arbitrary behavior, making them more flexible than
+ motions and operators, at the loss of orthogonality.
Defines an Ex command, and maps it to :name.
+ If a prefix is provided, it, and any prefixed substring of the
+ name beginning with the prefix can
+ be used to invoke the command. If the prefix is
+ falsy, then name is used as the prefix.
+ params.argString contains the part of the prompted
+ string after the command name. params.args is
+ params.argString split by whitespace. If the
+ command was prefixed with a
+ line range,
+ params.line and params.lineEnd will
+ be set.
+
Fix issue where trying to scroll to a horizontal position outside of the document's width could cause the gutter to be positioned incorrectly.
+
Use absolute, rather than fixed positioning in the context-menu intercept hack, to work around a problem when the editor is inside a transformed parent container.
+
Solve a problem where the horizontal scrollbar could hide text in Firefox.
+
Fix a bug that caused phantom scroll space under the text in some situations.
Incompatible:
+ The lint addon now passes the
+ editor's value as first argument to asynchronous lint functions,
+ for consistency. The editor is still passed, as fourth
+ argument.
+
Improved handling of unicode identifiers in modes for
+ languages that support them.
+
More mode
+ improvements: CoffeeScript
+ (indentation), Verilog
+ (indentation), Scala
+ (indentation, triple-quoted strings),
+ and PHP (interpolated
+ variables in heredoc strings).
Slightly incompatible:
+ The "cursorActivity"
+ event now fires after all other events for the operation (and only
+ for handlers that were actually registered at the time the
+ activity happened).
Merges the improvements from 4.1 that could
+ easily be applied to the 3.x code. Also improves the way the editor
+ size is updated when line widgets change.
CodeMirror 2 is a complete rewrite that's
+ faster, smaller, simpler to use, and less dependent on browser
+ quirks. See this
+ and this
+ for more information.
More consistent page-up/page-down behaviour
+ across browsers. Fix some issues with hidden editors looping forever
+ when line-numbers were enabled. Make PHP parser parse
+ "\\" correctly. Have jumpToLine work on
+ line handles, and add cursorLine function to fetch the
+ line handle where the cursor currently is. Add new
+ setStylesheet function to switch style-sheets in a
+ running editor.
Adds removeLine method to API.
+ Introduces the PLSQL parser.
+ Marks XML errors by adding (rather than replacing) a CSS class, so
+ that they can be disabled by modifying their style. Fixes several
+ selection bugs, and a number of small glitches.
Add support for having both line-wrapping and
+ line-numbers turned on, make paren-highlighting style customisable
+ (markParen and unmarkParen config
+ options), work around a selection bug that Opera
+ reintroduced in version 10.
Solves some issues introduced by the
+ paste-handling changes from the previous release. Adds
+ setSpellcheck, setTextWrapping,
+ setIndentUnit, setUndoDepth,
+ setTabMode, and setLineNumbers to
+ customise a running editor. Introduces an SQL parser. Fixes a few small
+ problems in the Python
+ parser. And, as usual, add workarounds for various newly discovered
+ browser incompatibilities.
Overhaul of paste-handling (less fragile), fixes for several
+ serious IE8 issues (cursor jumping, end-of-document bugs) and a number
+ of small problems.
Introduces Python
+ and Lua parsers. Add
+ setParser (on-the-fly mode changing) and
+ clearHistory methods. Make parsing passes time-based
+ instead of lines-based (see the passTime option).
So you found a problem in CodeMirror. By all means, report it! Bug
+reports from users are the main drive behind improvements to
+CodeMirror. But first, please read over these points:
+
+
+
CodeMirror is maintained by volunteers. They don't owe you
+ anything, so be polite. Reports with an indignant or belligerent
+ tone tend to be moved to the bottom of the pile.
+
+
Include information about the browser in which the
+ problem occurred. Even if you tested several browsers, and
+ the problem occurred in all of them, mention this fact in the bug
+ report. Also include browser version numbers and the operating
+ system that you're on.
+
+
Mention which release of CodeMirror you're using. Preferably,
+ try also with the current development snapshot, to ensure the
+ problem has not already been fixed.
+
+
Mention very precisely what went wrong. "X is broken" is not a
+ good bug report. What did you expect to happen? What happened
+ instead? Describe the exact steps a maintainer has to take to make
+ the problem occur. We can not fix something that we can not
+ observe.
+
+
If the problem can not be reproduced in any of the demos
+ included in the CodeMirror distribution, please provide an HTML
+ document that demonstrates the problem. The best way to do this is
+ to go to jsbin.com, enter
+ it there, press save, and include the resulting link in your bug
+ report.
There are a few things in the 2.2 release that require some care
+when upgrading.
+
+
No more default.css
+
+
The default theme is now included
+in codemirror.css, so
+you do not have to included it separately anymore. (It was tiny, so
+even if you're not using it, the extra data overhead is negligible.)
+
+
Different key customization
+
+
CodeMirror has moved to a system
+where keymaps are used to
+bind behavior to keys. This means custom
+bindings are now possible.
+
+
Three options that influenced key
+behavior, tabMode, enterMode,
+and smartHome, are no longer supported. Instead, you can
+provide custom bindings to influence the way these keys act. This is
+done through the
+new extraKeys
+option, which can hold an object mapping key names to functionality. A
+simple example would be:
Keys can be mapped either to functions, which will be given the
+editor instance as argument, or to strings, which are mapped through
+functions through the CodeMirror.commands table, which
+contains all the built-in editing commands, and can be inspected and
+extended by external code.
+
+
By default, the Home key is bound to
+the "goLineStartSmart" command, which moves the cursor to
+the first non-whitespace character on the line. You can set do this to
+make it always go to the very start instead:
+
+
extraKeys: {"Home": "goLineStart"}
+
+
Similarly, Enter is bound
+to "newlineAndIndent" by default. You can bind it to
+something else to get different behavior. To disable special handling
+completely and only get a newline character inserted, you can bind it
+to false:
+
+
extraKeys: {"Enter": false}
+
+
The same works for Tab. If you don't want CodeMirror
+to handle it, bind it to false. The default behaviour is
+to indent the current line more ("indentMore" command),
+and indent it less when shift is held ("indentLess").
+There are also "indentAuto" (smart indent)
+and "insertTab" commands provided for alternate
+behaviors. Or you can write your own handler function to do something
+different altogether.
+
+
Tabs
+
+
Handling of tabs changed completely. The display width of tabs can
+now be set with the tabSize option, and tabs can
+be styled by setting CSS rules
+for the cm-tab class.
+
+
The default width for tabs is now 4, as opposed to the 8 that is
+hard-wired into browsers. If you are relying on 8-space tabs, make
+sure you explicitly set tabSize: 8 in your options.
Version 3 does not depart too much from 2.x API, and sites that use
+CodeMirror in a very simple way might be able to upgrade without
+trouble. But it does introduce a number of incompatibilities. Please
+at least skim this text before upgrading.
+
+
Note that version 3 drops full support for Internet
+Explorer 7. The editor will mostly work on that browser, but
+it'll be significantly glitchy.
+
+
+
DOM structure
+
+
This one is the most likely to cause problems. The internal
+structure of the editor has changed quite a lot, mostly to implement a
+new scrolling model.
+
+
Editor height is now set on the outer wrapper element (CSS
+class CodeMirror), not on the scroller element
+(CodeMirror-scroll).
+
+
Other nodes were moved, dropped, and added. If you have any code
+that makes assumptions about the internal DOM structure of the editor,
+you'll have to re-test it and probably update it to work with v3.
+
+
See the styling section of the
+manual for more information.
+
+
+
Gutter model
+
+
In CodeMirror 2.x, there was a single gutter, and line markers
+created with setMarker would have to somehow coexist with
+the line numbers (if present). Version 3 allows you to specify an
+array of gutters, by class
+name,
+use setGutterMarker
+to add or remove markers in individual gutters, and clear whole
+gutters
+with clearGutter.
+Gutter markers are now specified as DOM nodes, rather than HTML
+snippets.
+
+
The gutters no longer horizontally scrolls along with the content.
+The fixedGutter option was removed (since it is now the
+only behavior).
+
+
+<style>
+ /* Define a gutter style */
+ .note-gutter { width: 3em; background: cyan; }
+</style>
+<script>
+ // Create an instance with two gutters -- line numbers and notes
+ var cm = new CodeMirror(document.body, {
+ gutters: ["note-gutter", "CodeMirror-linenumbers"],
+ lineNumbers: true
+ });
+ // Add a note to line 0
+ cm.setGutterMarker(0, "note-gutter", document.createTextNode("hi"));
+</script>
+
+
+
+
Event handling
+
+
Most of the onXYZ options have been removed. The same
+effect is now obtained by calling
+the on method with a string
+identifying the event type. Multiple handlers can now be registered
+(and individually unregistered) for an event, and objects such as line
+handlers now also expose events. See the
+full list here.
+
+
(The onKeyEvent and onDragEvent options,
+which act more as hooks than as event handlers, are still there in
+their old form.)
The markText method
+(which has gained some interesting new features, such as creating
+atomic and read-only spans, or replacing spans with widgets) no longer
+takes the CSS class name as a separate argument, but makes it an
+optional field in the options object instead.
+
+
+// Style first ten lines, and forbid the cursor from entering them
+cm.markText({line: 0, ch: 0}, {line: 10, ch: 0}, {
+ className: "magic-text",
+ inclusiveLeft: true,
+ atomic: true
+});
+
+
+
+
Line folding
+
+
The interface for hiding lines has been
+removed. markText can
+now be used to do the same in a more flexible and powerful way.
+
+
The folding script has been
+updated to use the new interface, and should now be more robust.
+
+
+// Fold a range, replacing it with the text "??"
+var range = cm.markText({line: 4, ch: 2}, {line: 8, ch: 1}, {
+ replacedWith: document.createTextNode("??"),
+ // Auto-unfold when cursor moves into the range
+ clearOnEnter: true
+});
+// Get notified when auto-unfolding
+CodeMirror.on(range, "clear", function() {
+ console.log("boom");
+});
+
+
+
+
Line CSS classes
+
+
The setLineClass method has been replaced
+by addLineClass
+and removeLineClass,
+which allow more modular control over the classes attached to a line.
All methods that take or return objects that represent screen
+positions now use {left, top, bottom, right} properties
+(not always all of them) instead of the {x, y, yBot} used
+by some methods in v2.x.
The matchBrackets
+option is no longer defined in the core editor.
+Load addon/edit/matchbrackets.js to enable it.
+
+
+
Mode management
+
+
The CodeMirror.listModes
+and CodeMirror.listMIMEs functions, used for listing
+defined modes, are gone. You are now encouraged to simply
+inspect CodeMirror.modes (mapping mode names to mode
+constructors) and CodeMirror.mimeModes (mapping MIME
+strings to mode specs).
+
+
+
New features
+
+
Some more reasons to upgrade to version 3.
+
+
+
Bi-directional text support. CodeMirror will now mostly do the
+ right thing when editing Arabic or Hebrew text.
+
Arbitrary line heights. Using fonts with different heights
+ inside the editor (whether off by one pixel or fifty) is now
+ supported and handled gracefully.
CodeMirror 4's interface is very close version 3, but it
+does fix a few awkward details in a backwards-incompatible ways. At
+least skim the text below before upgrading.
+
+
Multiple selections
+
+
The main new feature in version 4 is multiple selections. The
+single-selection variants of methods are still there, but now
+typically act only on the primary selection (usually the last
+one added).
+
+
The exception to this
+is getSelection,
+which will now return the content of all selections
+(separated by newlines, or whatever lineSep parameter you passed
+it).
+
+
+
+
The beforeSelectionChange event
+
+
This event still exists, but the object it is passed has
+a completely new
+interface, because such changes now concern multiple
+selections.
+
+
+
+
replaceSelection's collapsing behavior
+
+
By
+default, replaceSelection
+would leave the newly inserted text selected. This is only rarely what
+you want, and also (slightly) more expensive in the new model, so the
+default was changed to "end", meaning the old behavior
+must be explicitly specified by passing a second argument
+of "around".
+
+
+
+
change event data
+
+
Rather than forcing client code to follow next
+pointers from one change object to the next, the library will now
+simply fire
+multiple "change"
+events. Existing code will probably continue to work unmodified.
+
+
+
+
showIfHidden option to line widgets
+
+
This option, which conceptually caused line widgets to be visible
+even if their line was hidden, was never really well-defined, and was
+buggy from the start. It would be a rather expensive feature, both in
+code complexity and run-time performance, to implement properly. It
+has been dropped entirely in 4.0.
+
+
+
+
Module loaders
+
+
All modules in the CodeMirror distribution are now wrapped in a
+shim function to make them compatible with both AMD
+(requirejs) and CommonJS (as used
+by node
+and browserify) module loaders.
+When neither of these is present, they fall back to simply using the
+global CodeMirror variable.
+
+
If you have a module loader present in your environment, CodeMirror
+will attempt to use it, and you might need to change the way you load
+CodeMirror modules.
+
+
+
+
Mutating shared data structures
+
+
Data structures produced by the library should not be mutated
+unless explicitly allowed, in general. This is slightly more strict in
+4.0 than it was in earlier versions, which copied the position objects
+returned by getCursor
+for nebulous, historic reasons. In 4.0, mutating these
+objects will corrupt your editor's selection.
+
+
+
+
Deprecated interfaces dropped
+
+
A few properties and methods that have been deprecated for a while
+are now gone. Most notably, the onKeyEvent
+and onDragEvent options (use the
+corresponding events instead).
+
+
Two silly methods, which were mostly there to stay close to the 0.x
+API, setLine and removeLine are now gone.
+Use the more
+flexible replaceRange
+method instead.
+
+
The long names for folding and completing functions
+(CodeMirror.braceRangeFinder, CodeMirror.javascriptHint,
+etc) are also gone
+(use CodeMirror.fold.brace, CodeMirror.hint.javascript).
+
+
The className property in the return value
+of getTokenAt, which
+has been superseded by the type property, is also no
+longer present.
CodeMirror is a versatile text editor
+ implemented in JavaScript for the browser. It is specialized for
+ editing code, and comes with a number of language modes and addons
+ that implement more advanced editing functionality.
+
+
A rich programming API and a
+ CSS theming system are
+ available for customizing CodeMirror to fit your application, and
+ extending it with new functionality.
Development and bug tracking happens
+ on github
+ (alternate git
+ repository).
+ Please read these
+ pointers before submitting a bug. Use pull requests to submit
+ patches. All contributions must be released under the same MIT
+ license that CodeMirror uses.
+
+
Discussion around the project is done on
+ a discussion forum.
+ There is also
+ the codemirror-announce
+ list, which is only used for major announcements (such as new
+ versions). If needed, you can
+ contact the maintainer
+ directly. We aim to be an inclusive, welcoming community. To make
+ that explicit, we have
+ a code of
+ conduct that applies to communication around the project.
+
+
A list of CodeMirror-related software that is not part of the
+ main distribution is maintained
+ on our
+ wiki. Feel free to add your project.
+
+
+
+
Browser support
+
The desktop versions of the following browsers,
+ in standards mode (HTML5 <!doctype html>
+ recommended) are supported:
+
+
Firefox
version 4 and up
+
Chrome
any version
+
Safari
version 5.2 and up
+
Internet Explorer
version 8 and up
+
Opera
version 9 and up
+
+
Support for modern mobile browsers is experimental. Recent
+ versions of the iOS browser and Chrome on Android should work
+ pretty well.
+
+
+
diff --git a/www/code/codemirror-5.13.2/keymap/emacs.js b/www/code/codemirror-5.13.2/keymap/emacs.js
new file mode 100644
index 000000000..3eec1e576
--- /dev/null
+++ b/www/code/codemirror-5.13.2/keymap/emacs.js
@@ -0,0 +1,412 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ var Pos = CodeMirror.Pos;
+ function posEq(a, b) { return a.line == b.line && a.ch == b.ch; }
+
+ // Kill 'ring'
+
+ var killRing = [];
+ function addToRing(str) {
+ killRing.push(str);
+ if (killRing.length > 50) killRing.shift();
+ }
+ function growRingTop(str) {
+ if (!killRing.length) return addToRing(str);
+ killRing[killRing.length - 1] += str;
+ }
+ function getFromRing(n) { return killRing[killRing.length - (n ? Math.min(n, 1) : 1)] || ""; }
+ function popFromRing() { if (killRing.length > 1) killRing.pop(); return getFromRing(); }
+
+ var lastKill = null;
+
+ function kill(cm, from, to, mayGrow, text) {
+ if (text == null) text = cm.getRange(from, to);
+
+ if (mayGrow && lastKill && lastKill.cm == cm && posEq(from, lastKill.pos) && cm.isClean(lastKill.gen))
+ growRingTop(text);
+ else
+ addToRing(text);
+ cm.replaceRange("", from, to, "+delete");
+
+ if (mayGrow) lastKill = {cm: cm, pos: from, gen: cm.changeGeneration()};
+ else lastKill = null;
+ }
+
+ // Boundaries of various units
+
+ function byChar(cm, pos, dir) {
+ return cm.findPosH(pos, dir, "char", true);
+ }
+
+ function byWord(cm, pos, dir) {
+ return cm.findPosH(pos, dir, "word", true);
+ }
+
+ function byLine(cm, pos, dir) {
+ return cm.findPosV(pos, dir, "line", cm.doc.sel.goalColumn);
+ }
+
+ function byPage(cm, pos, dir) {
+ return cm.findPosV(pos, dir, "page", cm.doc.sel.goalColumn);
+ }
+
+ function byParagraph(cm, pos, dir) {
+ var no = pos.line, line = cm.getLine(no);
+ var sawText = /\S/.test(dir < 0 ? line.slice(0, pos.ch) : line.slice(pos.ch));
+ var fst = cm.firstLine(), lst = cm.lastLine();
+ for (;;) {
+ no += dir;
+ if (no < fst || no > lst)
+ return cm.clipPos(Pos(no - dir, dir < 0 ? 0 : null));
+ line = cm.getLine(no);
+ var hasText = /\S/.test(line);
+ if (hasText) sawText = true;
+ else if (sawText) return Pos(no, 0);
+ }
+ }
+
+ function bySentence(cm, pos, dir) {
+ var line = pos.line, ch = pos.ch;
+ var text = cm.getLine(pos.line), sawWord = false;
+ for (;;) {
+ var next = text.charAt(ch + (dir < 0 ? -1 : 0));
+ if (!next) { // End/beginning of line reached
+ if (line == (dir < 0 ? cm.firstLine() : cm.lastLine())) return Pos(line, ch);
+ text = cm.getLine(line + dir);
+ if (!/\S/.test(text)) return Pos(line, ch);
+ line += dir;
+ ch = dir < 0 ? text.length : 0;
+ continue;
+ }
+ if (sawWord && /[!?.]/.test(next)) return Pos(line, ch + (dir > 0 ? 1 : 0));
+ if (!sawWord) sawWord = /\w/.test(next);
+ ch += dir;
+ }
+ }
+
+ function byExpr(cm, pos, dir) {
+ var wrap;
+ if (cm.findMatchingBracket && (wrap = cm.findMatchingBracket(pos, true))
+ && wrap.match && (wrap.forward ? 1 : -1) == dir)
+ return dir > 0 ? Pos(wrap.to.line, wrap.to.ch + 1) : wrap.to;
+
+ for (var first = true;; first = false) {
+ var token = cm.getTokenAt(pos);
+ var after = Pos(pos.line, dir < 0 ? token.start : token.end);
+ if (first && dir > 0 && token.end == pos.ch || !/\w/.test(token.string)) {
+ var newPos = cm.findPosH(after, dir, "char");
+ if (posEq(after, newPos)) return pos;
+ else pos = newPos;
+ } else {
+ return after;
+ }
+ }
+ }
+
+ // Prefixes (only crudely supported)
+
+ function getPrefix(cm, precise) {
+ var digits = cm.state.emacsPrefix;
+ if (!digits) return precise ? null : 1;
+ clearPrefix(cm);
+ return digits == "-" ? -1 : Number(digits);
+ }
+
+ function repeated(cmd) {
+ var f = typeof cmd == "string" ? function(cm) { cm.execCommand(cmd); } : cmd;
+ return function(cm) {
+ var prefix = getPrefix(cm);
+ f(cm);
+ for (var i = 1; i < prefix; ++i) f(cm);
+ };
+ }
+
+ function findEnd(cm, pos, by, dir) {
+ var prefix = getPrefix(cm);
+ if (prefix < 0) { dir = -dir; prefix = -prefix; }
+ for (var i = 0; i < prefix; ++i) {
+ var newPos = by(cm, pos, dir);
+ if (posEq(newPos, pos)) break;
+ pos = newPos;
+ }
+ return pos;
+ }
+
+ function move(by, dir) {
+ var f = function(cm) {
+ cm.extendSelection(findEnd(cm, cm.getCursor(), by, dir));
+ };
+ f.motion = true;
+ return f;
+ }
+
+ function killTo(cm, by, dir) {
+ var selections = cm.listSelections(), cursor;
+ var i = selections.length;
+ while (i--) {
+ cursor = selections[i].head;
+ kill(cm, cursor, findEnd(cm, cursor, by, dir), true);
+ }
+ }
+
+ function killRegion(cm) {
+ if (cm.somethingSelected()) {
+ var selections = cm.listSelections(), selection;
+ var i = selections.length;
+ while (i--) {
+ selection = selections[i];
+ kill(cm, selection.anchor, selection.head);
+ }
+ return true;
+ }
+ }
+
+ function addPrefix(cm, digit) {
+ if (cm.state.emacsPrefix) {
+ if (digit != "-") cm.state.emacsPrefix += digit;
+ return;
+ }
+ // Not active yet
+ cm.state.emacsPrefix = digit;
+ cm.on("keyHandled", maybeClearPrefix);
+ cm.on("inputRead", maybeDuplicateInput);
+ }
+
+ var prefixPreservingKeys = {"Alt-G": true, "Ctrl-X": true, "Ctrl-Q": true, "Ctrl-U": true};
+
+ function maybeClearPrefix(cm, arg) {
+ if (!cm.state.emacsPrefixMap && !prefixPreservingKeys.hasOwnProperty(arg))
+ clearPrefix(cm);
+ }
+
+ function clearPrefix(cm) {
+ cm.state.emacsPrefix = null;
+ cm.off("keyHandled", maybeClearPrefix);
+ cm.off("inputRead", maybeDuplicateInput);
+ }
+
+ function maybeDuplicateInput(cm, event) {
+ var dup = getPrefix(cm);
+ if (dup > 1 && event.origin == "+input") {
+ var one = event.text.join("\n"), txt = "";
+ for (var i = 1; i < dup; ++i) txt += one;
+ cm.replaceSelection(txt);
+ }
+ }
+
+ function addPrefixMap(cm) {
+ cm.state.emacsPrefixMap = true;
+ cm.addKeyMap(prefixMap);
+ cm.on("keyHandled", maybeRemovePrefixMap);
+ cm.on("inputRead", maybeRemovePrefixMap);
+ }
+
+ function maybeRemovePrefixMap(cm, arg) {
+ if (typeof arg == "string" && (/^\d$/.test(arg) || arg == "Ctrl-U")) return;
+ cm.removeKeyMap(prefixMap);
+ cm.state.emacsPrefixMap = false;
+ cm.off("keyHandled", maybeRemovePrefixMap);
+ cm.off("inputRead", maybeRemovePrefixMap);
+ }
+
+ // Utilities
+
+ function setMark(cm) {
+ cm.setCursor(cm.getCursor());
+ cm.setExtending(!cm.getExtending());
+ cm.on("change", function() { cm.setExtending(false); });
+ }
+
+ function clearMark(cm) {
+ cm.setExtending(false);
+ cm.setCursor(cm.getCursor());
+ }
+
+ function getInput(cm, msg, f) {
+ if (cm.openDialog)
+ cm.openDialog(msg + ": ", f, {bottom: true});
+ else
+ f(prompt(msg, ""));
+ }
+
+ function operateOnWord(cm, op) {
+ var start = cm.getCursor(), end = cm.findPosH(start, 1, "word");
+ cm.replaceRange(op(cm.getRange(start, end)), start, end);
+ cm.setCursor(end);
+ }
+
+ function toEnclosingExpr(cm) {
+ var pos = cm.getCursor(), line = pos.line, ch = pos.ch;
+ var stack = [];
+ while (line >= cm.firstLine()) {
+ var text = cm.getLine(line);
+ for (var i = ch == null ? text.length : ch; i > 0;) {
+ var ch = text.charAt(--i);
+ if (ch == ")")
+ stack.push("(");
+ else if (ch == "]")
+ stack.push("[");
+ else if (ch == "}")
+ stack.push("{");
+ else if (/[\(\{\[]/.test(ch) && (!stack.length || stack.pop() != ch))
+ return cm.extendSelection(Pos(line, i));
+ }
+ --line; ch = null;
+ }
+ }
+
+ function quit(cm) {
+ cm.execCommand("clearSearch");
+ clearMark(cm);
+ }
+
+ // Actual keymap
+
+ var keyMap = CodeMirror.keyMap.emacs = CodeMirror.normalizeKeyMap({
+ "Ctrl-W": function(cm) {kill(cm, cm.getCursor("start"), cm.getCursor("end"));},
+ "Ctrl-K": repeated(function(cm) {
+ var start = cm.getCursor(), end = cm.clipPos(Pos(start.line));
+ var text = cm.getRange(start, end);
+ if (!/\S/.test(text)) {
+ text += "\n";
+ end = Pos(start.line + 1, 0);
+ }
+ kill(cm, start, end, true, text);
+ }),
+ "Alt-W": function(cm) {
+ addToRing(cm.getSelection());
+ clearMark(cm);
+ },
+ "Ctrl-Y": function(cm) {
+ var start = cm.getCursor();
+ cm.replaceRange(getFromRing(getPrefix(cm)), start, start, "paste");
+ cm.setSelection(start, cm.getCursor());
+ },
+ "Alt-Y": function(cm) {cm.replaceSelection(popFromRing(), "around", "paste");},
+
+ "Ctrl-Space": setMark, "Ctrl-Shift-2": setMark,
+
+ "Ctrl-F": move(byChar, 1), "Ctrl-B": move(byChar, -1),
+ "Right": move(byChar, 1), "Left": move(byChar, -1),
+ "Ctrl-D": function(cm) { killTo(cm, byChar, 1); },
+ "Delete": function(cm) { killRegion(cm) || killTo(cm, byChar, 1); },
+ "Ctrl-H": function(cm) { killTo(cm, byChar, -1); },
+ "Backspace": function(cm) { killRegion(cm) || killTo(cm, byChar, -1); },
+
+ "Alt-F": move(byWord, 1), "Alt-B": move(byWord, -1),
+ "Alt-D": function(cm) { killTo(cm, byWord, 1); },
+ "Alt-Backspace": function(cm) { killTo(cm, byWord, -1); },
+
+ "Ctrl-N": move(byLine, 1), "Ctrl-P": move(byLine, -1),
+ "Down": move(byLine, 1), "Up": move(byLine, -1),
+ "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
+ "End": "goLineEnd", "Home": "goLineStart",
+
+ "Alt-V": move(byPage, -1), "Ctrl-V": move(byPage, 1),
+ "PageUp": move(byPage, -1), "PageDown": move(byPage, 1),
+
+ "Ctrl-Up": move(byParagraph, -1), "Ctrl-Down": move(byParagraph, 1),
+
+ "Alt-A": move(bySentence, -1), "Alt-E": move(bySentence, 1),
+ "Alt-K": function(cm) { killTo(cm, bySentence, 1); },
+
+ "Ctrl-Alt-K": function(cm) { killTo(cm, byExpr, 1); },
+ "Ctrl-Alt-Backspace": function(cm) { killTo(cm, byExpr, -1); },
+ "Ctrl-Alt-F": move(byExpr, 1), "Ctrl-Alt-B": move(byExpr, -1),
+
+ "Shift-Ctrl-Alt-2": function(cm) {
+ var cursor = cm.getCursor();
+ cm.setSelection(findEnd(cm, cursor, byExpr, 1), cursor);
+ },
+ "Ctrl-Alt-T": function(cm) {
+ var leftStart = byExpr(cm, cm.getCursor(), -1), leftEnd = byExpr(cm, leftStart, 1);
+ var rightEnd = byExpr(cm, leftEnd, 1), rightStart = byExpr(cm, rightEnd, -1);
+ cm.replaceRange(cm.getRange(rightStart, rightEnd) + cm.getRange(leftEnd, rightStart) +
+ cm.getRange(leftStart, leftEnd), leftStart, rightEnd);
+ },
+ "Ctrl-Alt-U": repeated(toEnclosingExpr),
+
+ "Alt-Space": function(cm) {
+ var pos = cm.getCursor(), from = pos.ch, to = pos.ch, text = cm.getLine(pos.line);
+ while (from && /\s/.test(text.charAt(from - 1))) --from;
+ while (to < text.length && /\s/.test(text.charAt(to))) ++to;
+ cm.replaceRange(" ", Pos(pos.line, from), Pos(pos.line, to));
+ },
+ "Ctrl-O": repeated(function(cm) { cm.replaceSelection("\n", "start"); }),
+ "Ctrl-T": repeated(function(cm) {
+ cm.execCommand("transposeChars");
+ }),
+
+ "Alt-C": repeated(function(cm) {
+ operateOnWord(cm, function(w) {
+ var letter = w.search(/\w/);
+ if (letter == -1) return w;
+ return w.slice(0, letter) + w.charAt(letter).toUpperCase() + w.slice(letter + 1).toLowerCase();
+ });
+ }),
+ "Alt-U": repeated(function(cm) {
+ operateOnWord(cm, function(w) { return w.toUpperCase(); });
+ }),
+ "Alt-L": repeated(function(cm) {
+ operateOnWord(cm, function(w) { return w.toLowerCase(); });
+ }),
+
+ "Alt-;": "toggleComment",
+
+ "Ctrl-/": repeated("undo"), "Shift-Ctrl--": repeated("undo"),
+ "Ctrl-Z": repeated("undo"), "Cmd-Z": repeated("undo"),
+ "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd",
+ "Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": quit, "Shift-Alt-5": "replace",
+ "Alt-/": "autocomplete",
+ "Ctrl-J": "newlineAndIndent", "Enter": false, "Tab": "indentAuto",
+
+ "Alt-G G": function(cm) {
+ var prefix = getPrefix(cm, true);
+ if (prefix != null && prefix > 0) return cm.setCursor(prefix - 1);
+
+ getInput(cm, "Goto line", function(str) {
+ var num;
+ if (str && !isNaN(num = Number(str)) && num == (num|0) && num > 0)
+ cm.setCursor(num - 1);
+ });
+ },
+
+ "Ctrl-X Tab": function(cm) {
+ cm.indentSelection(getPrefix(cm, true) || cm.getOption("indentUnit"));
+ },
+ "Ctrl-X Ctrl-X": function(cm) {
+ cm.setSelection(cm.getCursor("head"), cm.getCursor("anchor"));
+ },
+ "Ctrl-X Ctrl-S": "save",
+ "Ctrl-X Ctrl-W": "save",
+ "Ctrl-X S": "saveAll",
+ "Ctrl-X F": "open",
+ "Ctrl-X U": repeated("undo"),
+ "Ctrl-X K": "close",
+ "Ctrl-X Delete": function(cm) { kill(cm, cm.getCursor(), bySentence(cm, cm.getCursor(), 1), true); },
+ "Ctrl-X H": "selectAll",
+
+ "Ctrl-Q Tab": repeated("insertTab"),
+ "Ctrl-U": addPrefixMap
+ });
+
+ var prefixMap = {"Ctrl-G": clearPrefix};
+ function regPrefix(d) {
+ prefixMap[d] = function(cm) { addPrefix(cm, d); };
+ keyMap["Ctrl-" + d] = function(cm) { addPrefix(cm, d); };
+ prefixPreservingKeys["Ctrl-" + d] = true;
+ }
+ for (var i = 0; i < 10; ++i) regPrefix(String(i));
+ regPrefix("-");
+});
diff --git a/www/code/codemirror-5.13.2/keymap/sublime.js b/www/code/codemirror-5.13.2/keymap/sublime.js
new file mode 100644
index 000000000..49db3b871
--- /dev/null
+++ b/www/code/codemirror-5.13.2/keymap/sublime.js
@@ -0,0 +1,569 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+// A rough approximation of Sublime Text's keybindings
+// Depends on addon/search/searchcursor.js and optionally addon/dialog/dialogs.js
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../lib/codemirror"), require("../addon/search/searchcursor"), require("../addon/edit/matchbrackets"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/edit/matchbrackets"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ var map = CodeMirror.keyMap.sublime = {fallthrough: "default"};
+ var cmds = CodeMirror.commands;
+ var Pos = CodeMirror.Pos;
+ var mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault;
+ var ctrl = mac ? "Cmd-" : "Ctrl-";
+
+ // This is not exactly Sublime's algorithm. I couldn't make heads or tails of that.
+ function findPosSubword(doc, start, dir) {
+ if (dir < 0 && start.ch == 0) return doc.clipPos(Pos(start.line - 1));
+ var line = doc.getLine(start.line);
+ if (dir > 0 && start.ch >= line.length) return doc.clipPos(Pos(start.line + 1, 0));
+ var state = "start", type;
+ for (var pos = start.ch, e = dir < 0 ? 0 : line.length, i = 0; pos != e; pos += dir, i++) {
+ var next = line.charAt(dir < 0 ? pos - 1 : pos);
+ var cat = next != "_" && CodeMirror.isWordChar(next) ? "w" : "o";
+ if (cat == "w" && next.toUpperCase() == next) cat = "W";
+ if (state == "start") {
+ if (cat != "o") { state = "in"; type = cat; }
+ } else if (state == "in") {
+ if (type != cat) {
+ if (type == "w" && cat == "W" && dir < 0) pos--;
+ if (type == "W" && cat == "w" && dir > 0) { type = "w"; continue; }
+ break;
+ }
+ }
+ }
+ return Pos(start.line, pos);
+ }
+
+ function moveSubword(cm, dir) {
+ cm.extendSelectionsBy(function(range) {
+ if (cm.display.shift || cm.doc.extend || range.empty())
+ return findPosSubword(cm.doc, range.head, dir);
+ else
+ return dir < 0 ? range.from() : range.to();
+ });
+ }
+
+ cmds[map["Alt-Left"] = "goSubwordLeft"] = function(cm) { moveSubword(cm, -1); };
+ cmds[map["Alt-Right"] = "goSubwordRight"] = function(cm) { moveSubword(cm, 1); };
+
+ var scrollLineCombo = mac ? "Ctrl-Alt-" : "Ctrl-";
+
+ cmds[map[scrollLineCombo + "Up"] = "scrollLineUp"] = function(cm) {
+ var info = cm.getScrollInfo();
+ if (!cm.somethingSelected()) {
+ var visibleBottomLine = cm.lineAtHeight(info.top + info.clientHeight, "local");
+ if (cm.getCursor().line >= visibleBottomLine)
+ cm.execCommand("goLineUp");
+ }
+ cm.scrollTo(null, info.top - cm.defaultTextHeight());
+ };
+ cmds[map[scrollLineCombo + "Down"] = "scrollLineDown"] = function(cm) {
+ var info = cm.getScrollInfo();
+ if (!cm.somethingSelected()) {
+ var visibleTopLine = cm.lineAtHeight(info.top, "local")+1;
+ if (cm.getCursor().line <= visibleTopLine)
+ cm.execCommand("goLineDown");
+ }
+ cm.scrollTo(null, info.top + cm.defaultTextHeight());
+ };
+
+ cmds[map["Shift-" + ctrl + "L"] = "splitSelectionByLine"] = function(cm) {
+ var ranges = cm.listSelections(), lineRanges = [];
+ for (var i = 0; i < ranges.length; i++) {
+ var from = ranges[i].from(), to = ranges[i].to();
+ for (var line = from.line; line <= to.line; ++line)
+ if (!(to.line > from.line && line == to.line && to.ch == 0))
+ lineRanges.push({anchor: line == from.line ? from : Pos(line, 0),
+ head: line == to.line ? to : Pos(line)});
+ }
+ cm.setSelections(lineRanges, 0);
+ };
+
+ map["Shift-Tab"] = "indentLess";
+
+ cmds[map["Esc"] = "singleSelectionTop"] = function(cm) {
+ var range = cm.listSelections()[0];
+ cm.setSelection(range.anchor, range.head, {scroll: false});
+ };
+
+ cmds[map[ctrl + "L"] = "selectLine"] = function(cm) {
+ var ranges = cm.listSelections(), extended = [];
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i];
+ extended.push({anchor: Pos(range.from().line, 0),
+ head: Pos(range.to().line + 1, 0)});
+ }
+ cm.setSelections(extended);
+ };
+
+ map["Shift-Ctrl-K"] = "deleteLine";
+
+ function insertLine(cm, above) {
+ if (cm.isReadOnly()) return CodeMirror.Pass
+ cm.operation(function() {
+ var len = cm.listSelections().length, newSelection = [], last = -1;
+ for (var i = 0; i < len; i++) {
+ var head = cm.listSelections()[i].head;
+ if (head.line <= last) continue;
+ var at = Pos(head.line + (above ? 0 : 1), 0);
+ cm.replaceRange("\n", at, null, "+insertLine");
+ cm.indentLine(at.line, null, true);
+ newSelection.push({head: at, anchor: at});
+ last = head.line + 1;
+ }
+ cm.setSelections(newSelection);
+ });
+ }
+
+ cmds[map[ctrl + "Enter"] = "insertLineAfter"] = function(cm) { return insertLine(cm, false); };
+
+ cmds[map["Shift-" + ctrl + "Enter"] = "insertLineBefore"] = function(cm) { return insertLine(cm, true); };
+
+ function wordAt(cm, pos) {
+ var start = pos.ch, end = start, line = cm.getLine(pos.line);
+ while (start && CodeMirror.isWordChar(line.charAt(start - 1))) --start;
+ while (end < line.length && CodeMirror.isWordChar(line.charAt(end))) ++end;
+ return {from: Pos(pos.line, start), to: Pos(pos.line, end), word: line.slice(start, end)};
+ }
+
+ cmds[map[ctrl + "D"] = "selectNextOccurrence"] = function(cm) {
+ var from = cm.getCursor("from"), to = cm.getCursor("to");
+ var fullWord = cm.state.sublimeFindFullWord == cm.doc.sel;
+ if (CodeMirror.cmpPos(from, to) == 0) {
+ var word = wordAt(cm, from);
+ if (!word.word) return;
+ cm.setSelection(word.from, word.to);
+ fullWord = true;
+ } else {
+ var text = cm.getRange(from, to);
+ var query = fullWord ? new RegExp("\\b" + text + "\\b") : text;
+ var cur = cm.getSearchCursor(query, to);
+ if (cur.findNext()) {
+ cm.addSelection(cur.from(), cur.to());
+ } else {
+ cur = cm.getSearchCursor(query, Pos(cm.firstLine(), 0));
+ if (cur.findNext())
+ cm.addSelection(cur.from(), cur.to());
+ }
+ }
+ if (fullWord)
+ cm.state.sublimeFindFullWord = cm.doc.sel;
+ };
+
+ var mirror = "(){}[]";
+ function selectBetweenBrackets(cm) {
+ var pos = cm.getCursor(), opening = cm.scanForBracket(pos, -1);
+ if (!opening) return;
+ for (;;) {
+ var closing = cm.scanForBracket(pos, 1);
+ if (!closing) return;
+ if (closing.ch == mirror.charAt(mirror.indexOf(opening.ch) + 1)) {
+ cm.setSelection(Pos(opening.pos.line, opening.pos.ch + 1), closing.pos, false);
+ return true;
+ }
+ pos = Pos(closing.pos.line, closing.pos.ch + 1);
+ }
+ }
+
+ cmds[map["Shift-" + ctrl + "Space"] = "selectScope"] = function(cm) {
+ selectBetweenBrackets(cm) || cm.execCommand("selectAll");
+ };
+ cmds[map["Shift-" + ctrl + "M"] = "selectBetweenBrackets"] = function(cm) {
+ if (!selectBetweenBrackets(cm)) return CodeMirror.Pass;
+ };
+
+ cmds[map[ctrl + "M"] = "goToBracket"] = function(cm) {
+ cm.extendSelectionsBy(function(range) {
+ var next = cm.scanForBracket(range.head, 1);
+ if (next && CodeMirror.cmpPos(next.pos, range.head) != 0) return next.pos;
+ var prev = cm.scanForBracket(range.head, -1);
+ return prev && Pos(prev.pos.line, prev.pos.ch + 1) || range.head;
+ });
+ };
+
+ var swapLineCombo = mac ? "Cmd-Ctrl-" : "Shift-Ctrl-";
+
+ cmds[map[swapLineCombo + "Up"] = "swapLineUp"] = function(cm) {
+ if (cm.isReadOnly()) return CodeMirror.Pass
+ var ranges = cm.listSelections(), linesToMove = [], at = cm.firstLine() - 1, newSels = [];
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i], from = range.from().line - 1, to = range.to().line;
+ newSels.push({anchor: Pos(range.anchor.line - 1, range.anchor.ch),
+ head: Pos(range.head.line - 1, range.head.ch)});
+ if (range.to().ch == 0 && !range.empty()) --to;
+ if (from > at) linesToMove.push(from, to);
+ else if (linesToMove.length) linesToMove[linesToMove.length - 1] = to;
+ at = to;
+ }
+ cm.operation(function() {
+ for (var i = 0; i < linesToMove.length; i += 2) {
+ var from = linesToMove[i], to = linesToMove[i + 1];
+ var line = cm.getLine(from);
+ cm.replaceRange("", Pos(from, 0), Pos(from + 1, 0), "+swapLine");
+ if (to > cm.lastLine())
+ cm.replaceRange("\n" + line, Pos(cm.lastLine()), null, "+swapLine");
+ else
+ cm.replaceRange(line + "\n", Pos(to, 0), null, "+swapLine");
+ }
+ cm.setSelections(newSels);
+ cm.scrollIntoView();
+ });
+ };
+
+ cmds[map[swapLineCombo + "Down"] = "swapLineDown"] = function(cm) {
+ if (cm.isReadOnly()) return CodeMirror.Pass
+ var ranges = cm.listSelections(), linesToMove = [], at = cm.lastLine() + 1;
+ for (var i = ranges.length - 1; i >= 0; i--) {
+ var range = ranges[i], from = range.to().line + 1, to = range.from().line;
+ if (range.to().ch == 0 && !range.empty()) from--;
+ if (from < at) linesToMove.push(from, to);
+ else if (linesToMove.length) linesToMove[linesToMove.length - 1] = to;
+ at = to;
+ }
+ cm.operation(function() {
+ for (var i = linesToMove.length - 2; i >= 0; i -= 2) {
+ var from = linesToMove[i], to = linesToMove[i + 1];
+ var line = cm.getLine(from);
+ if (from == cm.lastLine())
+ cm.replaceRange("", Pos(from - 1), Pos(from), "+swapLine");
+ else
+ cm.replaceRange("", Pos(from, 0), Pos(from + 1, 0), "+swapLine");
+ cm.replaceRange(line + "\n", Pos(to, 0), null, "+swapLine");
+ }
+ cm.scrollIntoView();
+ });
+ };
+
+ cmds[map[ctrl + "/"] = "toggleCommentIndented"] = function(cm) {
+ cm.toggleComment({ indent: true });
+ }
+
+ cmds[map[ctrl + "J"] = "joinLines"] = function(cm) {
+ var ranges = cm.listSelections(), joined = [];
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i], from = range.from();
+ var start = from.line, end = range.to().line;
+ while (i < ranges.length - 1 && ranges[i + 1].from().line == end)
+ end = ranges[++i].to().line;
+ joined.push({start: start, end: end, anchor: !range.empty() && from});
+ }
+ cm.operation(function() {
+ var offset = 0, ranges = [];
+ for (var i = 0; i < joined.length; i++) {
+ var obj = joined[i];
+ var anchor = obj.anchor && Pos(obj.anchor.line - offset, obj.anchor.ch), head;
+ for (var line = obj.start; line <= obj.end; line++) {
+ var actual = line - offset;
+ if (line == obj.end) head = Pos(actual, cm.getLine(actual).length + 1);
+ if (actual < cm.lastLine()) {
+ cm.replaceRange(" ", Pos(actual), Pos(actual + 1, /^\s*/.exec(cm.getLine(actual + 1))[0].length));
+ ++offset;
+ }
+ }
+ ranges.push({anchor: anchor || head, head: head});
+ }
+ cm.setSelections(ranges, 0);
+ });
+ };
+
+ cmds[map["Shift-" + ctrl + "D"] = "duplicateLine"] = function(cm) {
+ cm.operation(function() {
+ var rangeCount = cm.listSelections().length;
+ for (var i = 0; i < rangeCount; i++) {
+ var range = cm.listSelections()[i];
+ if (range.empty())
+ cm.replaceRange(cm.getLine(range.head.line) + "\n", Pos(range.head.line, 0));
+ else
+ cm.replaceRange(cm.getRange(range.from(), range.to()), range.from());
+ }
+ cm.scrollIntoView();
+ });
+ };
+
+ map[ctrl + "T"] = "transposeChars";
+
+ function sortLines(cm, caseSensitive) {
+ if (cm.isReadOnly()) return CodeMirror.Pass
+ var ranges = cm.listSelections(), toSort = [], selected;
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i];
+ if (range.empty()) continue;
+ var from = range.from().line, to = range.to().line;
+ while (i < ranges.length - 1 && ranges[i + 1].from().line == to)
+ to = range[++i].to().line;
+ toSort.push(from, to);
+ }
+ if (toSort.length) selected = true;
+ else toSort.push(cm.firstLine(), cm.lastLine());
+
+ cm.operation(function() {
+ var ranges = [];
+ for (var i = 0; i < toSort.length; i += 2) {
+ var from = toSort[i], to = toSort[i + 1];
+ var start = Pos(from, 0), end = Pos(to);
+ var lines = cm.getRange(start, end, false);
+ if (caseSensitive)
+ lines.sort();
+ else
+ lines.sort(function(a, b) {
+ var au = a.toUpperCase(), bu = b.toUpperCase();
+ if (au != bu) { a = au; b = bu; }
+ return a < b ? -1 : a == b ? 0 : 1;
+ });
+ cm.replaceRange(lines, start, end);
+ if (selected) ranges.push({anchor: start, head: end});
+ }
+ if (selected) cm.setSelections(ranges, 0);
+ });
+ }
+
+ cmds[map["F9"] = "sortLines"] = function(cm) { sortLines(cm, true); };
+ cmds[map[ctrl + "F9"] = "sortLinesInsensitive"] = function(cm) { sortLines(cm, false); };
+
+ cmds[map["F2"] = "nextBookmark"] = function(cm) {
+ var marks = cm.state.sublimeBookmarks;
+ if (marks) while (marks.length) {
+ var current = marks.shift();
+ var found = current.find();
+ if (found) {
+ marks.push(current);
+ return cm.setSelection(found.from, found.to);
+ }
+ }
+ };
+
+ cmds[map["Shift-F2"] = "prevBookmark"] = function(cm) {
+ var marks = cm.state.sublimeBookmarks;
+ if (marks) while (marks.length) {
+ marks.unshift(marks.pop());
+ var found = marks[marks.length - 1].find();
+ if (!found)
+ marks.pop();
+ else
+ return cm.setSelection(found.from, found.to);
+ }
+ };
+
+ cmds[map[ctrl + "F2"] = "toggleBookmark"] = function(cm) {
+ var ranges = cm.listSelections();
+ var marks = cm.state.sublimeBookmarks || (cm.state.sublimeBookmarks = []);
+ for (var i = 0; i < ranges.length; i++) {
+ var from = ranges[i].from(), to = ranges[i].to();
+ var found = cm.findMarks(from, to);
+ for (var j = 0; j < found.length; j++) {
+ if (found[j].sublimeBookmark) {
+ found[j].clear();
+ for (var k = 0; k < marks.length; k++)
+ if (marks[k] == found[j])
+ marks.splice(k--, 1);
+ break;
+ }
+ }
+ if (j == found.length)
+ marks.push(cm.markText(from, to, {sublimeBookmark: true, clearWhenEmpty: false}));
+ }
+ };
+
+ cmds[map["Shift-" + ctrl + "F2"] = "clearBookmarks"] = function(cm) {
+ var marks = cm.state.sublimeBookmarks;
+ if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear();
+ marks.length = 0;
+ };
+
+ cmds[map["Alt-F2"] = "selectBookmarks"] = function(cm) {
+ var marks = cm.state.sublimeBookmarks, ranges = [];
+ if (marks) for (var i = 0; i < marks.length; i++) {
+ var found = marks[i].find();
+ if (!found)
+ marks.splice(i--, 0);
+ else
+ ranges.push({anchor: found.from, head: found.to});
+ }
+ if (ranges.length)
+ cm.setSelections(ranges, 0);
+ };
+
+ map["Alt-Q"] = "wrapLines";
+
+ var cK = ctrl + "K ";
+
+ function modifyWordOrSelection(cm, mod) {
+ cm.operation(function() {
+ var ranges = cm.listSelections(), indices = [], replacements = [];
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i];
+ if (range.empty()) { indices.push(i); replacements.push(""); }
+ else replacements.push(mod(cm.getRange(range.from(), range.to())));
+ }
+ cm.replaceSelections(replacements, "around", "case");
+ for (var i = indices.length - 1, at; i >= 0; i--) {
+ var range = ranges[indices[i]];
+ if (at && CodeMirror.cmpPos(range.head, at) > 0) continue;
+ var word = wordAt(cm, range.head);
+ at = word.from;
+ cm.replaceRange(mod(word.word), word.from, word.to);
+ }
+ });
+ }
+
+ 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"));
+ var indentUnit = cm.getOption("indentUnit");
+
+ if (toStartOfLine && !/\S/.test(toStartOfLine) && column % indentUnit == 0) {
+ var prevIndent = new Pos(cursor.line,
+ CodeMirror.findColumn(toStartOfLine, column - indentUnit, indentUnit));
+
+ // If no smart delete is happening (due to tab sizing) just do a regular delete
+ if (prevIndent.ch == cursor.ch) return CodeMirror.Pass;
+
+ return cm.replaceRange("", prevIndent, cursor, "+delete");
+ } else {
+ return CodeMirror.Pass;
+ }
+ };
+
+ cmds[map[cK + ctrl + "K"] = "delLineRight"] = function(cm) {
+ cm.operation(function() {
+ var ranges = cm.listSelections();
+ for (var i = ranges.length - 1; i >= 0; i--)
+ cm.replaceRange("", ranges[i].anchor, Pos(ranges[i].to().line), "+delete");
+ cm.scrollIntoView();
+ });
+ };
+
+ cmds[map[cK + ctrl + "U"] = "upcaseAtCursor"] = function(cm) {
+ modifyWordOrSelection(cm, function(str) { return str.toUpperCase(); });
+ };
+ cmds[map[cK + ctrl + "L"] = "downcaseAtCursor"] = function(cm) {
+ modifyWordOrSelection(cm, function(str) { return str.toLowerCase(); });
+ };
+
+ cmds[map[cK + ctrl + "Space"] = "setSublimeMark"] = function(cm) {
+ if (cm.state.sublimeMark) cm.state.sublimeMark.clear();
+ cm.state.sublimeMark = cm.setBookmark(cm.getCursor());
+ };
+ cmds[map[cK + ctrl + "A"] = "selectToSublimeMark"] = function(cm) {
+ var found = cm.state.sublimeMark && cm.state.sublimeMark.find();
+ if (found) cm.setSelection(cm.getCursor(), found);
+ };
+ cmds[map[cK + ctrl + "W"] = "deleteToSublimeMark"] = function(cm) {
+ var found = cm.state.sublimeMark && cm.state.sublimeMark.find();
+ if (found) {
+ var from = cm.getCursor(), to = found;
+ if (CodeMirror.cmpPos(from, to) > 0) { var tmp = to; to = from; from = tmp; }
+ cm.state.sublimeKilled = cm.getRange(from, to);
+ cm.replaceRange("", from, to);
+ }
+ };
+ cmds[map[cK + ctrl + "X"] = "swapWithSublimeMark"] = function(cm) {
+ var found = cm.state.sublimeMark && cm.state.sublimeMark.find();
+ if (found) {
+ cm.state.sublimeMark.clear();
+ cm.state.sublimeMark = cm.setBookmark(cm.getCursor());
+ cm.setCursor(found);
+ }
+ };
+ cmds[map[cK + ctrl + "Y"] = "sublimeYank"] = function(cm) {
+ if (cm.state.sublimeKilled != null)
+ cm.replaceSelection(cm.state.sublimeKilled, null, "paste");
+ };
+
+ map[cK + ctrl + "G"] = "clearBookmarks";
+ cmds[map[cK + ctrl + "C"] = "showInCenter"] = function(cm) {
+ var pos = cm.cursorCoords(null, "local");
+ cm.scrollTo(null, (pos.top + pos.bottom) / 2 - cm.getScrollInfo().clientHeight / 2);
+ };
+
+ cmds[map["Shift-Alt-Up"] = "selectLinesUpward"] = function(cm) {
+ cm.operation(function() {
+ var ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i];
+ if (range.head.line > cm.firstLine())
+ cm.addSelection(Pos(range.head.line - 1, range.head.ch));
+ }
+ });
+ };
+ cmds[map["Shift-Alt-Down"] = "selectLinesDownward"] = function(cm) {
+ cm.operation(function() {
+ var ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i];
+ if (range.head.line < cm.lastLine())
+ cm.addSelection(Pos(range.head.line + 1, range.head.ch));
+ }
+ });
+ };
+
+ function getTarget(cm) {
+ var from = cm.getCursor("from"), to = cm.getCursor("to");
+ if (CodeMirror.cmpPos(from, to) == 0) {
+ var word = wordAt(cm, from);
+ if (!word.word) return;
+ from = word.from;
+ to = word.to;
+ }
+ return {from: from, to: to, query: cm.getRange(from, to), word: word};
+ }
+
+ function findAndGoTo(cm, forward) {
+ var target = getTarget(cm);
+ if (!target) return;
+ var query = target.query;
+ var cur = cm.getSearchCursor(query, forward ? target.to : target.from);
+
+ if (forward ? cur.findNext() : cur.findPrevious()) {
+ cm.setSelection(cur.from(), cur.to());
+ } else {
+ cur = cm.getSearchCursor(query, forward ? Pos(cm.firstLine(), 0)
+ : cm.clipPos(Pos(cm.lastLine())));
+ if (forward ? cur.findNext() : cur.findPrevious())
+ cm.setSelection(cur.from(), cur.to());
+ else if (target.word)
+ cm.setSelection(target.from, target.to);
+ }
+ };
+ cmds[map[ctrl + "F3"] = "findUnder"] = function(cm) { findAndGoTo(cm, true); };
+ cmds[map["Shift-" + ctrl + "F3"] = "findUnderPrevious"] = function(cm) { findAndGoTo(cm,false); };
+ cmds[map["Alt-F3"] = "findAllUnder"] = function(cm) {
+ var target = getTarget(cm);
+ if (!target) return;
+ var cur = cm.getSearchCursor(target.query);
+ var matches = [];
+ var primaryIndex = -1;
+ while (cur.findNext()) {
+ matches.push({anchor: cur.from(), head: cur.to()});
+ if (cur.from().line <= target.from.line && cur.from().ch <= target.from.ch)
+ primaryIndex++;
+ }
+ cm.setSelections(matches, primaryIndex);
+ };
+
+ map["Shift-" + ctrl + "["] = "fold";
+ map["Shift-" + ctrl + "]"] = "unfold";
+ map[cK + ctrl + "0"] = map[cK + ctrl + "j"] = "unfoldAll";
+
+ map[ctrl + "I"] = "findIncremental";
+ map["Shift-" + ctrl + "I"] = "findIncrementalReverse";
+ map[ctrl + "H"] = "replace";
+ map["F3"] = "findNext";
+ map["Shift-F3"] = "findPrev";
+
+ CodeMirror.normalizeKeyMap(map);
+});
diff --git a/www/code/codemirror-5.13.2/keymap/vim.js b/www/code/codemirror-5.13.2/keymap/vim.js
new file mode 100644
index 000000000..5ed2d9bf7
--- /dev/null
+++ b/www/code/codemirror-5.13.2/keymap/vim.js
@@ -0,0 +1,5066 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+/**
+ * Supported keybindings:
+ * Too many to list. Refer to defaultKeyMap below.
+ *
+ * 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.
+ *
+ * Events:
+ * 'vim-mode-change' - raised on the editor anytime the current mode changes,
+ * Event object: {mode: "visual", subMode: "linewise"}
+ *
+ * Code structure:
+ * 1. Default keymap
+ * 2. Variable declarations and short basic helpers
+ * 3. Instance (External API) implementation
+ * 4. Internal state tracking objects (input state, counter) implementation
+ * and instanstiation
+ * 5. Key handler (the main command dispatcher) implementation
+ * 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) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../lib/codemirror"), require("../addon/search/searchcursor"), require("../addon/dialog/dialog"), require("../addon/edit/matchbrackets.js"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/dialog/dialog", "../addon/edit/matchbrackets"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ 'use strict';
+
+ var defaultKeymap = [
+ // Key to key mapping. This goes first to make it possible to override
+ // existing mappings.
+ { keys: '', type: 'keyToKey', toKeys: 'h' },
+ { keys: '', type: 'keyToKey', toKeys: 'l' },
+ { keys: '', type: 'keyToKey', toKeys: 'k' },
+ { keys: '', type: 'keyToKey', toKeys: 'j' },
+ { keys: '', type: 'keyToKey', toKeys: 'l' },
+ { keys: '', type: 'keyToKey', toKeys: 'h', context: 'normal'},
+ { keys: '', type: 'keyToKey', toKeys: 'W' },
+ { keys: '', type: 'keyToKey', toKeys: 'B', context: 'normal' },
+ { keys: '', type: 'keyToKey', toKeys: 'w' },
+ { keys: '', type: 'keyToKey', toKeys: 'b', context: 'normal' },
+ { keys: '', type: 'keyToKey', toKeys: 'j' },
+ { keys: '', type: 'keyToKey', toKeys: 'k' },
+ { keys: '', type: 'keyToKey', toKeys: '' },
+ { keys: '', type: 'keyToKey', toKeys: '' },
+ { keys: '', type: 'keyToKey', toKeys: '', context: 'insert' },
+ { keys: '', type: 'keyToKey', toKeys: '', context: 'insert' },
+ { keys: 's', type: 'keyToKey', toKeys: 'cl', context: 'normal' },
+ { keys: 's', type: 'keyToKey', toKeys: 'c', context: 'visual'},
+ { keys: 'S', type: 'keyToKey', toKeys: 'cc', context: 'normal' },
+ { keys: 'S', type: 'keyToKey', toKeys: 'VdO', context: 'visual' },
+ { keys: '', type: 'keyToKey', toKeys: '0' },
+ { keys: '', type: 'keyToKey', toKeys: '$' },
+ { keys: '', type: 'keyToKey', toKeys: '' },
+ { keys: '', type: 'keyToKey', toKeys: '' },
+ { keys: '', type: 'keyToKey', toKeys: 'j^', context: 'normal' },
+ // Motions
+ { keys: 'H', type: 'motion', motion: 'moveToTopLine', motionArgs: { linewise: true, toJumplist: true }},
+ { keys: 'M', type: 'motion', motion: 'moveToMiddleLine', motionArgs: { linewise: true, toJumplist: true }},
+ { keys: 'L', type: 'motion', motion: 'moveToBottomLine', motionArgs: { linewise: true, toJumplist: true }},
+ { keys: 'h', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: false }},
+ { keys: 'l', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: true }},
+ { keys: 'j', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, linewise: true }},
+ { keys: 'k', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, linewise: true }},
+ { keys: 'gj', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: true }},
+ { keys: 'gk', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: false }},
+ { keys: 'w', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false }},
+ { keys: 'W', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false, bigWord: true }},
+ { keys: 'e', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, inclusive: true }},
+ { keys: 'E', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, bigWord: true, inclusive: true }},
+ { keys: 'b', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }},
+ { keys: 'B', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false, bigWord: true }},
+ { keys: 'ge', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, inclusive: true }},
+ { keys: 'gE', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, bigWord: true, inclusive: true }},
+ { keys: '{', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: false, toJumplist: true }},
+ { keys: '}', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: true, toJumplist: true }},
+ { keys: '', type: 'motion', motion: 'moveByPage', motionArgs: { forward: true }},
+ { keys: '', type: 'motion', motion: 'moveByPage', motionArgs: { forward: false }},
+ { keys: '', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: true, explicitRepeat: true }},
+ { keys: '', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: false, explicitRepeat: true }},
+ { keys: 'gg', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }},
+ { keys: 'G', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }},
+ { keys: '0', type: 'motion', motion: 'moveToStartOfLine' },
+ { keys: '^', type: 'motion', motion: 'moveToFirstNonWhiteSpaceCharacter' },
+ { keys: '+', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true }},
+ { keys: '-', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, toFirstChar:true }},
+ { keys: '_', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }},
+ { keys: '$', type: 'motion', motion: 'moveToEol', motionArgs: { inclusive: true }},
+ { keys: '%', type: 'motion', motion: 'moveToMatchedSymbol', motionArgs: { inclusive: true, toJumplist: true }},
+ { keys: 'f', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: true , inclusive: true }},
+ { keys: 'F', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: false }},
+ { keys: 't', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: true, inclusive: true }},
+ { keys: 'T', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: false }},
+ { keys: ';', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: true }},
+ { keys: ',', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: false }},
+ { keys: '\'', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true, linewise: true}},
+ { keys: '`', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true}},
+ { keys: ']`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } },
+ { keys: '[`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } },
+ { keys: ']\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } },
+ { keys: '[\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } },
+ // the next two aren't motions but must come before more general motion declarations
+ { keys: ']p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true, matchIndent: true}},
+ { keys: '[p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true, matchIndent: true}},
+ { keys: ']', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: true, toJumplist: true}},
+ { keys: '[', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: false, toJumplist: true}},
+ { keys: '|', type: 'motion', motion: 'moveToColumn'},
+ { keys: 'o', type: 'motion', motion: 'moveToOtherHighlightedEnd', context:'visual'},
+ { keys: 'O', type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: {sameLine: true}, context:'visual'},
+ // Operators
+ { keys: 'd', type: 'operator', operator: 'delete' },
+ { keys: 'y', type: 'operator', operator: 'yank' },
+ { keys: 'c', type: 'operator', operator: 'change' },
+ { keys: '>', type: 'operator', operator: 'indent', operatorArgs: { indentRight: true }},
+ { keys: '<', type: 'operator', operator: 'indent', operatorArgs: { indentRight: false }},
+ { keys: 'g~', type: 'operator', operator: 'changeCase' },
+ { keys: 'gu', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, isEdit: true },
+ { keys: 'gU', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, isEdit: true },
+ { keys: 'n', type: 'motion', motion: 'findNext', motionArgs: { forward: true, toJumplist: true }},
+ { keys: 'N', type: 'motion', motion: 'findNext', motionArgs: { forward: false, toJumplist: true }},
+ // Operator-Motion dual commands
+ { keys: 'x', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorMotionArgs: { visualLine: false }},
+ { keys: 'X', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: false }, operatorMotionArgs: { visualLine: true }},
+ { keys: 'D', type: 'operatorMotion', operator: 'delete', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},
+ { keys: 'D', type: 'operator', operator: 'delete', operatorArgs: { linewise: true }, context: 'visual'},
+ { keys: 'Y', type: 'operatorMotion', operator: 'yank', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},
+ { keys: 'Y', type: 'operator', operator: 'yank', operatorArgs: { linewise: true }, context: 'visual'},
+ { keys: 'C', type: 'operatorMotion', operator: 'change', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},
+ { keys: 'C', type: 'operator', operator: 'change', operatorArgs: { linewise: true }, context: 'visual'},
+ { keys: '~', type: 'operatorMotion', operator: 'changeCase', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorArgs: { shouldMoveCursor: true }, context: 'normal'},
+ { keys: '~', type: 'operator', operator: 'changeCase', context: 'visual'},
+ { keys: '', type: 'operatorMotion', operator: 'delete', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }, context: 'insert' },
+ // Actions
+ { keys: '', type: 'action', action: 'jumpListWalk', actionArgs: { forward: true }},
+ { keys: '', type: 'action', action: 'jumpListWalk', actionArgs: { forward: false }},
+ { keys: '', type: 'action', action: 'scroll', actionArgs: { forward: true, linewise: true }},
+ { keys: '', type: 'action', action: 'scroll', actionArgs: { forward: false, linewise: true }},
+ { keys: 'a', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'charAfter' }, context: 'normal' },
+ { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'eol' }, context: 'normal' },
+ { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'endOfSelectedArea' }, context: 'visual' },
+ { keys: 'i', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'inplace' }, context: 'normal' },
+ { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'firstNonBlank'}, context: 'normal' },
+ { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'startOfSelectedArea' }, context: 'visual' },
+ { keys: 'o', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: true }, context: 'normal' },
+ { keys: 'O', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: false }, context: 'normal' },
+ { keys: 'v', type: 'action', action: 'toggleVisualMode' },
+ { keys: 'V', type: 'action', action: 'toggleVisualMode', actionArgs: { linewise: true }},
+ { keys: '', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }},
+ { keys: '', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }},
+ { keys: 'gv', type: 'action', action: 'reselectLastSelection' },
+ { keys: 'J', type: 'action', action: 'joinLines', isEdit: true },
+ { keys: 'p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true }},
+ { keys: 'P', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true }},
+ { keys: 'r', type: 'action', action: 'replace', isEdit: true },
+ { keys: '@', type: 'action', action: 'replayMacro' },
+ { keys: 'q', type: 'action', action: 'enterMacroRecordMode' },
+ // Handle Replace-mode as a special case of insert mode.
+ { keys: 'R', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { replace: true }},
+ { keys: 'u', type: 'action', action: 'undo', context: 'normal' },
+ { keys: 'u', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, context: 'visual', isEdit: true },
+ { keys: 'U', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, context: 'visual', isEdit: true },
+ { keys: '', type: 'action', action: 'redo' },
+ { keys: 'm', type: 'action', action: 'setMark' },
+ { keys: '"', type: 'action', action: 'setRegister' },
+ { keys: 'zz', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }},
+ { keys: 'z.', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },
+ { keys: 'zt', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }},
+ { keys: 'z', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },
+ { keys: 'z-', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }},
+ { keys: 'zb', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },
+ { keys: '.', type: 'action', action: 'repeatLastEdit' },
+ { keys: '', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: true, backtrack: false}},
+ { keys: '', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: false, backtrack: false}},
+ // Text object motions
+ { keys: 'a', type: 'motion', motion: 'textObjectManipulation' },
+ { keys: 'i', type: 'motion', motion: 'textObjectManipulation', motionArgs: { textObjectInner: true }},
+ // Search
+ { keys: '/', type: 'search', searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }},
+ { keys: '?', type: 'search', searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }},
+ { keys: '*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }},
+ { keys: '#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }},
+ { keys: 'g*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }},
+ { keys: 'g#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }},
+ // Ex command
+ { 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() {
+ function enterVimMode(cm) {
+ cm.setOption('disableInput', true);
+ cm.setOption('showCursorWhenSelecting', false);
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
+ cm.on('cursorActivity', onCursorActivity);
+ maybeInitVimState(cm);
+ CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm));
+ }
+
+ function leaveVimMode(cm) {
+ cm.setOption('disableInput', false);
+ cm.off('cursorActivity', onCursorActivity);
+ CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm));
+ cm.state.vim = null;
+ }
+
+ function detachVimMap(cm, next) {
+ if (this == CodeMirror.keyMap.vim)
+ CodeMirror.rmClass(cm.getWrapperElement(), "cm-fat-cursor");
+
+ if (!next || next.attach != attachVimMap)
+ leaveVimMode(cm, false);
+ }
+ function attachVimMap(cm, prev) {
+ if (this == CodeMirror.keyMap.vim)
+ CodeMirror.addClass(cm.getWrapperElement(), "cm-fat-cursor");
+
+ if (!prev || prev.attach != attachVimMap)
+ enterVimMode(cm);
+ }
+
+ // Deprecated, simply setting the keymap works again.
+ CodeMirror.defineOption('vimMode', false, function(cm, val, prev) {
+ if (val && cm.getOption("keyMap") != "vim")
+ cm.setOption("keyMap", "vim");
+ else if (!val && prev != CodeMirror.Init && /^vim/.test(cm.getOption("keyMap")))
+ cm.setOption("keyMap", "default");
+ });
+
+ function cmKey(key, cm) {
+ if (!cm) { return undefined; }
+ var vimKey = cmKeyToVimKey(key);
+ if (!vimKey) {
+ return false;
+ }
+ var cmd = CodeMirror.Vim.findKey(cm, vimKey);
+ if (typeof cmd == 'function') {
+ CodeMirror.signal(cm, 'vim-keypress', vimKey);
+ }
+ return cmd;
+ }
+
+ var modifiers = {'Shift': 'S', 'Ctrl': 'C', 'Alt': 'A', 'Cmd': 'D', 'Mod': 'A'};
+ var specialKeys = {Enter:'CR',Backspace:'BS',Delete:'Del'};
+ function cmKeyToVimKey(key) {
+ if (key.charAt(0) == '\'') {
+ // Keypress character binding of format "'a'"
+ return key.charAt(1);
+ }
+ var pieces = key.split(/-(?!$)/);
+ var lastPiece = pieces[pieces.length - 1];
+ if (pieces.length == 1 && pieces[0].length == 1) {
+ // No-modifier bindings use literal character bindings above. Skip.
+ return false;
+ } else if (pieces.length == 2 && pieces[0] == 'Shift' && lastPiece.length == 1) {
+ // Ignore Shift+char bindings as they should be handled by literal character.
+ return false;
+ }
+ var hasCharacter = false;
+ for (var i = 0; i < pieces.length; i++) {
+ var piece = pieces[i];
+ if (piece in modifiers) { pieces[i] = modifiers[piece]; }
+ else { hasCharacter = true; }
+ if (piece in specialKeys) { pieces[i] = specialKeys[piece]; }
+ }
+ if (!hasCharacter) {
+ // Vim does not support modifier only keys.
+ return false;
+ }
+ // TODO: Current bindings expect the character to be lower case, but
+ // it looks like vim key notation uses upper case.
+ if (isUpperCase(lastPiece)) {
+ pieces[pieces.length - 1] = lastPiece.toLowerCase();
+ }
+ return '<' + pieces.join('-') + '>';
+ }
+
+ function getOnPasteFn(cm) {
+ var vim = cm.state.vim;
+ if (!vim.onPasteFn) {
+ vim.onPasteFn = function() {
+ if (!vim.insertMode) {
+ cm.setCursor(offsetCursor(cm.getCursor(), 0, 1));
+ actions.enterInsertMode(cm, {}, vim);
+ }
+ };
+ }
+ return vim.onPasteFn;
+ }
+
+ var numberRegex = /[\d]/;
+ 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++) {
+ keys.push(String.fromCharCode(i));
+ }
+ return keys;
+ }
+ var upperCaseAlphabet = makeKeyRange(65, 26);
+ var lowerCaseAlphabet = makeKeyRange(97, 26);
+ var numbers = makeKeyRange(48, 10);
+ var validMarks = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['<', '>']);
+ var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['-', '"', '.', ':', '/']);
+
+ function isLine(cm, line) {
+ return line >= cm.firstLine() && line <= cm.lastLine();
+ }
+ function isLowerCase(k) {
+ return (/^[a-z]$/).test(k);
+ }
+ function isMatchableSymbol(k) {
+ return '()[]{}'.indexOf(k) != -1;
+ }
+ function isNumber(k) {
+ return numberRegex.test(k);
+ }
+ function isUpperCase(k) {
+ return (/^[A-Z]$/).test(k);
+ }
+ function isWhiteSpaceString(k) {
+ return (/^\s*$/).test(k);
+ }
+ function inArray(val, arr) {
+ for (var i = 0; i < arr.length; i++) {
+ if (arr[i] == val) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ var options = {};
+ 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,
+ callback: callback
+ };
+ if (aliases) {
+ for (var i = 0; i < aliases.length; i++) {
+ options[aliases[i]] = options[name];
+ }
+ }
+ if (defaultValue) {
+ setOption(name, defaultValue);
+ }
+ }
+
+ function setOption(name, value, cm, cfg) {
+ var option = options[name];
+ cfg = cfg || {};
+ var scope = cfg.scope;
+ if (!option) {
+ throw Error('Unknown option: ' + name);
+ }
+ if (option.type == 'boolean') {
+ if (value && value !== true) {
+ throw Error('Invalid argument: ' + name + '=' + value);
+ } else if (value !== false) {
+ // Boolean options are set to true if value is not defined.
+ value = true;
+ }
+ }
+ 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, cm, cfg) {
+ var option = options[name];
+ cfg = cfg || {};
+ var scope = cfg.scope;
+ if (!option) {
+ throw Error('Unknown option: ' + name);
+ }
+ 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;
+ var head = 0;
+ var tail = 0;
+ var buffer = new Array(size);
+ function add(cm, oldCur, newCur) {
+ var current = pointer % size;
+ var curMark = buffer[current];
+ function useNextSlot(cursor) {
+ var next = ++pointer % size;
+ var trashMark = buffer[next];
+ if (trashMark) {
+ trashMark.clear();
+ }
+ buffer[next] = cm.setBookmark(cursor);
+ }
+ if (curMark) {
+ var markPos = curMark.find();
+ // avoid recording redundant cursor position
+ if (markPos && !cursorEqual(markPos, oldCur)) {
+ useNextSlot(oldCur);
+ }
+ } else {
+ useNextSlot(oldCur);
+ }
+ useNextSlot(newCur);
+ head = pointer;
+ tail = pointer - size + 1;
+ if (tail < 0) {
+ tail = 0;
+ }
+ }
+ function move(cm, offset) {
+ pointer += offset;
+ if (pointer > head) {
+ pointer = head;
+ } else if (pointer < tail) {
+ pointer = tail;
+ }
+ var mark = buffer[(size + pointer) % size];
+ // skip marks that are temporarily removed from text buffer
+ if (mark && !mark.find()) {
+ var inc = offset > 0 ? 1 : -1;
+ var newCur;
+ var oldCur = cm.getCursor();
+ do {
+ pointer += inc;
+ mark = buffer[(size + pointer) % size];
+ // skip marks that are the same as current position
+ if (mark &&
+ (newCur = mark.find()) &&
+ !cursorEqual(oldCur, newCur)) {
+ break;
+ }
+ } while (pointer < head && pointer > tail);
+ }
+ return mark;
+ }
+ return {
+ cachedCursor: undefined, //used for # and * jumps
+ add: add,
+ move: move
+ };
+ };
+
+ // Returns an object to track the changes associated insert mode. It
+ // clones the object that is passed in, or creates an empty object one if
+ // none is provided.
+ var createInsertModeChanges = function(c) {
+ if (c) {
+ // Copy construction
+ return {
+ changes: c.changes,
+ expectCursorActivityForChange: c.expectCursorActivityForChange
+ };
+ }
+ return {
+ // Change list
+ changes: [],
+ // Set to true on change, false on cursorActivity.
+ expectCursorActivityForChange: false
+ };
+ };
+
+ function MacroModeState() {
+ this.latestRegister = undefined;
+ this.isPlaying = false;
+ this.isRecording = false;
+ this.replaySearchQueries = [];
+ this.onRecordingDone = undefined;
+ this.lastInsertModeChanges = createInsertModeChanges();
+ }
+ MacroModeState.prototype = {
+ exitMacroRecordMode: function() {
+ var macroModeState = vimGlobalState.macroModeState;
+ if (macroModeState.onRecordingDone) {
+ macroModeState.onRecordingDone(); // close dialog
+ }
+ macroModeState.onRecordingDone = undefined;
+ macroModeState.isRecording = false;
+ },
+ enterMacroRecordMode: function(cm, registerName) {
+ var register =
+ vimGlobalState.registerController.getRegister(registerName);
+ if (register) {
+ register.clear();
+ this.latestRegister = registerName;
+ if (cm.openDialog) {
+ this.onRecordingDone = cm.openDialog(
+ '(recording)['+registerName+']', null, {bottom:true});
+ }
+ this.isRecording = true;
+ }
+ }
+ };
+
+ function maybeInitVimState(cm) {
+ if (!cm.state.vim) {
+ // Store instance state in the CodeMirror object.
+ cm.state.vim = {
+ inputState: new InputState(),
+ // Vim's input state that triggered the last edit, used to repeat
+ // motions and operators with '.'.
+ lastEditInputState: undefined,
+ // Vim's action command before the last edit, used to repeat actions
+ // with '.' and insert mode repeat.
+ lastEditActionCommand: undefined,
+ // When using jk for navigation, if you move from a longer line to a
+ // shorter line, the cursor may clip to the end of the shorter line.
+ // If j is pressed again and cursor goes to the next line, the
+ // cursor should go back to its horizontal position on the longer
+ // line if it can. This is to keep track of the horizontal position.
+ lastHPos: -1,
+ // Doing the same with screen-position for gj/gk
+ lastHSPos: -1,
+ // The last motion command run. Cleared if a non-motion command gets
+ // executed in between.
+ lastMotion: null,
+ marks: {},
+ // Mark for rendering fake cursor for visual mode.
+ fakeCursor: null,
+ insertMode: false,
+ // Repeat count for changes made in insert mode, triggered by key
+ // sequences like 3,i. Only exists when insertMode is true.
+ insertModeRepeat: undefined,
+ visualMode: false,
+ // If we are in visual line mode. No effect if visualMode is false.
+ visualLine: false,
+ visualBlock: false,
+ lastSelection: null,
+ lastPastedText: null,
+ sel: {},
+ // Buffer-local/window-local values of vim options.
+ options: {}
+ };
+ }
+ return cm.state.vim;
+ }
+ var vimGlobalState;
+ function resetVimGlobalState() {
+ vimGlobalState = {
+ // The current search query.
+ searchQuery: null,
+ // Whether we are searching backwards.
+ searchIsReversed: false,
+ // Replace part of the last substituted pattern
+ lastSubstituteReplacePart: undefined,
+ jumpList: createCircularJumpList(),
+ macroModeState: new MacroModeState,
+ // Recording latest f, t, F or T motion command.
+ lastChararacterSearch: {increment:0, forward:true, selectedCharacter:''},
+ registerController: new RegisterController({}),
+ // search history buffer
+ searchHistoryController: new HistoryController({}),
+ // ex Command history buffer
+ exCommandHistoryController : new HistoryController({})
+ };
+ for (var optionName in options) {
+ var option = options[optionName];
+ option.value = option.defaultValue;
+ }
+ }
+
+ var lastInsertModeKeyTimer;
+ var vimApi= {
+ buildKeyMap: function() {
+ // TODO: Convert keymap into dictionary format for fast lookup.
+ },
+ // Testing hook, though it might be useful to expose the register
+ // controller anyways.
+ getRegisterController: function() {
+ return vimGlobalState.registerController;
+ },
+ // Testing hook.
+ resetVimGlobalState_: resetVimGlobalState,
+
+ // Testing hook.
+ getVimGlobalState_: function() {
+ return vimGlobalState;
+ },
+
+ // Testing hook.
+ maybeInitVimState_: maybeInitVimState,
+
+ suppressErrorLogging: false,
+
+ InsertModeKey: InsertModeKey,
+ map: function(lhs, rhs, ctx) {
+ // Add user defined key bindings.
+ exCommandDispatcher.map(lhs, rhs, ctx);
+ },
+ unmap: function(lhs, ctx) {
+ exCommandDispatcher.unmap(lhs, 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,
+ defineEx: function(name, prefix, func){
+ if (!prefix) {
+ prefix = name;
+ } else if (name.indexOf(prefix) !== 0) {
+ throw new Error('(Vim.defineEx) "'+prefix+'" is not a prefix of "'+name+'", command not registered');
+ }
+ exCommands[name]=func;
+ exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'};
+ },
+ handleKey: function (cm, key, origin) {
+ var command = this.findKey(cm, key, origin);
+ if (typeof command === 'function') {
+ return command();
+ }
+ },
+ /**
+ * This is the outermost function called by CodeMirror, after keys have
+ * been mapped to their Vim equivalents.
+ *
+ * Finds a command based on the key (and cached keys if there is a
+ * multi-key sequence). Returns `undefined` if no key is matched, a noop
+ * function if a partial match is found (multi-key), and a function to
+ * execute the bound command if a a key is matched. The function always
+ * returns true.
+ */
+ findKey: function(cm, key, origin) {
+ var vim = maybeInitVimState(cm);
+ function handleMacroRecording() {
+ var macroModeState = vimGlobalState.macroModeState;
+ if (macroModeState.isRecording) {
+ if (key == 'q') {
+ macroModeState.exitMacroRecordMode();
+ clearInputState(cm);
+ return true;
+ }
+ if (origin != 'mapping') {
+ logKey(macroModeState, key);
+ }
+ }
+ }
+ function handleEsc() {
+ if (key == '') {
+ // Clear input state and get back to normal mode.
+ clearInputState(cm);
+ if (vim.visualMode) {
+ exitVisualMode(cm);
+ } else if (vim.insertMode) {
+ exitInsertMode(cm);
+ }
+ return true;
+ }
+ }
+ function doKeyToKey(keys) {
+ // TODO: prevent infinite recursion.
+ var match;
+ while (keys) {
+ // Pull off one command key, which is either a single character
+ // or a special sequence wrapped in '<' and '>', e.g. ''.
+ match = (/<\w+-.+?>|<\w+>|./).exec(keys);
+ key = match[0];
+ keys = keys.substring(match.index + key.length);
+ CodeMirror.Vim.handleKey(cm, key, 'mapping');
+ }
+ }
+
+ function handleKeyInsertMode() {
+ if (handleEsc()) { return true; }
+ var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key;
+ var keysAreChars = key.length == 1;
+ var match = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert');
+ // Need to check all key substrings in insert mode.
+ while (keys.length > 1 && match.type != 'full') {
+ var keys = vim.inputState.keyBuffer = keys.slice(1);
+ var thisMatch = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert');
+ if (thisMatch.type != 'none') { match = thisMatch; }
+ }
+ if (match.type == 'none') { clearInputState(cm); return false; }
+ else if (match.type == 'partial') {
+ if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); }
+ lastInsertModeKeyTimer = window.setTimeout(
+ function() { if (vim.insertMode && vim.inputState.keyBuffer) { clearInputState(cm); } },
+ getOption('insertModeEscKeysTimeout'));
+ return !keysAreChars;
+ }
+
+ if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); }
+ if (keysAreChars) {
+ var here = cm.getCursor();
+ cm.replaceRange('', offsetCursor(here, 0, -(keys.length - 1)), here, '+input');
+ }
+ clearInputState(cm);
+ return match.command;
+ }
+
+ function handleKeyNonInsertMode() {
+ if (handleMacroRecording() || handleEsc()) { return true; };
+
+ var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key;
+ if (/^[1-9]\d*$/.test(keys)) { return true; }
+
+ var keysMatcher = /^(\d*)(.*)$/.exec(keys);
+ if (!keysMatcher) { clearInputState(cm); return false; }
+ var context = vim.visualMode ? 'visual' :
+ 'normal';
+ var match = commandDispatcher.matchCommand(keysMatcher[2] || keysMatcher[1], defaultKeymap, vim.inputState, context);
+ if (match.type == 'none') { clearInputState(cm); return false; }
+ else if (match.type == 'partial') { return true; }
+
+ vim.inputState.keyBuffer = '';
+ var keysMatcher = /^(\d*)(.*)$/.exec(keys);
+ if (keysMatcher[1] && keysMatcher[1] != '0') {
+ vim.inputState.pushRepeatDigit(keysMatcher[1]);
+ }
+ return match.command;
+ }
+
+ var command;
+ if (vim.insertMode) { command = handleKeyInsertMode(); }
+ else { command = handleKeyNonInsertMode(); }
+ if (command === false) {
+ return undefined;
+ } else if (command === true) {
+ // TODO: Look into using CodeMirror's multi-key handling.
+ // Return no-op since we are caching the key. Counts as handled, but
+ // don't want act on it just yet.
+ return function() {};
+ } else {
+ return function() {
+ return cm.operation(function() {
+ cm.curOp.isVimOp = true;
+ try {
+ if (command.type == 'keyToKey') {
+ doKeyToKey(command.toKeys);
+ } else {
+ commandDispatcher.processCommand(cm, vim, command);
+ }
+ } catch (e) {
+ // clear VIM state in case it's in a bad state.
+ cm.state.vim = undefined;
+ maybeInitVimState(cm);
+ if (!CodeMirror.Vim.suppressErrorLogging) {
+ console['log'](e);
+ }
+ throw e;
+ }
+ return true;
+ });
+ };
+ }
+ },
+ handleEx: function(cm, input) {
+ exCommandDispatcher.processCommand(cm, input);
+ },
+
+ defineMotion: defineMotion,
+ defineAction: defineAction,
+ defineOperator: defineOperator,
+ mapCommand: mapCommand,
+ _mapCommand: _mapCommand,
+
+ defineRegister: defineRegister,
+
+ exitVisualMode: exitVisualMode,
+ exitInsertMode: exitInsertMode
+ };
+
+ // Represents the current input state.
+ function InputState() {
+ this.prefixRepeat = [];
+ this.motionRepeat = [];
+
+ this.operator = null;
+ this.operatorArgs = null;
+ this.motion = null;
+ this.motionArgs = null;
+ this.keyBuffer = []; // For matching multi-key commands.
+ this.registerName = null; // Defaults to the unnamed register.
+ }
+ InputState.prototype.pushRepeatDigit = function(n) {
+ if (!this.operator) {
+ this.prefixRepeat = this.prefixRepeat.concat(n);
+ } else {
+ this.motionRepeat = this.motionRepeat.concat(n);
+ }
+ };
+ InputState.prototype.getRepeat = function() {
+ var repeat = 0;
+ if (this.prefixRepeat.length > 0 || this.motionRepeat.length > 0) {
+ repeat = 1;
+ if (this.prefixRepeat.length > 0) {
+ repeat *= parseInt(this.prefixRepeat.join(''), 10);
+ }
+ if (this.motionRepeat.length > 0) {
+ repeat *= parseInt(this.motionRepeat.join(''), 10);
+ }
+ }
+ return repeat;
+ };
+
+ function clearInputState(cm, reason) {
+ cm.state.vim.inputState = new InputState();
+ CodeMirror.signal(cm, 'vim-command-done', reason);
+ }
+
+ /*
+ * Register stores information about copy and paste registers. Besides
+ * text, a register must store whether it is linewise (i.e., when it is
+ * pasted, should it insert itself into a new line, or should the text be
+ * inserted at the cursor position.)
+ */
+ function Register(text, linewise, blockwise) {
+ this.clear();
+ this.keyBuffer = [text || ''];
+ this.insertModeChanges = [];
+ this.searchQueries = [];
+ this.linewise = !!linewise;
+ this.blockwise = !!blockwise;
+ }
+ Register.prototype = {
+ setText: function(text, linewise, blockwise) {
+ this.keyBuffer = [text || ''];
+ this.linewise = !!linewise;
+ this.blockwise = !!blockwise;
+ },
+ pushText: function(text, linewise) {
+ // if this register has ever been set to linewise, use linewise.
+ if (linewise) {
+ if (!this.linewise) {
+ this.keyBuffer.push('\n');
+ }
+ this.linewise = true;
+ }
+ this.keyBuffer.push(text);
+ },
+ pushInsertModeChanges: function(changes) {
+ this.insertModeChanges.push(createInsertModeChanges(changes));
+ },
+ pushSearchQuery: function(query) {
+ this.searchQueries.push(query);
+ },
+ clear: function() {
+ this.keyBuffer = [];
+ this.insertModeChanges = [];
+ this.searchQueries = [];
+ this.linewise = false;
+ },
+ toString: function() {
+ return this.keyBuffer.join('');
+ }
+ };
+
+ /**
+ * Defines an external register.
+ *
+ * The name should be a single character that will be used to reference the register.
+ * The register should support setText, pushText, clear, and toString(). See Register
+ * for a reference implementation.
+ */
+ function defineRegister(name, register) {
+ var registers = vimGlobalState.registerController.registers[name];
+ if (!name || name.length != 1) {
+ throw Error('Register name must be 1 character');
+ }
+ if (registers[name]) {
+ throw Error('Register already defined ' + name);
+ }
+ registers[name] = register;
+ validRegisters.push(name);
+ }
+
+ /*
+ * vim registers allow you to keep many independent copy and paste buffers.
+ * See http://usevim.com/2012/04/13/registers/ for an introduction.
+ *
+ * RegisterController keeps the state of all the registers. An initial
+ * state may be passed in. The unnamed register '"' will always be
+ * overridden.
+ */
+ function RegisterController(registers) {
+ this.registers = registers;
+ this.unnamedRegister = registers['"'] = new Register();
+ registers['.'] = new Register();
+ registers[':'] = new Register();
+ registers['/'] = new Register();
+ }
+ RegisterController.prototype = {
+ pushText: function(registerName, operator, text, linewise, blockwise) {
+ if (linewise && text.charAt(0) == '\n') {
+ text = text.slice(1) + '\n';
+ }
+ if (linewise && text.charAt(text.length - 1) !== '\n'){
+ text += '\n';
+ }
+ // Lowercase and uppercase registers refer to the same register.
+ // Uppercase just means append.
+ var register = this.isValidRegister(registerName) ?
+ this.getRegister(registerName) : null;
+ // if no register/an invalid register was specified, things go to the
+ // default registers
+ if (!register) {
+ switch (operator) {
+ case 'yank':
+ // The 0 register contains the text from the most recent yank.
+ this.registers['0'] = new Register(text, linewise, blockwise);
+ break;
+ case 'delete':
+ case 'change':
+ if (text.indexOf('\n') == -1) {
+ // Delete less than 1 line. Update the small delete register.
+ this.registers['-'] = new Register(text, linewise);
+ } else {
+ // Shift down the contents of the numbered registers and put the
+ // deleted text into register 1.
+ this.shiftNumericRegisters_();
+ this.registers['1'] = new Register(text, linewise);
+ }
+ break;
+ }
+ // Make sure the unnamed register is set to what just happened
+ this.unnamedRegister.setText(text, linewise, blockwise);
+ return;
+ }
+
+ // If we've gotten to this point, we've actually specified a register
+ var append = isUpperCase(registerName);
+ if (append) {
+ register.pushText(text, linewise);
+ } else {
+ register.setText(text, linewise, blockwise);
+ }
+ // The unnamed register always has the same value as the last used
+ // register.
+ this.unnamedRegister.setText(register.toString(), linewise);
+ },
+ // Gets the register named @name. If one of @name doesn't already exist,
+ // create it. If @name is invalid, return the unnamedRegister.
+ getRegister: function(name) {
+ if (!this.isValidRegister(name)) {
+ return this.unnamedRegister;
+ }
+ name = name.toLowerCase();
+ if (!this.registers[name]) {
+ this.registers[name] = new Register();
+ }
+ return this.registers[name];
+ },
+ isValidRegister: function(name) {
+ return name && inArray(name, validRegisters);
+ },
+ shiftNumericRegisters_: function() {
+ for (var i = 9; i >= 2; i--) {
+ this.registers[i] = this.getRegister('' + (i - 1));
+ }
+ }
+ };
+ function HistoryController() {
+ this.historyBuffer = [];
+ this.iterator;
+ this.initialPrefix = null;
+ }
+ HistoryController.prototype = {
+ // the input argument here acts a user entered prefix for a small time
+ // until we start autocompletion in which case it is the autocompleted.
+ nextMatch: function (input, up) {
+ var historyBuffer = this.historyBuffer;
+ var dir = up ? -1 : 1;
+ if (this.initialPrefix === null) this.initialPrefix = input;
+ for (var i = this.iterator + dir; up ? i >= 0 : i < historyBuffer.length; i+= dir) {
+ var element = historyBuffer[i];
+ for (var j = 0; j <= element.length; j++) {
+ if (this.initialPrefix == element.substring(0, j)) {
+ this.iterator = i;
+ return element;
+ }
+ }
+ }
+ // should return the user input in case we reach the end of buffer.
+ if (i >= historyBuffer.length) {
+ this.iterator = historyBuffer.length;
+ return this.initialPrefix;
+ }
+ // return the last autocompleted query or exCommand as it is.
+ if (i < 0 ) return input;
+ },
+ pushInput: function(input) {
+ var index = this.historyBuffer.indexOf(input);
+ if (index > -1) this.historyBuffer.splice(index, 1);
+ if (input.length) this.historyBuffer.push(input);
+ },
+ reset: function() {
+ this.initialPrefix = null;
+ this.iterator = this.historyBuffer.length;
+ }
+ };
+ var commandDispatcher = {
+ matchCommand: function(keys, keyMap, inputState, context) {
+ var matches = commandMatches(keys, keyMap, context, inputState);
+ if (!matches.full && !matches.partial) {
+ return {type: 'none'};
+ } else if (!matches.full && matches.partial) {
+ return {type: 'partial'};
+ }
+
+ var bestMatch;
+ for (var i = 0; i < matches.full.length; i++) {
+ var match = matches.full[i];
+ if (!bestMatch) {
+ bestMatch = match;
+ }
+ }
+ if (bestMatch.keys.slice(-11) == '') {
+ inputState.selectedCharacter = lastChar(keys);
+ }
+ return {type: 'full', command: bestMatch};
+ },
+ processCommand: function(cm, vim, command) {
+ vim.inputState.repeatOverride = command.repeatOverride;
+ switch (command.type) {
+ case 'motion':
+ this.processMotion(cm, vim, command);
+ break;
+ case 'operator':
+ this.processOperator(cm, vim, command);
+ break;
+ case 'operatorMotion':
+ this.processOperatorMotion(cm, vim, command);
+ break;
+ case 'action':
+ this.processAction(cm, vim, command);
+ break;
+ case 'search':
+ this.processSearch(cm, vim, command);
+ break;
+ case 'ex':
+ case 'keyToEx':
+ this.processEx(cm, vim, command);
+ break;
+ default:
+ break;
+ }
+ },
+ processMotion: function(cm, vim, command) {
+ vim.inputState.motion = command.motion;
+ vim.inputState.motionArgs = copyArgs(command.motionArgs);
+ this.evalInput(cm, vim);
+ },
+ processOperator: function(cm, vim, command) {
+ var inputState = vim.inputState;
+ if (inputState.operator) {
+ if (inputState.operator == command.operator) {
+ // Typing an operator twice like 'dd' makes the operator operate
+ // linewise
+ inputState.motion = 'expandToLine';
+ inputState.motionArgs = { linewise: true };
+ this.evalInput(cm, vim);
+ return;
+ } else {
+ // 2 different operators in a row doesn't make sense.
+ clearInputState(cm);
+ }
+ }
+ inputState.operator = command.operator;
+ inputState.operatorArgs = copyArgs(command.operatorArgs);
+ if (vim.visualMode) {
+ // Operating on a selection in visual mode. We don't need a motion.
+ this.evalInput(cm, vim);
+ }
+ },
+ processOperatorMotion: function(cm, vim, command) {
+ var visualMode = vim.visualMode;
+ var operatorMotionArgs = copyArgs(command.operatorMotionArgs);
+ if (operatorMotionArgs) {
+ // Operator motions may have special behavior in visual mode.
+ if (visualMode && operatorMotionArgs.visualLine) {
+ vim.visualLine = true;
+ }
+ }
+ this.processOperator(cm, vim, command);
+ if (!visualMode) {
+ this.processMotion(cm, vim, command);
+ }
+ },
+ processAction: function(cm, vim, command) {
+ var inputState = vim.inputState;
+ var repeat = inputState.getRepeat();
+ var repeatIsExplicit = !!repeat;
+ var actionArgs = copyArgs(command.actionArgs) || {};
+ if (inputState.selectedCharacter) {
+ actionArgs.selectedCharacter = inputState.selectedCharacter;
+ }
+ // Actions may or may not have motions and operators. Do these first.
+ if (command.operator) {
+ this.processOperator(cm, vim, command);
+ }
+ if (command.motion) {
+ this.processMotion(cm, vim, command);
+ }
+ if (command.motion || command.operator) {
+ this.evalInput(cm, vim);
+ }
+ actionArgs.repeat = repeat || 1;
+ actionArgs.repeatIsExplicit = repeatIsExplicit;
+ actionArgs.registerName = inputState.registerName;
+ clearInputState(cm);
+ vim.lastMotion = null;
+ if (command.isEdit) {
+ this.recordLastEdit(vim, inputState, command);
+ }
+ actions[command.action](cm, actionArgs, vim);
+ },
+ processSearch: function(cm, vim, command) {
+ if (!cm.getSearchCursor) {
+ // Search depends on SearchCursor.
+ return;
+ }
+ var forward = command.searchArgs.forward;
+ var wholeWordOnly = command.searchArgs.wholeWordOnly;
+ getSearchState(cm).setReversed(!forward);
+ var promptPrefix = (forward) ? '/' : '?';
+ var originalQuery = getSearchState(cm).getQuery();
+ var originalScrollPos = cm.getScrollInfo();
+ function handleQuery(query, ignoreCase, smartCase) {
+ vimGlobalState.searchHistoryController.pushInput(query);
+ vimGlobalState.searchHistoryController.reset();
+ try {
+ updateSearchQuery(cm, query, ignoreCase, smartCase);
+ } catch (e) {
+ showConfirm(cm, 'Invalid regex: ' + query);
+ clearInputState(cm);
+ return;
+ }
+ commandDispatcher.processMotion(cm, vim, {
+ type: 'motion',
+ motion: 'findNext',
+ motionArgs: { forward: true, toJumplist: command.searchArgs.toJumplist }
+ });
+ }
+ function onPromptClose(query) {
+ cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
+ handleQuery(query, true /** ignoreCase */, true /** smartCase */);
+ var macroModeState = vimGlobalState.macroModeState;
+ if (macroModeState.isRecording) {
+ logSearchQuery(macroModeState, query);
+ }
+ }
+ function onPromptKeyUp(e, query, close) {
+ var keyName = CodeMirror.keyName(e), up;
+ if (keyName == 'Up' || keyName == 'Down') {
+ up = keyName == 'Up' ? true : false;
+ query = vimGlobalState.searchHistoryController.nextMatch(query, up) || '';
+ close(query);
+ } else {
+ if ( keyName != 'Left' && keyName != 'Right' && keyName != 'Ctrl' && keyName != 'Alt' && keyName != 'Shift')
+ vimGlobalState.searchHistoryController.reset();
+ }
+ var parsedQuery;
+ try {
+ parsedQuery = updateSearchQuery(cm, query,
+ true /** ignoreCase */, true /** smartCase */);
+ } catch (e) {
+ // Swallow bad regexes for incremental search.
+ }
+ if (parsedQuery) {
+ cm.scrollIntoView(findNext(cm, !forward, parsedQuery), 30);
+ } else {
+ clearSearchHighlight(cm);
+ cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
+ }
+ }
+ function onPromptKeyDown(e, query, close) {
+ var keyName = CodeMirror.keyName(e);
+ 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) {
+ case 'prompt':
+ var macroModeState = vimGlobalState.macroModeState;
+ if (macroModeState.isPlaying) {
+ var query = macroModeState.replaySearchQueries.shift();
+ handleQuery(query, true /** ignoreCase */, false /** smartCase */);
+ } else {
+ showPrompt(cm, {
+ onClose: onPromptClose,
+ prefix: promptPrefix,
+ desc: searchPromptDesc,
+ onKeyUp: onPromptKeyUp,
+ onKeyDown: onPromptKeyDown
+ });
+ }
+ break;
+ case 'wordUnderCursor':
+ var word = expandWordUnderCursor(cm, false /** inclusive */,
+ true /** forward */, false /** bigWord */,
+ true /** noSymbol */);
+ var isKeyword = true;
+ if (!word) {
+ word = expandWordUnderCursor(cm, false /** inclusive */,
+ true /** forward */, false /** bigWord */,
+ false /** noSymbol */);
+ isKeyword = false;
+ }
+ if (!word) {
+ return;
+ }
+ var query = cm.getLine(word.start.line).substring(word.start.ch,
+ word.end.ch);
+ if (isKeyword && wholeWordOnly) {
+ query = '\\b' + query + '\\b';
+ } else {
+ query = escapeRegex(query);
+ }
+
+ // cachedCursor is used to save the old position of the cursor
+ // when * or # causes vim to seek for the nearest word and shift
+ // the cursor before entering the motion.
+ vimGlobalState.jumpList.cachedCursor = cm.getCursor();
+ cm.setCursor(word.start);
+
+ handleQuery(query, true /** ignoreCase */, false /** smartCase */);
+ break;
+ }
+ },
+ processEx: function(cm, vim, command) {
+ function onPromptClose(input) {
+ // Give the prompt some time to close so that if processCommand shows
+ // an error, the elements don't overlap.
+ vimGlobalState.exCommandHistoryController.pushInput(input);
+ vimGlobalState.exCommandHistoryController.reset();
+ exCommandDispatcher.processCommand(cm, input);
+ }
+ function onPromptKeyDown(e, input, close) {
+ var keyName = CodeMirror.keyName(e), up;
+ 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();
+ }
+ if (keyName == 'Up' || keyName == 'Down') {
+ 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();
+ }
+ }
+ if (command.type == 'keyToEx') {
+ // Handle user defined Ex to Ex mappings
+ exCommandDispatcher.processCommand(cm, command.exArgs.input);
+ } else {
+ if (vim.visualMode) {
+ showPrompt(cm, { onClose: onPromptClose, prefix: ':', value: '\'<,\'>',
+ onKeyDown: onPromptKeyDown});
+ } else {
+ showPrompt(cm, { onClose: onPromptClose, prefix: ':',
+ onKeyDown: onPromptKeyDown});
+ }
+ }
+ },
+ evalInput: function(cm, vim) {
+ // If the motion comand is set, execute both the operator and motion.
+ // Otherwise return.
+ var inputState = vim.inputState;
+ var motion = inputState.motion;
+ var motionArgs = inputState.motionArgs || {};
+ var operator = inputState.operator;
+ var operatorArgs = inputState.operatorArgs || {};
+ 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 ? 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;
+ var repeat;
+ if (operator) {
+ this.recordLastEdit(vim, inputState);
+ }
+ if (inputState.repeatOverride !== undefined) {
+ // If repeatOverride is specified, that takes precedence over the
+ // input state's repeat. Used by Ex mode and can be user defined.
+ repeat = inputState.repeatOverride;
+ } else {
+ repeat = inputState.getRepeat();
+ }
+ if (repeat > 0 && motionArgs.explicitRepeat) {
+ motionArgs.repeatIsExplicit = true;
+ } else if (motionArgs.noRepeat ||
+ (!motionArgs.explicitRepeat && repeat === 0)) {
+ repeat = 1;
+ motionArgs.repeatIsExplicit = false;
+ }
+ if (inputState.selectedCharacter) {
+ // If there is a character input, stick it in all of the arg arrays.
+ motionArgs.selectedCharacter = operatorArgs.selectedCharacter =
+ inputState.selectedCharacter;
+ }
+ motionArgs.repeat = repeat;
+ clearInputState(cm);
+ if (motion) {
+ var motionResult = motions[motion](cm, origHead, motionArgs, vim);
+ vim.lastMotion = motions[motion];
+ if (!motionResult) {
+ return;
+ }
+ if (motionArgs.toJumplist) {
+ var jumpList = vimGlobalState.jumpList;
+ // if the current motion is # or *, use cachedCursor
+ var cachedCursor = jumpList.cachedCursor;
+ if (cachedCursor) {
+ recordJumpPosition(cm, cachedCursor, motionResult);
+ delete jumpList.cachedCursor;
+ } else {
+ recordJumpPosition(cm, origHead, motionResult);
+ }
+ }
+ if (motionResult instanceof Array) {
+ newAnchor = motionResult[0];
+ newHead = motionResult[1];
+ } else {
+ newHead = motionResult;
+ }
+ // TODO: Handle null returns from motion commands better.
+ if (!newHead) {
+ newHead = copyCursor(origHead);
+ }
+ if (vim.visualMode) {
+ if (!(vim.visualBlock && newHead.ch === Infinity)) {
+ newHead = clipCursorToContent(cm, newHead, vim.visualBlock);
+ }
+ if (newAnchor) {
+ newAnchor = clipCursorToContent(cm, newAnchor, true);
+ }
+ newAnchor = newAnchor || oldAnchor;
+ sel.anchor = newAnchor;
+ sel.head = newHead;
+ updateCmSelection(cm);
+ updateMark(cm, vim, '<',
+ cursorIsBefore(newAnchor, newHead) ? newAnchor
+ : newHead);
+ updateMark(cm, vim, '>',
+ cursorIsBefore(newAnchor, newHead) ? newHead
+ : newAnchor);
+ } else if (!operator) {
+ newHead = clipCursorToContent(cm, newHead);
+ cm.setCursor(newHead.line, newHead.ch);
+ }
+ }
+ if (operator) {
+ if (operatorArgs.lastSel) {
+ // Replaying a visual mode operation
+ newAnchor = oldAnchor;
+ var lastSel = operatorArgs.lastSel;
+ var lineOffset = Math.abs(lastSel.head.line - lastSel.anchor.line);
+ var chOffset = Math.abs(lastSel.head.ch - lastSel.anchor.ch);
+ if (lastSel.visualLine) {
+ // Linewise Visual mode: The same number of lines.
+ newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch);
+ } else if (lastSel.visualBlock) {
+ // Blockwise Visual mode: The same number of lines and columns.
+ newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch + chOffset);
+ } else if (lastSel.head.line == lastSel.anchor.line) {
+ // Normal Visual mode within one line: The same number of characters.
+ newHead = Pos(oldAnchor.line, oldAnchor.ch + chOffset);
+ } else {
+ // Normal Visual mode with several lines: The same number of lines, in the
+ // last line the same number of characters as in the last line the last time.
+ newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch);
+ }
+ vim.visualMode = true;
+ vim.visualLine = lastSel.visualLine;
+ vim.visualBlock = lastSel.visualBlock;
+ sel = vim.sel = {
+ anchor: newAnchor,
+ head: newHead
+ };
+ updateCmSelection(cm);
+ } else if (vim.visualMode) {
+ operatorArgs.lastSel = {
+ anchor: copyCursor(sel.anchor),
+ head: copyCursor(sel.head),
+ visualBlock: vim.visualBlock,
+ visualLine: vim.visualLine
+ };
+ }
+ var curStart, curEnd, linewise, mode;
+ var cmSel;
+ if (vim.visualMode) {
+ // Init visual op
+ curStart = cursorMin(sel.head, sel.anchor);
+ curEnd = cursorMax(sel.head, sel.anchor);
+ linewise = vim.visualLine || operatorArgs.linewise;
+ mode = vim.visualBlock ? 'block' :
+ linewise ? 'line' :
+ 'char';
+ cmSel = makeCmSelection(cm, {
+ anchor: curStart,
+ head: curEnd
+ }, mode);
+ if (linewise) {
+ var ranges = cmSel.ranges;
+ if (mode == 'block') {
+ // Linewise operators in visual block mode extend to end of line
+ for (var i = 0; i < ranges.length; i++) {
+ ranges[i].head.ch = lineLength(cm, ranges[i].head.line);
+ }
+ } else if (mode == 'line') {
+ ranges[0].head = Pos(ranges[0].head.line + 1, 0);
+ }
+ }
+ } else {
+ // Init motion op
+ curStart = copyCursor(newAnchor || oldAnchor);
+ curEnd = copyCursor(newHead || oldHead);
+ if (cursorIsBefore(curEnd, curStart)) {
+ var tmp = curStart;
+ curStart = curEnd;
+ curEnd = tmp;
+ }
+ linewise = motionArgs.linewise || operatorArgs.linewise;
+ if (linewise) {
+ // Expand selection to entire line.
+ expandSelectionToLine(cm, curStart, curEnd);
+ } else if (motionArgs.forward) {
+ // Clip to trailing newlines only if the motion goes forward.
+ clipToLine(cm, curStart, curEnd);
+ }
+ mode = 'char';
+ var exclusive = !motionArgs.inclusive || linewise;
+ cmSel = makeCmSelection(cm, {
+ anchor: curStart,
+ head: curEnd
+ }, mode, exclusive);
+ }
+ cm.setSelections(cmSel.ranges, cmSel.primary);
+ vim.lastMotion = null;
+ operatorArgs.repeat = repeat; // For indent in visual mode.
+ operatorArgs.registerName = registerName;
+ // Keep track of linewise as it affects how paste and change behave.
+ operatorArgs.linewise = linewise;
+ var operatorMoveTo = operators[operator](
+ cm, operatorArgs, cmSel.ranges, oldAnchor, newHead);
+ if (vim.visualMode) {
+ exitVisualMode(cm, operatorMoveTo != null);
+ }
+ if (operatorMoveTo) {
+ cm.setCursor(operatorMoveTo);
+ }
+ }
+ },
+ recordLastEdit: function(vim, inputState, actionCommand) {
+ var macroModeState = vimGlobalState.macroModeState;
+ if (macroModeState.isPlaying) { return; }
+ vim.lastEditInputState = inputState;
+ vim.lastEditActionCommand = actionCommand;
+ macroModeState.lastInsertModeChanges.changes = [];
+ macroModeState.lastInsertModeChanges.expectCursorActivityForChange = false;
+ }
+ };
+
+ /**
+ * typedef {Object{line:number,ch:number}} Cursor An object containing the
+ * position of the cursor.
+ */
+ // All of the functions below return Cursor objects.
+ var motions = {
+ moveToTopLine: function(cm, _head, motionArgs) {
+ var line = getUserVisibleLines(cm).top + motionArgs.repeat -1;
+ return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
+ },
+ moveToMiddleLine: function(cm) {
+ var range = getUserVisibleLines(cm);
+ var line = Math.floor((range.top + range.bottom) * 0.5);
+ return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
+ },
+ moveToBottomLine: function(cm, _head, motionArgs) {
+ var line = getUserVisibleLines(cm).bottom - motionArgs.repeat +1;
+ return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
+ },
+ expandToLine: function(_cm, head, motionArgs) {
+ // Expands forward to end of line, and then to next line if repeat is
+ // >1. Does not handle backward motion!
+ var cur = head;
+ return Pos(cur.line + motionArgs.repeat - 1, Infinity);
+ },
+ findNext: function(cm, _head, motionArgs) {
+ var state = getSearchState(cm);
+ var query = state.getQuery();
+ if (!query) {
+ return;
+ }
+ var prev = !motionArgs.forward;
+ // If search is initiated with ? instead of /, negate direction.
+ prev = (state.isReversed()) ? !prev : prev;
+ highlightSearchMatches(cm, query);
+ return findNext(cm, prev/** prev */, query, motionArgs.repeat);
+ },
+ goToMark: function(cm, _head, motionArgs, vim) {
+ var mark = vim.marks[motionArgs.selectedCharacter];
+ if (mark) {
+ var pos = mark.find();
+ return motionArgs.linewise ? { line: pos.line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(pos.line)) } : pos;
+ }
+ return null;
+ },
+ moveToOtherHighlightedEnd: function(cm, _head, motionArgs, vim) {
+ if (vim.visualBlock && motionArgs.sameLine) {
+ var sel = vim.sel;
+ return [
+ clipCursorToContent(cm, Pos(sel.anchor.line, sel.head.ch)),
+ clipCursorToContent(cm, Pos(sel.head.line, sel.anchor.ch))
+ ];
+ } else {
+ return ([vim.sel.head, vim.sel.anchor]);
+ }
+ },
+ jumpToMark: function(cm, head, motionArgs, vim) {
+ var best = head;
+ for (var i = 0; i < motionArgs.repeat; i++) {
+ var cursor = best;
+ for (var key in vim.marks) {
+ if (!isLowerCase(key)) {
+ continue;
+ }
+ var mark = vim.marks[key].find();
+ var isWrongDirection = (motionArgs.forward) ?
+ cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark);
+
+ if (isWrongDirection) {
+ continue;
+ }
+ if (motionArgs.linewise && (mark.line == cursor.line)) {
+ continue;
+ }
+
+ var equal = cursorEqual(cursor, best);
+ var between = (motionArgs.forward) ?
+ cursorIsBetween(cursor, mark, best) :
+ cursorIsBetween(best, mark, cursor);
+
+ if (equal || between) {
+ best = mark;
+ }
+ }
+ }
+
+ if (motionArgs.linewise) {
+ // Vim places the cursor on the first non-whitespace character of
+ // the line if there is one, else it places the cursor at the end
+ // of the line, regardless of whether a mark was found.
+ best = Pos(best.line, findFirstNonWhiteSpaceCharacter(cm.getLine(best.line)));
+ }
+ return best;
+ },
+ moveByCharacters: function(_cm, head, motionArgs) {
+ var cur = head;
+ var repeat = motionArgs.repeat;
+ var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat;
+ return Pos(cur.line, ch);
+ },
+ moveByLines: function(cm, head, motionArgs, vim) {
+ var cur = head;
+ var endCh = cur.ch;
+ // Depending what our last motion was, we may want to do different
+ // things. If our last motion was moving vertically, we want to
+ // preserve the HPos from our last horizontal move. If our last motion
+ // was going to the end of a line, moving vertically we should go to
+ // the end of the line, etc.
+ switch (vim.lastMotion) {
+ case this.moveByLines:
+ case this.moveByDisplayLines:
+ case this.moveByScroll:
+ case this.moveToColumn:
+ case this.moveToEol:
+ endCh = vim.lastHPos;
+ break;
+ default:
+ vim.lastHPos = endCh;
+ }
+ var repeat = motionArgs.repeat+(motionArgs.repeatOffset||0);
+ var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat;
+ var first = cm.firstLine();
+ var last = cm.lastLine();
+ // Vim go to line begin or line end when cursor at first/last line and
+ // move to previous/next line is triggered.
+ if (line < first && cur.line == first){
+ return this.moveToStartOfLine(cm, head, motionArgs, vim);
+ }else if (line > last && cur.line == last){
+ return this.moveToEol(cm, head, motionArgs, vim);
+ }
+ if (motionArgs.toFirstChar){
+ endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line));
+ vim.lastHPos = endCh;
+ }
+ vim.lastHSPos = cm.charCoords(Pos(line, endCh),'div').left;
+ return Pos(line, endCh);
+ },
+ moveByDisplayLines: function(cm, head, motionArgs, vim) {
+ var cur = head;
+ switch (vim.lastMotion) {
+ case this.moveByDisplayLines:
+ case this.moveByScroll:
+ case this.moveByLines:
+ case this.moveToColumn:
+ case this.moveToEol:
+ break;
+ default:
+ vim.lastHSPos = cm.charCoords(cur,'div').left;
+ }
+ var repeat = motionArgs.repeat;
+ var res=cm.findPosV(cur,(motionArgs.forward ? repeat : -repeat),'line',vim.lastHSPos);
+ if (res.hitSide) {
+ if (motionArgs.forward) {
+ var lastCharCoords = cm.charCoords(res, 'div');
+ var goalCoords = { top: lastCharCoords.top + 8, left: vim.lastHSPos };
+ var res = cm.coordsChar(goalCoords, 'div');
+ } else {
+ var resCoords = cm.charCoords(Pos(cm.firstLine(), 0), 'div');
+ resCoords.left = vim.lastHSPos;
+ res = cm.coordsChar(resCoords, 'div');
+ }
+ }
+ vim.lastHPos = res.ch;
+ return res;
+ },
+ moveByPage: function(cm, head, motionArgs) {
+ // CodeMirror only exposes functions that move the cursor page down, so
+ // doing this bad hack to move the cursor and move it back. evalInput
+ // will move the cursor to where it should be in the end.
+ var curStart = head;
+ var repeat = motionArgs.repeat;
+ return cm.findPosV(curStart, (motionArgs.forward ? repeat : -repeat), 'page');
+ },
+ moveByParagraph: function(cm, head, motionArgs) {
+ var dir = motionArgs.forward ? 1 : -1;
+ return findParagraph(cm, head, motionArgs.repeat, dir);
+ },
+ moveByScroll: function(cm, head, motionArgs, vim) {
+ var scrollbox = cm.getScrollInfo();
+ var curEnd = null;
+ var repeat = motionArgs.repeat;
+ if (!repeat) {
+ repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight());
+ }
+ var orig = cm.charCoords(head, 'local');
+ motionArgs.repeat = repeat;
+ var curEnd = motions.moveByDisplayLines(cm, head, motionArgs, vim);
+ if (!curEnd) {
+ return null;
+ }
+ var dest = cm.charCoords(curEnd, 'local');
+ cm.scrollTo(null, scrollbox.top + dest.top - orig.top);
+ return curEnd;
+ },
+ moveByWords: function(cm, head, motionArgs) {
+ return moveToWord(cm, head, motionArgs.repeat, !!motionArgs.forward,
+ !!motionArgs.wordEnd, !!motionArgs.bigWord);
+ },
+ moveTillCharacter: function(cm, _head, motionArgs) {
+ var repeat = motionArgs.repeat;
+ var curEnd = moveToCharacter(cm, repeat, motionArgs.forward,
+ motionArgs.selectedCharacter);
+ var increment = motionArgs.forward ? -1 : 1;
+ recordLastCharacterSearch(increment, motionArgs);
+ if (!curEnd) return null;
+ curEnd.ch += increment;
+ return curEnd;
+ },
+ moveToCharacter: function(cm, head, motionArgs) {
+ var repeat = motionArgs.repeat;
+ recordLastCharacterSearch(0, motionArgs);
+ return moveToCharacter(cm, repeat, motionArgs.forward,
+ motionArgs.selectedCharacter) || head;
+ },
+ moveToSymbol: function(cm, head, motionArgs) {
+ var repeat = motionArgs.repeat;
+ return findSymbol(cm, repeat, motionArgs.forward,
+ motionArgs.selectedCharacter) || head;
+ },
+ moveToColumn: function(cm, head, motionArgs, vim) {
+ var repeat = motionArgs.repeat;
+ // repeat is equivalent to which column we want to move to!
+ vim.lastHPos = repeat - 1;
+ vim.lastHSPos = cm.charCoords(head,'div').left;
+ return moveToColumn(cm, repeat);
+ },
+ moveToEol: function(cm, head, motionArgs, vim) {
+ var cur = head;
+ vim.lastHPos = Infinity;
+ var retval= Pos(cur.line + motionArgs.repeat - 1, Infinity);
+ var end=cm.clipPos(retval);
+ end.ch--;
+ vim.lastHSPos = cm.charCoords(end,'div').left;
+ return retval;
+ },
+ moveToFirstNonWhiteSpaceCharacter: function(cm, head) {
+ // Go to the start of the line where the text begins, or the end for
+ // whitespace-only lines
+ var cursor = head;
+ return Pos(cursor.line,
+ findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)));
+ },
+ moveToMatchedSymbol: function(cm, head) {
+ var cursor = head;
+ var line = cursor.line;
+ var ch = cursor.ch;
+ var lineText = cm.getLine(line);
+ var symbol;
+ do {
+ symbol = lineText.charAt(ch++);
+ if (symbol && isMatchableSymbol(symbol)) {
+ var style = cm.getTokenTypeAt(Pos(line, ch));
+ if (style !== "string" && style !== "comment") {
+ break;
+ }
+ }
+ } while (symbol);
+ if (symbol) {
+ var matched = cm.findMatchingBracket(Pos(line, ch));
+ return matched.to;
+ } else {
+ return cursor;
+ }
+ },
+ moveToStartOfLine: function(_cm, head) {
+ return Pos(head.line, 0);
+ },
+ moveToLineOrEdgeOfDocument: function(cm, _head, motionArgs) {
+ var lineNum = motionArgs.forward ? cm.lastLine() : cm.firstLine();
+ if (motionArgs.repeatIsExplicit) {
+ lineNum = motionArgs.repeat - cm.getOption('firstLineNumber');
+ }
+ return Pos(lineNum,
+ findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum)));
+ },
+ textObjectManipulation: function(cm, head, motionArgs, vim) {
+ // TODO: lots of possible exceptions that can be thrown here. Try da(
+ // outside of a () block.
+
+ // TODO: adding <> >< to this map doesn't work, presumably because
+ // they're operators
+ var mirroredPairs = {'(': ')', ')': '(',
+ '{': '}', '}': '{',
+ '[': ']', ']': '['};
+ var selfPaired = {'\'': true, '"': true};
+
+ var character = motionArgs.selectedCharacter;
+ // 'b' refers to '()' block.
+ // 'B' refers to '{}' block.
+ if (character == 'b') {
+ character = '(';
+ } else if (character == 'B') {
+ character = '{';
+ }
+
+ // Inclusive is the difference between a and i
+ // TODO: Instead of using the additional text object map to perform text
+ // object operations, merge the map into the defaultKeyMap and use
+ // motionArgs to define behavior. Define separate entries for 'aw',
+ // 'iw', 'a[', 'i[', etc.
+ var inclusive = !motionArgs.textObjectInner;
+
+ var tmp;
+ if (mirroredPairs[character]) {
+ tmp = selectCompanionObject(cm, head, character, inclusive);
+ } else if (selfPaired[character]) {
+ tmp = findBeginningAndEnd(cm, head, character, inclusive);
+ } else if (character === 'W') {
+ tmp = expandWordUnderCursor(cm, inclusive, true /** forward */,
+ true /** bigWord */);
+ } else if (character === 'w') {
+ tmp = expandWordUnderCursor(cm, inclusive, true /** forward */,
+ false /** bigWord */);
+ } else if (character === 'p') {
+ tmp = findParagraph(cm, head, motionArgs.repeat, 0, inclusive);
+ motionArgs.linewise = true;
+ if (vim.visualMode) {
+ if (!vim.visualLine) { vim.visualLine = true; }
+ } else {
+ var operatorArgs = vim.inputState.operatorArgs;
+ if (operatorArgs) { operatorArgs.linewise = true; }
+ tmp.end.line--;
+ }
+ } else {
+ // No text object defined for this, don't move.
+ return null;
+ }
+
+ if (!cm.state.vim.visualMode) {
+ return [tmp.start, tmp.end];
+ } else {
+ return expandSelection(cm, tmp.start, tmp.end);
+ }
+ },
+
+ repeatLastCharacterSearch: function(cm, head, motionArgs) {
+ var lastSearch = vimGlobalState.lastChararacterSearch;
+ var repeat = motionArgs.repeat;
+ var forward = motionArgs.forward === lastSearch.forward;
+ var increment = (lastSearch.increment ? 1 : 0) * (forward ? -1 : 1);
+ cm.moveH(-increment, 'char');
+ motionArgs.inclusive = forward ? true : false;
+ var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter);
+ if (!curEnd) {
+ cm.moveH(increment, 'char');
+ return head;
+ }
+ curEnd.ch += increment;
+ return curEnd;
+ }
+ };
+
+ function defineMotion(name, fn) {
+ motions[name] = fn;
+ }
+
+ function fillArray(val, times) {
+ var arr = [];
+ for (var i = 0; i < times; i++) {
+ arr.push(val);
+ }
+ return arr;
+ }
+ /**
+ * An operator acts on a text selection. It receives the list of selections
+ * as input. The corresponding CodeMirror selection is guaranteed to
+ * match the input selection.
+ */
+ var operators = {
+ change: function(cm, args, ranges) {
+ var finalHead, text;
+ var vim = cm.state.vim;
+ vimGlobalState.macroModeState.lastInsertModeChanges.inVisualBlock = vim.visualBlock;
+ if (!vim.visualMode) {
+ var anchor = ranges[0].anchor,
+ head = ranges[0].head;
+ text = cm.getRange(anchor, head);
+ 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 && lastState.motionArgs && lastState.motionArgs.forward) {
+ head = offsetCursor(head, 0, - match[0].length);
+ text = text.slice(0, - match[0].length);
+ }
+ }
+ var prevLineEnd = new Pos(anchor.line - 1, Number.MAX_VALUE);
+ var wasLastLine = cm.firstLine() == cm.lastLine();
+ if (head.line > cm.lastLine() && args.linewise && !wasLastLine) {
+ cm.replaceRange('', prevLineEnd, head);
+ } else {
+ cm.replaceRange('', anchor, head);
+ }
+ if (args.linewise) {
+ // Push the next line back down, if there is a next line.
+ if (!wasLastLine) {
+ cm.setCursor(prevLineEnd);
+ CodeMirror.commands.newlineAndIndent(cm);
+ }
+ // make sure cursor ends up at the end of the line.
+ anchor.ch = Number.MAX_VALUE;
+ }
+ finalHead = anchor;
+ } else {
+ text = cm.getSelection();
+ var replacement = fillArray('', ranges.length);
+ cm.replaceSelections(replacement);
+ finalHead = cursorMin(ranges[0].head, ranges[0].anchor);
+ }
+ vimGlobalState.registerController.pushText(
+ args.registerName, 'change', text,
+ args.linewise, ranges.length > 1);
+ actions.enterInsertMode(cm, {head: finalHead}, cm.state.vim);
+ },
+ // delete is a javascript keyword.
+ 'delete': function(cm, args, ranges) {
+ var finalHead, text;
+ var vim = cm.state.vim;
+ if (!vim.visualBlock) {
+ var anchor = ranges[0].anchor,
+ head = ranges[0].head;
+ if (args.linewise &&
+ head.line != cm.firstLine() &&
+ anchor.line == cm.lastLine() &&
+ anchor.line == head.line - 1) {
+ // Special case for dd on last line (and first line).
+ if (anchor.line == cm.firstLine()) {
+ anchor.ch = 0;
+ } else {
+ anchor = Pos(anchor.line - 1, lineLength(cm, anchor.line - 1));
+ }
+ }
+ text = cm.getRange(anchor, head);
+ cm.replaceRange('', anchor, head);
+ finalHead = anchor;
+ if (args.linewise) {
+ finalHead = motions.moveToFirstNonWhiteSpaceCharacter(cm, anchor);
+ }
+ } else {
+ text = cm.getSelection();
+ var replacement = fillArray('', ranges.length);
+ cm.replaceSelections(replacement);
+ finalHead = ranges[0].anchor;
+ }
+ vimGlobalState.registerController.pushText(
+ args.registerName, 'delete', text,
+ args.linewise, vim.visualBlock);
+ return clipCursorToContent(cm, finalHead);
+ },
+ indent: function(cm, args, ranges) {
+ var vim = cm.state.vim;
+ var startLine = ranges[0].anchor.line;
+ var endLine = vim.visualBlock ?
+ ranges[ranges.length - 1].anchor.line :
+ ranges[0].head.line;
+ // In visual mode, n> shifts the selection right n times, instead of
+ // shifting n lines right once.
+ var repeat = (vim.visualMode) ? args.repeat : 1;
+ if (args.linewise) {
+ // The only way to delete a newline is to delete until the start of
+ // the next line, so in linewise mode evalInput will include the next
+ // line. We don't want this in indent, so we go back a line.
+ endLine--;
+ }
+ for (var i = startLine; i <= endLine; i++) {
+ for (var j = 0; j < repeat; j++) {
+ cm.indentLine(i, args.indentRight);
+ }
+ }
+ return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor);
+ },
+ changeCase: function(cm, args, ranges, oldAnchor, newHead) {
+ var selections = cm.getSelections();
+ var swapped = [];
+ var toLower = args.toLower;
+ for (var j = 0; j < selections.length; j++) {
+ var toSwap = selections[j];
+ var text = '';
+ if (toLower === true) {
+ text = toSwap.toLowerCase();
+ } else if (toLower === false) {
+ text = toSwap.toUpperCase();
+ } else {
+ for (var i = 0; i < toSwap.length; i++) {
+ var character = toSwap.charAt(i);
+ text += isUpperCase(character) ? character.toLowerCase() :
+ character.toUpperCase();
+ }
+ }
+ swapped.push(text);
+ }
+ cm.replaceSelections(swapped);
+ if (args.shouldMoveCursor){
+ return newHead;
+ } else if (!cm.state.vim.visualMode && args.linewise && ranges[0].anchor.line + 1 == ranges[0].head.line) {
+ return motions.moveToFirstNonWhiteSpaceCharacter(cm, oldAnchor);
+ } else if (args.linewise){
+ return oldAnchor;
+ } else {
+ return cursorMin(ranges[0].anchor, ranges[0].head);
+ }
+ },
+ yank: function(cm, args, ranges, oldAnchor) {
+ var vim = cm.state.vim;
+ var text = cm.getSelection();
+ var endPos = vim.visualMode
+ ? cursorMin(vim.sel.anchor, vim.sel.head, ranges[0].head, ranges[0].anchor)
+ : oldAnchor;
+ vimGlobalState.registerController.pushText(
+ args.registerName, 'yank',
+ text, args.linewise, vim.visualBlock);
+ return endPos;
+ }
+ };
+
+ function defineOperator(name, fn) {
+ operators[name] = fn;
+ }
+
+ var actions = {
+ jumpListWalk: function(cm, actionArgs, vim) {
+ if (vim.visualMode) {
+ return;
+ }
+ var repeat = actionArgs.repeat;
+ var forward = actionArgs.forward;
+ var jumpList = vimGlobalState.jumpList;
+
+ var mark = jumpList.move(cm, forward ? repeat : -repeat);
+ var markPos = mark ? mark.find() : undefined;
+ markPos = markPos ? markPos : cm.getCursor();
+ cm.setCursor(markPos);
+ },
+ scroll: function(cm, actionArgs, vim) {
+ if (vim.visualMode) {
+ return;
+ }
+ var repeat = actionArgs.repeat || 1;
+ var lineHeight = cm.defaultTextHeight();
+ var top = cm.getScrollInfo().top;
+ var delta = lineHeight * repeat;
+ var newPos = actionArgs.forward ? top + delta : top - delta;
+ var cursor = copyCursor(cm.getCursor());
+ var cursorCoords = cm.charCoords(cursor, 'local');
+ if (actionArgs.forward) {
+ if (newPos > cursorCoords.top) {
+ cursor.line += (newPos - cursorCoords.top) / lineHeight;
+ cursor.line = Math.ceil(cursor.line);
+ cm.setCursor(cursor);
+ cursorCoords = cm.charCoords(cursor, 'local');
+ cm.scrollTo(null, cursorCoords.top);
+ } else {
+ // Cursor stays within bounds. Just reposition the scroll window.
+ cm.scrollTo(null, newPos);
+ }
+ } else {
+ var newBottom = newPos + cm.getScrollInfo().clientHeight;
+ if (newBottom < cursorCoords.bottom) {
+ cursor.line -= (cursorCoords.bottom - newBottom) / lineHeight;
+ cursor.line = Math.floor(cursor.line);
+ cm.setCursor(cursor);
+ cursorCoords = cm.charCoords(cursor, 'local');
+ cm.scrollTo(
+ null, cursorCoords.bottom - cm.getScrollInfo().clientHeight);
+ } else {
+ // Cursor stays within bounds. Just reposition the scroll window.
+ cm.scrollTo(null, newPos);
+ }
+ }
+ },
+ scrollToCursor: function(cm, actionArgs) {
+ var lineNum = cm.getCursor().line;
+ var charCoords = cm.charCoords(Pos(lineNum, 0), 'local');
+ var height = cm.getScrollInfo().clientHeight;
+ var y = charCoords.top;
+ var lineHeight = charCoords.bottom - y;
+ switch (actionArgs.position) {
+ case 'center': y = y - (height / 2) + lineHeight;
+ break;
+ case 'bottom': y = y - height + lineHeight;
+ break;
+ }
+ cm.scrollTo(null, y);
+ },
+ replayMacro: function(cm, actionArgs, vim) {
+ var registerName = actionArgs.selectedCharacter;
+ var repeat = actionArgs.repeat;
+ var macroModeState = vimGlobalState.macroModeState;
+ if (registerName == '@') {
+ registerName = macroModeState.latestRegister;
+ }
+ while(repeat--){
+ executeMacroRegister(cm, vim, macroModeState, registerName);
+ }
+ },
+ enterMacroRecordMode: function(cm, actionArgs) {
+ var macroModeState = vimGlobalState.macroModeState;
+ var registerName = actionArgs.selectedCharacter;
+ macroModeState.enterMacroRecordMode(cm, registerName);
+ },
+ enterInsertMode: function(cm, actionArgs, vim) {
+ if (cm.getOption('readOnly')) { return; }
+ vim.insertMode = true;
+ vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1;
+ var insertAt = (actionArgs) ? actionArgs.insertAt : null;
+ var sel = vim.sel;
+ var head = actionArgs.head || cm.getCursor('head');
+ var height = cm.listSelections().length;
+ if (insertAt == 'eol') {
+ head = Pos(head.line, lineLength(cm, head.line));
+ } else if (insertAt == 'charAfter') {
+ head = offsetCursor(head, 0, 1);
+ } else if (insertAt == 'firstNonBlank') {
+ head = motions.moveToFirstNonWhiteSpaceCharacter(cm, head);
+ } else if (insertAt == 'startOfSelectedArea') {
+ if (!vim.visualBlock) {
+ if (sel.head.line < sel.anchor.line) {
+ head = sel.head;
+ } else {
+ head = Pos(sel.anchor.line, 0);
+ }
+ } else {
+ head = Pos(
+ Math.min(sel.head.line, sel.anchor.line),
+ Math.min(sel.head.ch, sel.anchor.ch));
+ height = Math.abs(sel.head.line - sel.anchor.line) + 1;
+ }
+ } else if (insertAt == 'endOfSelectedArea') {
+ if (!vim.visualBlock) {
+ if (sel.head.line >= sel.anchor.line) {
+ head = offsetCursor(sel.head, 0, 1);
+ } else {
+ head = Pos(sel.anchor.line, 0);
+ }
+ } else {
+ head = Pos(
+ Math.min(sel.head.line, sel.anchor.line),
+ Math.max(sel.head.ch + 1, sel.anchor.ch));
+ height = Math.abs(sel.head.line - sel.anchor.line) + 1;
+ }
+ } else if (insertAt == 'inplace') {
+ if (vim.visualMode){
+ return;
+ }
+ }
+ cm.setOption('keyMap', 'vim-insert');
+ cm.setOption('disableInput', false);
+ if (actionArgs && actionArgs.replace) {
+ // Handle Replace-mode as a special case of insert mode.
+ cm.toggleOverwrite(true);
+ cm.setOption('keyMap', 'vim-replace');
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"});
+ } else {
+ cm.setOption('keyMap', 'vim-insert');
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"});
+ }
+ if (!vimGlobalState.macroModeState.isPlaying) {
+ // Only record if not replaying.
+ cm.on('change', onChange);
+ CodeMirror.on(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
+ }
+ if (vim.visualMode) {
+ exitVisualMode(cm);
+ }
+ selectForInsert(cm, head, height);
+ },
+ toggleVisualMode: function(cm, actionArgs, vim) {
+ var repeat = actionArgs.repeat;
+ var anchor = cm.getCursor();
+ var head;
+ // TODO: The repeat should actually select number of characters/lines
+ // equal to the repeat times the size of the previous visual
+ // operation.
+ if (!vim.visualMode) {
+ // Entering visual mode
+ vim.visualMode = true;
+ vim.visualLine = !!actionArgs.linewise;
+ vim.visualBlock = !!actionArgs.blockwise;
+ head = clipCursorToContent(
+ cm, Pos(anchor.line, anchor.ch + repeat - 1),
+ true /** includeLineBreak */);
+ vim.sel = {
+ anchor: anchor,
+ head: head
+ };
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""});
+ updateCmSelection(cm);
+ updateMark(cm, vim, '<', cursorMin(anchor, head));
+ updateMark(cm, vim, '>', cursorMax(anchor, head));
+ } else if (vim.visualLine ^ actionArgs.linewise ||
+ vim.visualBlock ^ actionArgs.blockwise) {
+ // Toggling between modes
+ vim.visualLine = !!actionArgs.linewise;
+ vim.visualBlock = !!actionArgs.blockwise;
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""});
+ updateCmSelection(cm);
+ } else {
+ exitVisualMode(cm);
+ }
+ },
+ reselectLastSelection: function(cm, _actionArgs, vim) {
+ var lastSelection = vim.lastSelection;
+ if (vim.visualMode) {
+ updateLastSelection(cm, vim);
+ }
+ if (lastSelection) {
+ var anchor = lastSelection.anchorMark.find();
+ var head = lastSelection.headMark.find();
+ if (!anchor || !head) {
+ // If the marks have been destroyed due to edits, do nothing.
+ return;
+ }
+ vim.sel = {
+ anchor: anchor,
+ head: head
+ };
+ vim.visualMode = true;
+ vim.visualLine = lastSelection.visualLine;
+ vim.visualBlock = lastSelection.visualBlock;
+ updateCmSelection(cm);
+ updateMark(cm, vim, '<', cursorMin(anchor, head));
+ updateMark(cm, vim, '>', cursorMax(anchor, head));
+ CodeMirror.signal(cm, 'vim-mode-change', {
+ mode: 'visual',
+ subMode: vim.visualLine ? 'linewise' :
+ vim.visualBlock ? 'blockwise' : ''});
+ }
+ },
+ joinLines: function(cm, actionArgs, vim) {
+ var curStart, curEnd;
+ if (vim.visualMode) {
+ curStart = cm.getCursor('anchor');
+ curEnd = cm.getCursor('head');
+ if (cursorIsBefore(curEnd, curStart)) {
+ var tmp = curEnd;
+ curEnd = curStart;
+ curStart = tmp;
+ }
+ curEnd.ch = lineLength(cm, curEnd.line) - 1;
+ } else {
+ // Repeat is the number of lines to join. Minimum 2 lines.
+ var repeat = Math.max(actionArgs.repeat, 2);
+ curStart = cm.getCursor();
+ curEnd = clipCursorToContent(cm, Pos(curStart.line + repeat - 1,
+ Infinity));
+ }
+ var finalCh = 0;
+ for (var i = curStart.line; i < curEnd.line; i++) {
+ finalCh = lineLength(cm, curStart.line);
+ var tmp = Pos(curStart.line + 1,
+ lineLength(cm, curStart.line + 1));
+ var text = cm.getRange(curStart, tmp);
+ text = text.replace(/\n\s*/g, ' ');
+ cm.replaceRange(text, curStart, tmp);
+ }
+ var curFinalPos = Pos(curStart.line, finalCh);
+ if (vim.visualMode) {
+ exitVisualMode(cm, false);
+ }
+ cm.setCursor(curFinalPos);
+ },
+ newLineAndEnterInsertMode: function(cm, actionArgs, vim) {
+ vim.insertMode = true;
+ var insertAt = copyCursor(cm.getCursor());
+ if (insertAt.line === cm.firstLine() && !actionArgs.after) {
+ // Special case for inserting newline before start of document.
+ cm.replaceRange('\n', Pos(cm.firstLine(), 0));
+ cm.setCursor(cm.firstLine(), 0);
+ } else {
+ insertAt.line = (actionArgs.after) ? insertAt.line :
+ insertAt.line - 1;
+ insertAt.ch = lineLength(cm, insertAt.line);
+ cm.setCursor(insertAt);
+ var newlineFn = CodeMirror.commands.newlineAndIndentContinueComment ||
+ CodeMirror.commands.newlineAndIndent;
+ newlineFn(cm);
+ }
+ this.enterInsertMode(cm, { repeat: actionArgs.repeat }, vim);
+ },
+ paste: function(cm, actionArgs, vim) {
+ var cur = copyCursor(cm.getCursor());
+ var register = vimGlobalState.registerController.getRegister(
+ actionArgs.registerName);
+ var text = register.toString();
+ if (!text) {
+ return;
+ }
+ if (actionArgs.matchIndent) {
+ var tabSize = cm.getOption("tabSize");
+ // length that considers tabs and tabSize
+ var whitespaceLength = function(str) {
+ var tabs = (str.split("\t").length - 1);
+ var spaces = (str.split(" ").length - 1);
+ return tabs * tabSize + spaces * 1;
+ };
+ var currentLine = cm.getLine(cm.getCursor().line);
+ var indent = whitespaceLength(currentLine.match(/^\s*/)[0]);
+ // chomp last newline b/c don't want it to match /^\s*/gm
+ var chompedText = text.replace(/\n$/, '');
+ var wasChomped = text !== chompedText;
+ var firstIndent = whitespaceLength(text.match(/^\s*/)[0]);
+ var text = chompedText.replace(/^\s*/gm, function(wspace) {
+ var newIndent = indent + (whitespaceLength(wspace) - firstIndent);
+ if (newIndent < 0) {
+ return "";
+ }
+ else if (cm.getOption("indentWithTabs")) {
+ var quotient = Math.floor(newIndent / tabSize);
+ return Array(quotient + 1).join('\t');
+ }
+ else {
+ return Array(newIndent + 1).join(' ');
+ }
+ });
+ text += wasChomped ? "\n" : "";
+ }
+ if (actionArgs.repeat > 1) {
+ var text = Array(actionArgs.repeat + 1).join(text);
+ }
+ var linewise = register.linewise;
+ var blockwise = register.blockwise;
+ if (linewise) {
+ if(vim.visualMode) {
+ text = vim.visualLine ? text.slice(0, -1) : '\n' + text.slice(0, text.length - 1) + '\n';
+ } else if (actionArgs.after) {
+ // Move the newline at the end to the start instead, and paste just
+ // before the newline character of the line we are on right now.
+ text = '\n' + text.slice(0, text.length - 1);
+ cur.ch = lineLength(cm, cur.line);
+ } else {
+ cur.ch = 0;
+ }
+ } else {
+ if (blockwise) {
+ text = text.split('\n');
+ for (var i = 0; i < text.length; i++) {
+ text[i] = (text[i] == '') ? ' ' : text[i];
+ }
+ }
+ cur.ch += actionArgs.after ? 1 : 0;
+ }
+ var curPosFinal;
+ var idx;
+ if (vim.visualMode) {
+ // save the pasted text for reselection if the need arises
+ vim.lastPastedText = text;
+ var lastSelectionCurEnd;
+ var selectedArea = getSelectedAreaRange(cm, vim);
+ var selectionStart = selectedArea[0];
+ var selectionEnd = selectedArea[1];
+ var selectedText = cm.getSelection();
+ var selections = cm.listSelections();
+ var emptyStrings = new Array(selections.length).join('1').split('1');
+ // save the curEnd marker before it get cleared due to cm.replaceRange.
+ if (vim.lastSelection) {
+ lastSelectionCurEnd = vim.lastSelection.headMark.find();
+ }
+ // push the previously selected text to unnamed register
+ vimGlobalState.registerController.unnamedRegister.setText(selectedText);
+ if (blockwise) {
+ // first delete the selected text
+ cm.replaceSelections(emptyStrings);
+ // Set new selections as per the block length of the yanked text
+ selectionEnd = Pos(selectionStart.line + text.length-1, selectionStart.ch);
+ cm.setCursor(selectionStart);
+ selectBlock(cm, selectionEnd);
+ cm.replaceSelections(text);
+ curPosFinal = selectionStart;
+ } else if (vim.visualBlock) {
+ cm.replaceSelections(emptyStrings);
+ cm.setCursor(selectionStart);
+ cm.replaceRange(text, selectionStart, selectionStart);
+ curPosFinal = selectionStart;
+ } else {
+ cm.replaceRange(text, selectionStart, selectionEnd);
+ curPosFinal = cm.posFromIndex(cm.indexFromPos(selectionStart) + text.length - 1);
+ }
+ // restore the the curEnd marker
+ if(lastSelectionCurEnd) {
+ vim.lastSelection.headMark = cm.setBookmark(lastSelectionCurEnd);
+ }
+ if (linewise) {
+ curPosFinal.ch=0;
+ }
+ } else {
+ if (blockwise) {
+ cm.setCursor(cur);
+ for (var i = 0; i < text.length; i++) {
+ var line = cur.line+i;
+ if (line > cm.lastLine()) {
+ cm.replaceRange('\n', Pos(line, 0));
+ }
+ var lastCh = lineLength(cm, line);
+ if (lastCh < cur.ch) {
+ extendLineToColumn(cm, line, cur.ch);
+ }
+ }
+ cm.setCursor(cur);
+ selectBlock(cm, Pos(cur.line + text.length-1, cur.ch));
+ cm.replaceSelections(text);
+ curPosFinal = cur;
+ } else {
+ cm.replaceRange(text, cur);
+ // Now fine tune the cursor to where we want it.
+ if (linewise && actionArgs.after) {
+ curPosFinal = Pos(
+ cur.line + 1,
+ findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1)));
+ } else if (linewise && !actionArgs.after) {
+ curPosFinal = Pos(
+ cur.line,
+ findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line)));
+ } else if (!linewise && actionArgs.after) {
+ idx = cm.indexFromPos(cur);
+ curPosFinal = cm.posFromIndex(idx + text.length - 1);
+ } else {
+ idx = cm.indexFromPos(cur);
+ curPosFinal = cm.posFromIndex(idx + text.length);
+ }
+ }
+ }
+ if (vim.visualMode) {
+ exitVisualMode(cm, false);
+ }
+ cm.setCursor(curPosFinal);
+ },
+ undo: function(cm, actionArgs) {
+ cm.operation(function() {
+ repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)();
+ cm.setCursor(cm.getCursor('anchor'));
+ });
+ },
+ redo: function(cm, actionArgs) {
+ repeatFn(cm, CodeMirror.commands.redo, actionArgs.repeat)();
+ },
+ setRegister: function(_cm, actionArgs, vim) {
+ vim.inputState.registerName = actionArgs.selectedCharacter;
+ },
+ setMark: function(cm, actionArgs, vim) {
+ var markName = actionArgs.selectedCharacter;
+ updateMark(cm, vim, markName, cm.getCursor());
+ },
+ replace: function(cm, actionArgs, vim) {
+ var replaceWith = actionArgs.selectedCharacter;
+ var curStart = cm.getCursor();
+ var replaceTo;
+ var curEnd;
+ var selections = cm.listSelections();
+ if (vim.visualMode) {
+ curStart = cm.getCursor('start');
+ curEnd = cm.getCursor('end');
+ } else {
+ var line = cm.getLine(curStart.line);
+ replaceTo = curStart.ch + actionArgs.repeat;
+ if (replaceTo > line.length) {
+ replaceTo=line.length;
+ }
+ curEnd = Pos(curStart.line, replaceTo);
+ }
+ if (replaceWith=='\n') {
+ if (!vim.visualMode) cm.replaceRange('', curStart, curEnd);
+ // special case, where vim help says to replace by just one line-break
+ (CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm);
+ } else {
+ var replaceWithStr = cm.getRange(curStart, curEnd);
+ //replace all characters in range by selected, but keep linebreaks
+ replaceWithStr = replaceWithStr.replace(/[^\n]/g, replaceWith);
+ if (vim.visualBlock) {
+ // Tabs are split in visua block before replacing
+ var spaces = new Array(cm.getOption("tabSize")+1).join(' ');
+ replaceWithStr = cm.getSelection();
+ replaceWithStr = replaceWithStr.replace(/\t/g, spaces).replace(/[^\n]/g, replaceWith).split('\n');
+ cm.replaceSelections(replaceWithStr);
+ } else {
+ cm.replaceRange(replaceWithStr, curStart, curEnd);
+ }
+ if (vim.visualMode) {
+ curStart = cursorIsBefore(selections[0].anchor, selections[0].head) ?
+ selections[0].anchor : selections[0].head;
+ cm.setCursor(curStart);
+ exitVisualMode(cm, false);
+ } else {
+ cm.setCursor(offsetCursor(curEnd, 0, -1));
+ }
+ }
+ },
+ incrementNumberToken: function(cm, actionArgs) {
+ var cur = cm.getCursor();
+ var lineStr = cm.getLine(cur.line);
+ var re = /-?\d+/g;
+ var match;
+ var start;
+ var end;
+ var numberStr;
+ var token;
+ while ((match = re.exec(lineStr)) !== null) {
+ token = match[0];
+ start = match.index;
+ end = start + token.length;
+ if (cur.ch < end)break;
+ }
+ if (!actionArgs.backtrack && (end <= cur.ch))return;
+ if (token) {
+ var increment = actionArgs.increase ? 1 : -1;
+ var number = parseInt(token) + (increment * actionArgs.repeat);
+ var from = Pos(cur.line, start);
+ var to = Pos(cur.line, end);
+ numberStr = number.toString();
+ cm.replaceRange(numberStr, from, to);
+ } else {
+ return;
+ }
+ cm.setCursor(Pos(cur.line, start + numberStr.length - 1));
+ },
+ repeatLastEdit: function(cm, actionArgs, vim) {
+ var lastEditInputState = vim.lastEditInputState;
+ if (!lastEditInputState) { return; }
+ var repeat = actionArgs.repeat;
+ if (repeat && actionArgs.repeatIsExplicit) {
+ vim.lastEditInputState.repeatOverride = repeat;
+ } else {
+ repeat = vim.lastEditInputState.repeatOverride || repeat;
+ }
+ repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */);
+ },
+ exitInsertMode: exitInsertMode
+ };
+
+ function defineAction(name, fn) {
+ actions[name] = fn;
+ }
+
+ /*
+ * Below are miscellaneous utility functions used by vim.js
+ */
+
+ /**
+ * Clips cursor to ensure that line is within the buffer's range
+ * If includeLineBreak is true, then allow cur.ch == lineLength.
+ */
+ function clipCursorToContent(cm, cur, includeLineBreak) {
+ var line = Math.min(Math.max(cm.firstLine(), cur.line), cm.lastLine() );
+ var maxCh = lineLength(cm, line) - 1;
+ maxCh = (includeLineBreak) ? maxCh + 1 : maxCh;
+ var ch = Math.min(Math.max(0, cur.ch), maxCh);
+ return Pos(line, ch);
+ }
+ function copyArgs(args) {
+ var ret = {};
+ for (var prop in args) {
+ if (args.hasOwnProperty(prop)) {
+ ret[prop] = args[prop];
+ }
+ }
+ return ret;
+ }
+ function offsetCursor(cur, offsetLine, offsetCh) {
+ if (typeof offsetLine === 'object') {
+ offsetCh = offsetLine.ch;
+ offsetLine = offsetLine.line;
+ }
+ return Pos(cur.line + offsetLine, cur.ch + offsetCh);
+ }
+ function getOffset(anchor, head) {
+ return {
+ line: head.line - anchor.line,
+ ch: head.line - anchor.line
+ };
+ }
+ function commandMatches(keys, keyMap, context, inputState) {
+ // Partial matches are not applied. They inform the key handler
+ // that the current key sequence is a subsequence of a valid key
+ // sequence, so that the key buffer is not cleared.
+ var match, partial = [], full = [];
+ for (var i = 0; i < keyMap.length; i++) {
+ var command = keyMap[i];
+ if (context == 'insert' && command.context != 'insert' ||
+ command.context && command.context != context ||
+ inputState.operator && command.type == 'action' ||
+ !(match = commandMatch(keys, command.keys))) { continue; }
+ if (match == 'partial') { partial.push(command); }
+ if (match == 'full') { full.push(command); }
+ }
+ return {
+ partial: partial.length && partial,
+ full: full.length && full
+ };
+ }
+ function commandMatch(pressed, mapped) {
+ if (mapped.slice(-11) == '') {
+ // Last character matches anything.
+ var prefixLen = mapped.length - 11;
+ var pressedPrefix = pressed.slice(0, prefixLen);
+ var mappedPrefix = mapped.slice(0, prefixLen);
+ return pressedPrefix == mappedPrefix && pressed.length > prefixLen ? 'full' :
+ mappedPrefix.indexOf(pressedPrefix) == 0 ? 'partial' : false;
+ } else {
+ return pressed == mapped ? 'full' :
+ mapped.indexOf(pressed) == 0 ? 'partial' : false;
+ }
+ }
+ function lastChar(keys) {
+ var match = /^.*(<[\w\-]+>)$/.exec(keys);
+ var selectedCharacter = match ? match[1] : keys.slice(-1);
+ if (selectedCharacter.length > 1){
+ switch(selectedCharacter){
+ case '':
+ selectedCharacter='\n';
+ break;
+ case '':
+ selectedCharacter=' ';
+ break;
+ default:
+ break;
+ }
+ }
+ return selectedCharacter;
+ }
+ function repeatFn(cm, fn, repeat) {
+ return function() {
+ for (var i = 0; i < repeat; i++) {
+ fn(cm);
+ }
+ };
+ }
+ function copyCursor(cur) {
+ return Pos(cur.line, cur.ch);
+ }
+ function cursorEqual(cur1, cur2) {
+ return cur1.ch == cur2.ch && cur1.line == cur2.line;
+ }
+ function cursorIsBefore(cur1, cur2) {
+ if (cur1.line < cur2.line) {
+ return true;
+ }
+ if (cur1.line == cur2.line && cur1.ch < cur2.ch) {
+ return true;
+ }
+ return false;
+ }
+ function cursorMin(cur1, cur2) {
+ if (arguments.length > 2) {
+ cur2 = cursorMin.apply(undefined, Array.prototype.slice.call(arguments, 1));
+ }
+ return cursorIsBefore(cur1, cur2) ? cur1 : cur2;
+ }
+ function cursorMax(cur1, cur2) {
+ if (arguments.length > 2) {
+ cur2 = cursorMax.apply(undefined, Array.prototype.slice.call(arguments, 1));
+ }
+ return cursorIsBefore(cur1, cur2) ? cur2 : cur1;
+ }
+ function cursorIsBetween(cur1, cur2, cur3) {
+ // returns true if cur2 is between cur1 and cur3.
+ var cur1before2 = cursorIsBefore(cur1, cur2);
+ var cur2before3 = cursorIsBefore(cur2, cur3);
+ return cur1before2 && cur2before3;
+ }
+ function lineLength(cm, lineNum) {
+ return cm.getLine(lineNum).length;
+ }
+ function trim(s) {
+ if (s.trim) {
+ return s.trim();
+ }
+ return s.replace(/^\s+|\s+$/g, '');
+ }
+ function escapeRegex(s) {
+ return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1');
+ }
+ function extendLineToColumn(cm, lineNum, column) {
+ var endCh = lineLength(cm, lineNum);
+ var spaces = new Array(column-endCh+1).join(' ');
+ cm.setCursor(Pos(lineNum, endCh));
+ cm.replaceRange(spaces, cm.getCursor());
+ }
+ // This functions selects a rectangular block
+ // of text with selectionEnd as any of its corner
+ // Height of block:
+ // Difference in selectionEnd.line and first/last selection.line
+ // Width of the block:
+ // Distance between selectionEnd.ch and any(first considered here) selection.ch
+ function selectBlock(cm, selectionEnd) {
+ var selections = [], ranges = cm.listSelections();
+ var head = copyCursor(cm.clipPos(selectionEnd));
+ var isClipped = !cursorEqual(selectionEnd, head);
+ var curHead = cm.getCursor('head');
+ var primIndex = getIndex(ranges, curHead);
+ var wasClipped = cursorEqual(ranges[primIndex].head, ranges[primIndex].anchor);
+ var max = ranges.length - 1;
+ var index = max - primIndex > primIndex ? max : 0;
+ var base = ranges[index].anchor;
+
+ var firstLine = Math.min(base.line, head.line);
+ var lastLine = Math.max(base.line, head.line);
+ var baseCh = base.ch, headCh = head.ch;
+
+ var dir = ranges[index].head.ch - baseCh;
+ var newDir = headCh - baseCh;
+ if (dir > 0 && newDir <= 0) {
+ baseCh++;
+ if (!isClipped) { headCh--; }
+ } else if (dir < 0 && newDir >= 0) {
+ baseCh--;
+ if (!wasClipped) { headCh++; }
+ } else if (dir < 0 && newDir == -1) {
+ baseCh--;
+ headCh++;
+ }
+ for (var line = firstLine; line <= lastLine; line++) {
+ var range = {anchor: new Pos(line, baseCh), head: new Pos(line, headCh)};
+ selections.push(range);
+ }
+ primIndex = head.line == lastLine ? selections.length - 1 : 0;
+ cm.setSelections(selections);
+ selectionEnd.ch = headCh;
+ base.ch = baseCh;
+ return base;
+ }
+ function selectForInsert(cm, head, height) {
+ var sel = [];
+ for (var i = 0; i < height; i++) {
+ var lineHead = offsetCursor(head, i, 0);
+ sel.push({anchor: lineHead, head: lineHead});
+ }
+ cm.setSelections(sel, 0);
+ }
+ // getIndex returns the index of the cursor in the selections.
+ function getIndex(ranges, cursor, end) {
+ for (var i = 0; i < ranges.length; i++) {
+ var atAnchor = end != 'head' && cursorEqual(ranges[i].anchor, cursor);
+ var atHead = end != 'anchor' && cursorEqual(ranges[i].head, cursor);
+ if (atAnchor || atHead) {
+ return i;
+ }
+ }
+ return -1;
+ }
+ function getSelectedAreaRange(cm, vim) {
+ var lastSelection = vim.lastSelection;
+ var getCurrentSelectedAreaRange = function() {
+ var selections = cm.listSelections();
+ var start = selections[0];
+ var end = selections[selections.length-1];
+ var selectionStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head;
+ var selectionEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor;
+ return [selectionStart, selectionEnd];
+ };
+ var getLastSelectedAreaRange = function() {
+ var selectionStart = cm.getCursor();
+ var selectionEnd = cm.getCursor();
+ var block = lastSelection.visualBlock;
+ if (block) {
+ var width = block.width;
+ var height = block.height;
+ selectionEnd = Pos(selectionStart.line + height, selectionStart.ch + width);
+ var selections = [];
+ // selectBlock creates a 'proper' rectangular block.
+ // We do not want that in all cases, so we manually set selections.
+ for (var i = selectionStart.line; i < selectionEnd.line; i++) {
+ var anchor = Pos(i, selectionStart.ch);
+ var head = Pos(i, selectionEnd.ch);
+ var range = {anchor: anchor, head: head};
+ selections.push(range);
+ }
+ cm.setSelections(selections);
+ } else {
+ var start = lastSelection.anchorMark.find();
+ var end = lastSelection.headMark.find();
+ var line = end.line - start.line;
+ var ch = end.ch - start.ch;
+ selectionEnd = {line: selectionEnd.line + line, ch: line ? selectionEnd.ch : ch + selectionEnd.ch};
+ if (lastSelection.visualLine) {
+ selectionStart = Pos(selectionStart.line, 0);
+ selectionEnd = Pos(selectionEnd.line, lineLength(cm, selectionEnd.line));
+ }
+ cm.setSelection(selectionStart, selectionEnd);
+ }
+ return [selectionStart, selectionEnd];
+ };
+ if (!vim.visualMode) {
+ // In case of replaying the action.
+ return getLastSelectedAreaRange();
+ } else {
+ return getCurrentSelectedAreaRange();
+ }
+ }
+ // Updates the previous selection with the current selection's values. This
+ // should only be called in visual mode.
+ function updateLastSelection(cm, vim) {
+ var anchor = vim.sel.anchor;
+ var head = vim.sel.head;
+ // To accommodate the effect of lastPastedText in the last selection
+ if (vim.lastPastedText) {
+ head = cm.posFromIndex(cm.indexFromPos(anchor) + vim.lastPastedText.length);
+ vim.lastPastedText = null;
+ }
+ vim.lastSelection = {'anchorMark': cm.setBookmark(anchor),
+ 'headMark': cm.setBookmark(head),
+ 'anchor': copyCursor(anchor),
+ 'head': copyCursor(head),
+ 'visualMode': vim.visualMode,
+ 'visualLine': vim.visualLine,
+ 'visualBlock': vim.visualBlock};
+ }
+ function expandSelection(cm, start, end) {
+ var sel = cm.state.vim.sel;
+ var head = sel.head;
+ var anchor = sel.anchor;
+ var tmp;
+ if (cursorIsBefore(end, start)) {
+ tmp = end;
+ end = start;
+ start = tmp;
+ }
+ if (cursorIsBefore(head, anchor)) {
+ head = cursorMin(start, head);
+ anchor = cursorMax(anchor, end);
+ } else {
+ anchor = cursorMin(start, anchor);
+ head = cursorMax(head, end);
+ head = offsetCursor(head, 0, -1);
+ if (head.ch == -1 && head.line != cm.firstLine()) {
+ head = Pos(head.line - 1, lineLength(cm, head.line - 1));
+ }
+ }
+ return [anchor, head];
+ }
+ /**
+ * Updates the CodeMirror selection to match the provided vim selection.
+ * If no arguments are given, it uses the current vim selection state.
+ */
+ function updateCmSelection(cm, sel, mode) {
+ var vim = cm.state.vim;
+ sel = sel || vim.sel;
+ var mode = mode ||
+ vim.visualLine ? 'line' : vim.visualBlock ? 'block' : 'char';
+ var cmSel = makeCmSelection(cm, sel, mode);
+ cm.setSelections(cmSel.ranges, cmSel.primary);
+ updateFakeCursor(cm);
+ }
+ function makeCmSelection(cm, sel, mode, exclusive) {
+ var head = copyCursor(sel.head);
+ var anchor = copyCursor(sel.anchor);
+ if (mode == 'char') {
+ var headOffset = !exclusive && !cursorIsBefore(sel.head, sel.anchor) ? 1 : 0;
+ var anchorOffset = cursorIsBefore(sel.head, sel.anchor) ? 1 : 0;
+ head = offsetCursor(sel.head, 0, headOffset);
+ anchor = offsetCursor(sel.anchor, 0, anchorOffset);
+ return {
+ ranges: [{anchor: anchor, head: head}],
+ primary: 0
+ };
+ } else if (mode == 'line') {
+ if (!cursorIsBefore(sel.head, sel.anchor)) {
+ anchor.ch = 0;
+
+ var lastLine = cm.lastLine();
+ if (head.line > lastLine) {
+ head.line = lastLine;
+ }
+ head.ch = lineLength(cm, head.line);
+ } else {
+ head.ch = 0;
+ anchor.ch = lineLength(cm, anchor.line);
+ }
+ return {
+ ranges: [{anchor: anchor, head: head}],
+ primary: 0
+ };
+ } else if (mode == 'block') {
+ var top = Math.min(anchor.line, head.line),
+ left = Math.min(anchor.ch, head.ch),
+ bottom = Math.max(anchor.line, head.line),
+ right = Math.max(anchor.ch, head.ch) + 1;
+ var height = bottom - top + 1;
+ var primary = head.line == top ? 0 : height - 1;
+ var ranges = [];
+ for (var i = 0; i < height; i++) {
+ ranges.push({
+ anchor: Pos(top + i, left),
+ head: Pos(top + i, right)
+ });
+ }
+ return {
+ ranges: ranges,
+ primary: primary
+ };
+ }
+ }
+ function getHead(cm) {
+ var cur = cm.getCursor('head');
+ if (cm.getSelection().length == 1) {
+ // Small corner case when only 1 character is selected. The "real"
+ // head is the left of head and anchor.
+ cur = cursorMin(cur, cm.getCursor('anchor'));
+ }
+ return cur;
+ }
+
+ /**
+ * If moveHead is set to false, the CodeMirror selection will not be
+ * touched. The caller assumes the responsibility of putting the cursor
+ * in the right place.
+ */
+ function exitVisualMode(cm, moveHead) {
+ var vim = cm.state.vim;
+ if (moveHead !== false) {
+ cm.setCursor(clipCursorToContent(cm, vim.sel.head));
+ }
+ updateLastSelection(cm, vim);
+ vim.visualMode = false;
+ vim.visualLine = false;
+ vim.visualBlock = false;
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
+ if (vim.fakeCursor) {
+ vim.fakeCursor.clear();
+ }
+ }
+
+ // Remove any trailing newlines from the selection. For
+ // example, with the caret at the start of the last word on the line,
+ // 'dw' should word, but not the newline, while 'w' should advance the
+ // caret to the first character of the next line.
+ function clipToLine(cm, curStart, curEnd) {
+ var selection = cm.getRange(curStart, curEnd);
+ // Only clip if the selection ends with trailing newline + whitespace
+ if (/\n\s*$/.test(selection)) {
+ var lines = selection.split('\n');
+ // We know this is all whitepsace.
+ lines.pop();
+
+ // Cases:
+ // 1. Last word is an empty line - do not clip the trailing '\n'
+ // 2. Last word is not an empty line - clip the trailing '\n'
+ var line;
+ // Find the line containing the last word, and clip all whitespace up
+ // to it.
+ for (var line = lines.pop(); lines.length > 0 && line && isWhiteSpaceString(line); line = lines.pop()) {
+ curEnd.line--;
+ curEnd.ch = 0;
+ }
+ // If the last word is not an empty line, clip an additional newline
+ if (line) {
+ curEnd.line--;
+ curEnd.ch = lineLength(cm, curEnd.line);
+ } else {
+ curEnd.ch = 0;
+ }
+ }
+ }
+
+ // Expand the selection to line ends.
+ function expandSelectionToLine(_cm, curStart, curEnd) {
+ curStart.ch = 0;
+ curEnd.ch = 0;
+ curEnd.line++;
+ }
+
+ function findFirstNonWhiteSpaceCharacter(text) {
+ if (!text) {
+ return 0;
+ }
+ var firstNonWS = text.search(/\S/);
+ return firstNonWS == -1 ? text.length : firstNonWS;
+ }
+
+ function expandWordUnderCursor(cm, inclusive, _forward, bigWord, noSymbol) {
+ var cur = getHead(cm);
+ var line = cm.getLine(cur.line);
+ var idx = cur.ch;
+
+ // Seek to first word or non-whitespace character, depending on if
+ // noSymbol is true.
+ var test = noSymbol ? wordCharTest[0] : bigWordCharTest [0];
+ while (!test(line.charAt(idx))) {
+ idx++;
+ if (idx >= line.length) { return null; }
+ }
+
+ if (bigWord) {
+ test = bigWordCharTest[0];
+ } else {
+ test = wordCharTest[0];
+ if (!test(line.charAt(idx))) {
+ test = wordCharTest[1];
+ }
+ }
+
+ 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, 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, start), end: Pos(cur.line, end) };
+ }
+
+ function recordJumpPosition(cm, oldCur, newCur) {
+ if (!cursorEqual(oldCur, newCur)) {
+ vimGlobalState.jumpList.add(cm, oldCur, newCur);
+ }
+ }
+
+ function recordLastCharacterSearch(increment, args) {
+ vimGlobalState.lastChararacterSearch.increment = increment;
+ vimGlobalState.lastChararacterSearch.forward = args.forward;
+ vimGlobalState.lastChararacterSearch.selectedCharacter = args.selectedCharacter;
+ }
+
+ var symbolToMode = {
+ '(': 'bracket', ')': 'bracket', '{': 'bracket', '}': 'bracket',
+ '[': 'section', ']': 'section',
+ '*': 'comment', '/': 'comment',
+ 'm': 'method', 'M': 'method',
+ '#': 'preprocess'
+ };
+ var findSymbolModes = {
+ bracket: {
+ isComplete: function(state) {
+ if (state.nextCh === state.symb) {
+ state.depth++;
+ if (state.depth >= 1)return true;
+ } else if (state.nextCh === state.reverseSymb) {
+ state.depth--;
+ }
+ return false;
+ }
+ },
+ section: {
+ init: function(state) {
+ state.curMoveThrough = true;
+ state.symb = (state.forward ? ']' : '[') === state.symb ? '{' : '}';
+ },
+ isComplete: function(state) {
+ return state.index === 0 && state.nextCh === state.symb;
+ }
+ },
+ comment: {
+ isComplete: function(state) {
+ var found = state.lastCh === '*' && state.nextCh === '/';
+ state.lastCh = state.nextCh;
+ return found;
+ }
+ },
+ // TODO: The original Vim implementation only operates on level 1 and 2.
+ // The current implementation doesn't check for code block level and
+ // therefore it operates on any levels.
+ method: {
+ init: function(state) {
+ state.symb = (state.symb === 'm' ? '{' : '}');
+ state.reverseSymb = state.symb === '{' ? '}' : '{';
+ },
+ isComplete: function(state) {
+ if (state.nextCh === state.symb)return true;
+ return false;
+ }
+ },
+ preprocess: {
+ init: function(state) {
+ state.index = 0;
+ },
+ isComplete: function(state) {
+ if (state.nextCh === '#') {
+ var token = state.lineText.match(/#(\w+)/)[1];
+ if (token === 'endif') {
+ if (state.forward && state.depth === 0) {
+ return true;
+ }
+ state.depth++;
+ } else if (token === 'if') {
+ if (!state.forward && state.depth === 0) {
+ return true;
+ }
+ state.depth--;
+ }
+ if (token === 'else' && state.depth === 0)return true;
+ }
+ return false;
+ }
+ }
+ };
+ function findSymbol(cm, repeat, forward, symb) {
+ var cur = copyCursor(cm.getCursor());
+ var increment = forward ? 1 : -1;
+ var endLine = forward ? cm.lineCount() : -1;
+ var curCh = cur.ch;
+ var line = cur.line;
+ var lineText = cm.getLine(line);
+ var state = {
+ lineText: lineText,
+ nextCh: lineText.charAt(curCh),
+ lastCh: null,
+ index: curCh,
+ symb: symb,
+ reverseSymb: (forward ? { ')': '(', '}': '{' } : { '(': ')', '{': '}' })[symb],
+ forward: forward,
+ depth: 0,
+ curMoveThrough: false
+ };
+ var mode = symbolToMode[symb];
+ if (!mode)return cur;
+ var init = findSymbolModes[mode].init;
+ var isComplete = findSymbolModes[mode].isComplete;
+ if (init) { init(state); }
+ while (line !== endLine && repeat) {
+ state.index += increment;
+ state.nextCh = state.lineText.charAt(state.index);
+ if (!state.nextCh) {
+ line += increment;
+ state.lineText = cm.getLine(line) || '';
+ if (increment > 0) {
+ state.index = 0;
+ } else {
+ var lineLen = state.lineText.length;
+ state.index = (lineLen > 0) ? (lineLen-1) : 0;
+ }
+ state.nextCh = state.lineText.charAt(state.index);
+ }
+ if (isComplete(state)) {
+ cur.line = line;
+ cur.ch = state.index;
+ repeat--;
+ }
+ }
+ if (state.nextCh || state.curMoveThrough) {
+ return Pos(line, state.index);
+ }
+ return cur;
+ }
+
+ /**
+ * Returns the boundaries of the next word. If the cursor in the middle of
+ * the word, then returns the boundaries of the current word, starting at
+ * the cursor. If the cursor is at the start/end of a word, and we are going
+ * forward/backward, respectively, find the boundaries of the next word.
+ *
+ * @param {CodeMirror} cm CodeMirror object.
+ * @param {Cursor} cur The cursor position.
+ * @param {boolean} forward True to search forward. False to search
+ * backward.
+ * @param {boolean} bigWord True if punctuation count as part of the word.
+ * False if only [a-zA-Z0-9] characters count as part of the word.
+ * @param {boolean} emptyLineIsWord True if empty lines should be treated
+ * as words.
+ * @return {Object{from:number, to:number, line: number}} The boundaries of
+ * the word, or null if there are no more words.
+ */
+ function findWord(cm, cur, forward, bigWord, emptyLineIsWord) {
+ var lineNum = cur.line;
+ var pos = cur.ch;
+ var line = cm.getLine(lineNum);
+ var dir = forward ? 1 : -1;
+ var charTests = bigWord ? bigWordCharTest: wordCharTest;
+
+ if (emptyLineIsWord && line == '') {
+ lineNum += dir;
+ line = cm.getLine(lineNum);
+ if (!isLine(cm, lineNum)) {
+ return null;
+ }
+ pos = (forward) ? 0 : line.length;
+ }
+
+ while (true) {
+ if (emptyLineIsWord && line == '') {
+ return { from: 0, to: 0, line: lineNum };
+ }
+ var stop = (dir > 0) ? line.length : -1;
+ var wordStart = stop, wordEnd = stop;
+ // Find bounds of next word.
+ while (pos != stop) {
+ var foundWord = false;
+ 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 && charTests[i](line.charAt(pos))) {
+ pos += dir;
+ }
+ wordEnd = pos;
+ foundWord = wordStart != wordEnd;
+ if (wordStart == cur.ch && lineNum == cur.line &&
+ wordEnd == wordStart + dir) {
+ // We started at the end of a word. Find the next one.
+ continue;
+ } else {
+ return {
+ from: Math.min(wordStart, wordEnd + 1),
+ to: Math.max(wordStart, wordEnd),
+ line: lineNum };
+ }
+ }
+ }
+ if (!foundWord) {
+ pos += dir;
+ }
+ }
+ // Advance to next/prev line.
+ lineNum += dir;
+ if (!isLine(cm, lineNum)) {
+ return null;
+ }
+ line = cm.getLine(lineNum);
+ pos = (dir > 0) ? 0 : line.length;
+ }
+ // Should never get here.
+ throw new Error('The impossible happened.');
+ }
+
+ /**
+ * @param {CodeMirror} cm CodeMirror object.
+ * @param {Pos} cur The position to start from.
+ * @param {int} repeat Number of words to move past.
+ * @param {boolean} forward True to search forward. False to search
+ * backward.
+ * @param {boolean} wordEnd True to move to end of word. False to move to
+ * beginning of word.
+ * @param {boolean} bigWord True if punctuation count as part of the word.
+ * False if only alphabet characters count as part of the word.
+ * @return {Cursor} The position the cursor should move to.
+ */
+ function moveToWord(cm, cur, repeat, forward, wordEnd, bigWord) {
+ var curStart = copyCursor(cur);
+ var words = [];
+ if (forward && !wordEnd || !forward && wordEnd) {
+ repeat++;
+ }
+ // For 'e', empty lines are not considered words, go figure.
+ var emptyLineIsWord = !(forward && wordEnd);
+ for (var i = 0; i < repeat; i++) {
+ var word = findWord(cm, cur, forward, bigWord, emptyLineIsWord);
+ if (!word) {
+ var eodCh = lineLength(cm, cm.lastLine());
+ words.push(forward
+ ? {line: cm.lastLine(), from: eodCh, to: eodCh}
+ : {line: 0, from: 0, to: 0});
+ break;
+ }
+ words.push(word);
+ cur = Pos(word.line, forward ? (word.to - 1) : word.from);
+ }
+ var shortCircuit = words.length != repeat;
+ var firstWord = words[0];
+ var lastWord = words.pop();
+ if (forward && !wordEnd) {
+ // w
+ if (!shortCircuit && (firstWord.from != curStart.ch || firstWord.line != curStart.line)) {
+ // We did not start in the middle of a word. Discard the extra word at the end.
+ lastWord = words.pop();
+ }
+ return Pos(lastWord.line, lastWord.from);
+ } else if (forward && wordEnd) {
+ return Pos(lastWord.line, lastWord.to - 1);
+ } else if (!forward && wordEnd) {
+ // ge
+ if (!shortCircuit && (firstWord.to != curStart.ch || firstWord.line != curStart.line)) {
+ // We did not start in the middle of a word. Discard the extra word at the end.
+ lastWord = words.pop();
+ }
+ return Pos(lastWord.line, lastWord.to);
+ } else {
+ // b
+ return Pos(lastWord.line, lastWord.from);
+ }
+ }
+
+ function moveToCharacter(cm, repeat, forward, character) {
+ var cur = cm.getCursor();
+ var start = cur.ch;
+ var idx;
+ for (var i = 0; i < repeat; i ++) {
+ var line = cm.getLine(cur.line);
+ idx = charIdxInLine(start, line, character, forward, true);
+ if (idx == -1) {
+ return null;
+ }
+ start = idx;
+ }
+ return Pos(cm.getCursor().line, idx);
+ }
+
+ function moveToColumn(cm, repeat) {
+ // repeat is always >= 1, so repeat - 1 always corresponds
+ // to the column we want to go to.
+ var line = cm.getCursor().line;
+ return clipCursorToContent(cm, Pos(line, repeat - 1));
+ }
+
+ function updateMark(cm, vim, markName, pos) {
+ if (!inArray(markName, validMarks)) {
+ return;
+ }
+ if (vim.marks[markName]) {
+ vim.marks[markName].clear();
+ }
+ vim.marks[markName] = cm.setBookmark(pos);
+ }
+
+ function charIdxInLine(start, line, character, forward, includeChar) {
+ // Search for char in line.
+ // motion_options: {forward, includeChar}
+ // If includeChar = true, include it too.
+ // If forward = true, search forward, else search backwards.
+ // If char is not found on this line, do nothing
+ var idx;
+ if (forward) {
+ idx = line.indexOf(character, start + 1);
+ if (idx != -1 && !includeChar) {
+ idx -= 1;
+ }
+ } else {
+ idx = line.lastIndexOf(character, start - 1);
+ if (idx != -1 && !includeChar) {
+ idx += 1;
+ }
+ }
+ return idx;
+ }
+
+ function findParagraph(cm, head, repeat, dir, inclusive) {
+ var line = head.line;
+ var min = cm.firstLine();
+ var max = cm.lastLine();
+ var start, end, i = line;
+ function isEmpty(i) { return !cm.getLine(i); }
+ function isBoundary(i, dir, any) {
+ if (any) { return isEmpty(i) != isEmpty(i + dir); }
+ return !isEmpty(i) && isEmpty(i + dir);
+ }
+ if (dir) {
+ while (min <= i && i <= max && repeat > 0) {
+ if (isBoundary(i, dir)) { repeat--; }
+ i += dir;
+ }
+ return new Pos(i, 0);
+ }
+
+ var vim = cm.state.vim;
+ if (vim.visualLine && isBoundary(line, 1, true)) {
+ var anchor = vim.sel.anchor;
+ if (isBoundary(anchor.line, -1, true)) {
+ if (!inclusive || anchor.line != line) {
+ line += 1;
+ }
+ }
+ }
+ var startState = isEmpty(line);
+ for (i = line; i <= max && repeat; i++) {
+ if (isBoundary(i, 1, true)) {
+ if (!inclusive || isEmpty(i) != startState) {
+ repeat--;
+ }
+ }
+ }
+ end = new Pos(i, 0);
+ // select boundary before paragraph for the last one
+ if (i > max && !startState) { startState = true; }
+ else { inclusive = false; }
+ for (i = line; i > min; i--) {
+ if (!inclusive || isEmpty(i) == startState || i == line) {
+ if (isBoundary(i, -1, true)) { break; }
+ }
+ }
+ start = new Pos(i, 0);
+ return { start: start, end: end };
+ }
+
+ // TODO: perhaps this finagling of start and end positions belonds
+ // in codmirror/replaceRange?
+ function selectCompanionObject(cm, head, symb, inclusive) {
+ var cur = head, start, end;
+
+ var bracketRegexp = ({
+ '(': /[()]/, ')': /[()]/,
+ '[': /[[\]]/, ']': /[[\]]/,
+ '{': /[{}]/, '}': /[{}]/})[symb];
+ var openSym = ({
+ '(': '(', ')': '(',
+ '[': '[', ']': '[',
+ '{': '{', '}': '{'})[symb];
+ var curChar = cm.getLine(cur.line).charAt(cur.ch);
+ // Due to the behavior of scanForBracket, we need to add an offset if the
+ // cursor is on a matching open bracket.
+ var offset = curChar === openSym ? 1 : 0;
+
+ start = cm.scanForBracket(Pos(cur.line, cur.ch + offset), -1, null, {'bracketRegex': bracketRegexp});
+ end = cm.scanForBracket(Pos(cur.line, cur.ch + offset), 1, null, {'bracketRegex': bracketRegexp});
+
+ if (!start || !end) {
+ return { start: cur, end: cur };
+ }
+
+ start = start.pos;
+ end = end.pos;
+
+ if ((start.line == end.line && start.ch > end.ch)
+ || (start.line > end.line)) {
+ var tmp = start;
+ start = end;
+ end = tmp;
+ }
+
+ if (inclusive) {
+ end.ch += 1;
+ } else {
+ start.ch += 1;
+ }
+
+ return { start: start, end: end };
+ }
+
+ // Takes in a symbol and a cursor and tries to simulate text objects that
+ // have identical opening and closing symbols
+ // TODO support across multiple lines
+ function findBeginningAndEnd(cm, head, symb, inclusive) {
+ var cur = copyCursor(head);
+ var line = cm.getLine(cur.line);
+ var chars = line.split('');
+ var start, end, i, len;
+ var firstIndex = chars.indexOf(symb);
+
+ // the decision tree is to always look backwards for the beginning first,
+ // but if the cursor is in front of the first instance of the symb,
+ // then move the cursor forward
+ if (cur.ch < firstIndex) {
+ cur.ch = firstIndex;
+ // Why is this line even here???
+ // cm.setCursor(cur.line, firstIndex+1);
+ }
+ // otherwise if the cursor is currently on the closing symbol
+ else if (firstIndex < cur.ch && chars[cur.ch] == symb) {
+ end = cur.ch; // assign end to the current cursor
+ --cur.ch; // make sure to look backwards
+ }
+
+ // if we're currently on the symbol, we've got a start
+ if (chars[cur.ch] == symb && !end) {
+ start = cur.ch + 1; // assign start to ahead of the cursor
+ } else {
+ // go backwards to find the start
+ for (i = cur.ch; i > -1 && !start; i--) {
+ if (chars[i] == symb) {
+ start = i + 1;
+ }
+ }
+ }
+
+ // look forwards for the end symbol
+ if (start && !end) {
+ for (i = start, len = chars.length; i < len && !end; i++) {
+ if (chars[i] == symb) {
+ end = i;
+ }
+ }
+ }
+
+ // nothing found
+ if (!start || !end) {
+ return { start: cur, end: cur };
+ }
+
+ // include the symbols
+ if (inclusive) {
+ --start; ++end;
+ }
+
+ return {
+ start: Pos(cur.line, start),
+ end: Pos(cur.line, end)
+ };
+ }
+
+ // Search functions
+ defineOption('pcre', true, 'boolean');
+ function SearchState() {}
+ SearchState.prototype = {
+ getQuery: function() {
+ return vimGlobalState.query;
+ },
+ setQuery: function(query) {
+ vimGlobalState.query = query;
+ },
+ getOverlay: function() {
+ return this.searchOverlay;
+ },
+ setOverlay: function(overlay) {
+ this.searchOverlay = overlay;
+ },
+ isReversed: function() {
+ return vimGlobalState.isReversed;
+ },
+ setReversed: function(reversed) {
+ vimGlobalState.isReversed = reversed;
+ },
+ getScrollbarAnnotate: function() {
+ return this.annotate;
+ },
+ setScrollbarAnnotate: function(annotate) {
+ this.annotate = annotate;
+ }
+ };
+ function getSearchState(cm) {
+ var vim = cm.state.vim;
+ return vim.searchState_ || (vim.searchState_ = new SearchState());
+ }
+ 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,
+ selectValueOnOpen: false});
+ }
+ else {
+ onClose(prompt(shortText, ''));
+ }
+ }
+ function splitBySlash(argString) {
+ var slashes = findUnescapedSlashes(argString) || [];
+ if (!slashes.length) return [];
+ var tokens = [];
+ // in case of strings like foo/bar
+ if (slashes[0] !== 0) return;
+ for (var i = 0; i < slashes.length; i++) {
+ if (typeof slashes[i] == 'number')
+ tokens.push(argString.substring(slashes[i] + 1, slashes[i+1]));
+ }
+ return tokens;
+ }
+
+ function findUnescapedSlashes(str) {
+ var escapeNextChar = false;
+ var slashes = [];
+ for (var i = 0; i < str.length; i++) {
+ var c = str.charAt(i);
+ if (!escapeNextChar && c == '/') {
+ slashes.push(i);
+ }
+ escapeNextChar = !escapeNextChar && (c == '\\');
+ }
+ return slashes;
+ }
+
+ // Translates a search string from ex (vim) syntax into javascript form.
+ function translateRegex(str) {
+ // When these match, add a '\' if unescaped or remove one if escaped.
+ var specials = '|(){';
+ // Remove, but never add, a '\' for these.
+ var unescape = '}';
+ var escapeNextChar = false;
+ var out = [];
+ for (var i = -1; i < str.length; i++) {
+ var c = str.charAt(i) || '';
+ var n = str.charAt(i+1) || '';
+ var specialComesNext = (n && specials.indexOf(n) != -1);
+ if (escapeNextChar) {
+ if (c !== '\\' || !specialComesNext) {
+ out.push(c);
+ }
+ escapeNextChar = false;
+ } else {
+ if (c === '\\') {
+ escapeNextChar = true;
+ // Treat the unescape list as special for removing, but not adding '\'.
+ if (n && unescape.indexOf(n) != -1) {
+ specialComesNext = true;
+ }
+ // Not passing this test means removing a '\'.
+ if (!specialComesNext || n === '\\') {
+ out.push(c);
+ }
+ } else {
+ out.push(c);
+ if (specialComesNext && n !== '\\') {
+ out.push('\\');
+ }
+ }
+ }
+ }
+ return out.join('');
+ }
+
+ // Translates the replace part of a search and replace from ex (vim) syntax into
+ // javascript form. Similar to translateRegex, but additionally fixes back references
+ // (translates '\[0..9]' to '$[0..9]') and follows different rules for escaping '$'.
+ var charUnescapes = {'\\n': '\n', '\\r': '\r', '\\t': '\t'};
+ function translateRegexReplace(str) {
+ var escapeNextChar = false;
+ var out = [];
+ for (var i = -1; i < str.length; i++) {
+ var c = str.charAt(i) || '';
+ var n = str.charAt(i+1) || '';
+ if (charUnescapes[c + n]) {
+ out.push(charUnescapes[c+n]);
+ i++;
+ } else if (escapeNextChar) {
+ // At any point in the loop, escapeNextChar is true if the previous
+ // character was a '\' and was not escaped.
+ out.push(c);
+ escapeNextChar = false;
+ } else {
+ if (c === '\\') {
+ escapeNextChar = true;
+ if ((isNumber(n) || n === '$')) {
+ out.push('$');
+ } else if (n !== '/' && n !== '\\') {
+ out.push('\\');
+ }
+ } else {
+ if (c === '$') {
+ out.push('$');
+ }
+ out.push(c);
+ if (n === '/') {
+ out.push('\\');
+ }
+ }
+ }
+ }
+ return out.join('');
+ }
+
+ // Unescape \ and / in the replace part, for PCRE mode.
+ var unescapes = {'\\/': '/', '\\\\': '\\', '\\n': '\n', '\\r': '\r', '\\t': '\t'};
+ function unescapeRegexReplace(str) {
+ var stream = new CodeMirror.StringStream(str);
+ var output = [];
+ while (!stream.eol()) {
+ // Search for \.
+ while (stream.peek() && stream.peek() != '\\') {
+ output.push(stream.next());
+ }
+ var matched = false;
+ for (var matcher in unescapes) {
+ if (stream.match(matcher, true)) {
+ matched = true;
+ output.push(unescapes[matcher]);
+ break;
+ }
+ }
+ if (!matched) {
+ // Don't change anything
+ output.push(stream.next());
+ }
+ }
+ return output.join('');
+ }
+
+ /**
+ * Extract the regular expression from the query and return a Regexp object.
+ * Returns null if the query is blank.
+ * If ignoreCase is passed in, the Regexp object will have the 'i' flag set.
+ * If smartCase is passed in, and the query contains upper case letters,
+ * then ignoreCase is overridden, and the 'i' flag will not be set.
+ * If the query contains the /i in the flag part of the regular expression,
+ * then both ignoreCase and smartCase are ignored, and 'i' will be passed
+ * through to the Regex object.
+ */
+ function parseQuery(query, ignoreCase, smartCase) {
+ // First update the last search register
+ var lastSearchRegister = vimGlobalState.registerController.getRegister('/');
+ lastSearchRegister.setText(query);
+ // Check if the query is already a regex.
+ if (query instanceof RegExp) { return query; }
+ // First try to extract regex + flags from the input. If no flags found,
+ // extract just the regex. IE does not accept flags directly defined in
+ // the regex string in the form /regex/flags
+ var slashes = findUnescapedSlashes(query);
+ var regexPart;
+ var forceIgnoreCase;
+ if (!slashes.length) {
+ // Query looks like 'regexp'
+ regexPart = query;
+ } else {
+ // Query looks like 'regexp/...'
+ regexPart = query.substring(0, slashes[0]);
+ var flagsPart = query.substring(slashes[0]);
+ forceIgnoreCase = (flagsPart.indexOf('i') != -1);
+ }
+ if (!regexPart) {
+ return null;
+ }
+ if (!getOption('pcre')) {
+ regexPart = translateRegex(regexPart);
+ }
+ if (smartCase) {
+ ignoreCase = (/^[^A-Z]*$/).test(regexPart);
+ }
+ var regexp = new RegExp(regexPart,
+ (ignoreCase || forceIgnoreCase) ? 'i' : undefined);
+ return regexp;
+ }
+ function showConfirm(cm, text) {
+ if (cm.openNotification) {
+ cm.openNotification('' + text + '',
+ {bottom: true, duration: 5000});
+ } else {
+ alert(text);
+ }
+ }
+ function makePrompt(prefix, desc) {
+ var raw = '';
+ if (prefix) {
+ raw += '' + prefix + '';
+ }
+ raw += ' ' +
+ '';
+ if (desc) {
+ raw += '';
+ raw += desc;
+ raw += '';
+ }
+ return raw;
+ }
+ var searchPromptDesc = '(Javascript regexp)';
+ function showPrompt(cm, options) {
+ var shortText = (options.prefix || '') + ' ' + (options.desc || '');
+ var prompt = makePrompt(options.prefix, options.desc);
+ dialog(cm, prompt, shortText, options.onClose, options);
+ }
+ function regexEqual(r1, r2) {
+ if (r1 instanceof RegExp && r2 instanceof RegExp) {
+ var props = ['global', 'multiline', 'ignoreCase', 'source'];
+ for (var i = 0; i < props.length; i++) {
+ var prop = props[i];
+ if (r1[prop] !== r2[prop]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+ // Returns true if the query is valid.
+ function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) {
+ if (!rawQuery) {
+ return;
+ }
+ var state = getSearchState(cm);
+ var query = parseQuery(rawQuery, !!ignoreCase, !!smartCase);
+ if (!query) {
+ return;
+ }
+ highlightSearchMatches(cm, query);
+ if (regexEqual(query, state.getQuery())) {
+ return query;
+ }
+ state.setQuery(query);
+ return query;
+ }
+ function searchOverlay(query) {
+ if (query.source.charAt(0) == '^') {
+ var matchSol = true;
+ }
+ return {
+ token: function(stream) {
+ if (matchSol && !stream.sol()) {
+ stream.skipToEnd();
+ return;
+ }
+ var match = stream.match(query, false);
+ if (match) {
+ if (match[0].length == 0) {
+ // Matched empty string, skip to next.
+ stream.next();
+ return 'searching';
+ }
+ if (!stream.sol()) {
+ // Backtrack 1 to match \b
+ stream.backUp(1);
+ if (!query.exec(stream.next() + match[0])) {
+ stream.next();
+ return null;
+ }
+ }
+ stream.match(query);
+ return 'searching';
+ }
+ while (!stream.eol()) {
+ stream.next();
+ if (stream.match(query, false)) break;
+ }
+ },
+ query: query
+ };
+ }
+ function highlightSearchMatches(cm, query) {
+ var searchState = getSearchState(cm);
+ var overlay = searchState.getOverlay();
+ if (!overlay || query != overlay.query) {
+ if (overlay) {
+ cm.removeOverlay(overlay);
+ }
+ overlay = searchOverlay(query);
+ cm.addOverlay(overlay);
+ if (cm.showMatchesOnScrollbar) {
+ if (searchState.getScrollbarAnnotate()) {
+ searchState.getScrollbarAnnotate().clear();
+ }
+ searchState.setScrollbarAnnotate(cm.showMatchesOnScrollbar(query));
+ }
+ searchState.setOverlay(overlay);
+ }
+ }
+ function findNext(cm, prev, query, repeat) {
+ if (repeat === undefined) { repeat = 1; }
+ return cm.operation(function() {
+ var pos = cm.getCursor();
+ var cursor = cm.getSearchCursor(query, pos);
+ for (var i = 0; i < repeat; i++) {
+ var found = cursor.find(prev);
+ if (i == 0 && found && cursorEqual(cursor.from(), pos)) { found = cursor.find(prev); }
+ if (!found) {
+ // SearchCursor may have returned null because it hit EOF, wrap
+ // around and try again.
+ cursor = cm.getSearchCursor(query,
+ (prev) ? Pos(cm.lastLine()) : Pos(cm.firstLine(), 0) );
+ if (!cursor.find(prev)) {
+ return;
+ }
+ }
+ }
+ return cursor.from();
+ });
+ }
+ function clearSearchHighlight(cm) {
+ var state = getSearchState(cm);
+ cm.removeOverlay(getSearchState(cm).getOverlay());
+ state.setOverlay(null);
+ if (state.getScrollbarAnnotate()) {
+ state.getScrollbarAnnotate().clear();
+ state.setScrollbarAnnotate(null);
+ }
+ }
+ /**
+ * Check if pos is in the specified range, INCLUSIVE.
+ * Range can be specified with 1 or 2 arguments.
+ * If the first range argument is an array, treat it as an array of line
+ * numbers. Match pos against any of the lines.
+ * If the first range argument is a number,
+ * if there is only 1 range argument, check if pos has the same line
+ * number
+ * if there are 2 range arguments, then check if pos is in between the two
+ * range arguments.
+ */
+ function isInRange(pos, start, end) {
+ if (typeof pos != 'number') {
+ // Assume it is a cursor position. Get the line number.
+ pos = pos.line;
+ }
+ if (start instanceof Array) {
+ return inArray(pos, start);
+ } else {
+ if (end) {
+ return (pos >= start && pos <= end);
+ } else {
+ return pos == start;
+ }
+ }
+ }
+ function getUserVisibleLines(cm) {
+ var scrollInfo = cm.getScrollInfo();
+ var occludeToleranceTop = 6;
+ var occludeToleranceBottom = 10;
+ var from = cm.coordsChar({left:0, top: occludeToleranceTop + scrollInfo.top}, 'local');
+ var bottomY = scrollInfo.clientHeight - occludeToleranceBottom + scrollInfo.top;
+ var to = cm.coordsChar({left:0, top: bottomY}, 'local');
+ return {top: from.line, bottom: to.line};
+ }
+
+ 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();
+ if (vim.visualMode) {
+ exitVisualMode(cm);
+ }
+ var inputStream = new CodeMirror.StringStream(input);
+ // update ": with the latest command whether valid or invalid
+ commandHistoryRegister.setText(input);
+ var params = opt_params || {};
+ params.input = input;
+ try {
+ this.parseInput_(cm, inputStream, params);
+ } catch(e) {
+ showConfirm(cm, e);
+ throw e;
+ }
+ var command;
+ var commandName;
+ if (!params.commandName) {
+ // If only a line range is defined, move to the line.
+ if (params.line !== undefined) {
+ commandName = 'move';
+ }
+ } else {
+ command = this.matchCommand_(params.commandName);
+ if (command) {
+ commandName = command.name;
+ if (command.excludeFromCommandHistory) {
+ commandHistoryRegister.setText(previousCommand);
+ }
+ this.parseCommandArgs_(inputStream, params, command);
+ if (command.type == 'exToKey') {
+ // Handle Ex to Key mapping.
+ for (var i = 0; i < command.toKeys.length; i++) {
+ CodeMirror.Vim.handleKey(cm, command.toKeys[i], 'mapping');
+ }
+ return;
+ } else if (command.type == 'exToEx') {
+ // Handle Ex to Ex mapping.
+ this.processCommand(cm, command.toInput);
+ return;
+ }
+ }
+ }
+ if (!commandName) {
+ showConfirm(cm, 'Not an editor command ":' + input + '"');
+ return;
+ }
+ try {
+ exCommands[commandName](cm, params);
+ // Possibly asynchronous commands (e.g. substitute, which might have a
+ // user confirmation), are responsible for calling the callback when
+ // done. All others have it taken care of for them here.
+ if ((!command || !command.possiblyAsync) && params.callback) {
+ params.callback();
+ }
+ } catch(e) {
+ showConfirm(cm, e);
+ throw e;
+ }
+ },
+ parseInput_: function(cm, inputStream, result) {
+ inputStream.eatWhile(':');
+ // Parse range.
+ if (inputStream.eat('%')) {
+ result.line = cm.firstLine();
+ result.lineEnd = cm.lastLine();
+ } else {
+ result.line = this.parseLineSpec_(cm, inputStream);
+ if (result.line !== undefined && inputStream.eat(',')) {
+ result.lineEnd = this.parseLineSpec_(cm, inputStream);
+ }
+ }
+
+ // Parse command name.
+ var commandMatch = inputStream.match(/^(\w+)/);
+ if (commandMatch) {
+ result.commandName = commandMatch[1];
+ } else {
+ result.commandName = inputStream.match(/.*/)[0];
+ }
+
+ return result;
+ },
+ parseLineSpec_: function(cm, inputStream) {
+ var numberMatch = inputStream.match(/^(\d+)/);
+ if (numberMatch) {
+ return parseInt(numberMatch[1], 10) - 1;
+ }
+ switch (inputStream.next()) {
+ case '.':
+ return cm.getCursor().line;
+ case '$':
+ return cm.lastLine();
+ case '\'':
+ var mark = cm.state.vim.marks[inputStream.next()];
+ if (mark && mark.find()) {
+ return mark.find().line;
+ }
+ throw new Error('Mark not set');
+ default:
+ inputStream.backUp(1);
+ return undefined;
+ }
+ },
+ parseCommandArgs_: function(inputStream, params, command) {
+ if (inputStream.eol()) {
+ return;
+ }
+ params.argString = inputStream.match(/.*/)[0];
+ // Parse command-line arguments
+ var delim = command.argDelimiter || /\s+/;
+ var args = trim(params.argString).split(delim);
+ if (args.length && args[0]) {
+ params.args = args;
+ }
+ },
+ matchCommand_: function(commandName) {
+ // Return the command in the command map that matches the shortest
+ // prefix of the passed in command name. The match is guaranteed to be
+ // unambiguous if the defaultExCommandMap's shortNames are set up
+ // correctly. (see @code{defaultExCommandMap}).
+ for (var i = commandName.length; i > 0; i--) {
+ var prefix = commandName.substring(0, i);
+ if (this.commandMap_[prefix]) {
+ var command = this.commandMap_[prefix];
+ if (command.name.indexOf(commandName) === 0) {
+ return command;
+ }
+ }
+ }
+ return null;
+ },
+ buildCommandMap_: function() {
+ this.commandMap_ = {};
+ for (var i = 0; i < defaultExCommandMap.length; i++) {
+ var command = defaultExCommandMap[i];
+ var key = command.shortName || command.name;
+ this.commandMap_[key] = command;
+ }
+ },
+ map: function(lhs, rhs, ctx) {
+ if (lhs != ':' && lhs.charAt(0) == ':') {
+ if (ctx) { throw Error('Mode not supported for ex mappings'); }
+ var commandName = lhs.substring(1);
+ if (rhs != ':' && rhs.charAt(0) == ':') {
+ // Ex to Ex mapping
+ this.commandMap_[commandName] = {
+ name: commandName,
+ type: 'exToEx',
+ toInput: rhs.substring(1),
+ user: true
+ };
+ } else {
+ // Ex to key mapping
+ this.commandMap_[commandName] = {
+ name: commandName,
+ type: 'exToKey',
+ toKeys: rhs,
+ user: true
+ };
+ }
+ } else {
+ if (rhs != ':' && rhs.charAt(0) == ':') {
+ // Key to Ex mapping.
+ var mapping = {
+ keys: lhs,
+ type: 'keyToEx',
+ exArgs: { input: rhs.substring(1) },
+ user: true};
+ if (ctx) { mapping.context = ctx; }
+ defaultKeymap.unshift(mapping);
+ } else {
+ // Key to key mapping
+ var mapping = {
+ keys: lhs,
+ type: 'keyToKey',
+ toKeys: rhs,
+ user: true
+ };
+ if (ctx) { mapping.context = ctx; }
+ defaultKeymap.unshift(mapping);
+ }
+ }
+ },
+ unmap: function(lhs, ctx) {
+ if (lhs != ':' && lhs.charAt(0) == ':') {
+ // Ex to Ex or Ex to key mapping
+ if (ctx) { throw Error('Mode not supported for ex mappings'); }
+ var commandName = lhs.substring(1);
+ if (this.commandMap_[commandName] && this.commandMap_[commandName].user) {
+ delete this.commandMap_[commandName];
+ return;
+ }
+ } else {
+ // Key to Ex or key to key mapping
+ var keys = lhs;
+ for (var i = 0; i < defaultKeymap.length; i++) {
+ if (keys == defaultKeymap[i].keys
+ && defaultKeymap[i].context === ctx
+ && defaultKeymap[i].user) {
+ defaultKeymap.splice(i, 1);
+ return;
+ }
+ }
+ }
+ throw Error('No such mapping.');
+ }
+ };
+
+ 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) {
+ if (cm) {
+ showConfirm(cm, 'Invalid mapping: ' + params.input);
+ }
+ return;
+ }
+ exCommandDispatcher.map(mapArgs[0], mapArgs[1], ctx);
+ },
+ imap: function(cm, params) { this.map(cm, params, 'insert'); },
+ nmap: function(cm, params) { this.map(cm, params, 'normal'); },
+ vmap: function(cm, params) { this.map(cm, params, 'visual'); },
+ unmap: function(cm, params, ctx) {
+ var mapArgs = params.args;
+ if (!mapArgs || mapArgs.length < 1) {
+ if (cm) {
+ showConfirm(cm, 'No such mapping: ' + params.input);
+ }
+ return;
+ }
+ exCommandDispatcher.unmap(mapArgs[0], ctx);
+ },
+ move: function(cm, params) {
+ commandDispatcher.processCommand(cm, cm.state.vim, {
+ type: 'motion',
+ motion: 'moveToLineOrEdgeOfDocument',
+ motionArgs: { forward: false, explicitRepeat: true,
+ linewise: true },
+ repeatOverride: params.line+1});
+ },
+ 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);
+ }
+ return;
+ }
+ var expr = setArgs[0].split('=');
+ var optionName = expr[0];
+ var value = expr[1];
+ var forceGet = false;
+
+ if (optionName.charAt(optionName.length - 1) == '?') {
+ // If post-fixed with ?, then the set is actually a get.
+ if (value) { throw Error('Trailing characters: ' + params.argString); }
+ optionName = optionName.substring(0, optionName.length - 1);
+ forceGet = true;
+ }
+ if (value === undefined && optionName.substring(0, 2) == 'no') {
+ // To set boolean options to false, the option name is prefixed with
+ // 'no'.
+ 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 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, cm, setCfg);
+ }
+ },
+ 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----------
';
+ if (!regArgs) {
+ for (var registerName in registers) {
+ var text = registers[registerName].toString();
+ if (text.length) {
+ regInfo += '"' + registerName + ' ' + text + ' ';
+ }
+ }
+ } else {
+ var registerName;
+ regArgs = regArgs.join('');
+ for (var i = 0; i < regArgs.length; i++) {
+ registerName = regArgs.charAt(i);
+ if (!vimGlobalState.registerController.isValidRegister(registerName)) {
+ continue;
+ }
+ var register = registers[registerName] || new Register();
+ regInfo += '"' + registerName + ' ' + register.toString() + ' ';
+ }
+ }
+ showConfirm(cm, regInfo);
+ },
+ sort: function(cm, params) {
+ var reverse, ignoreCase, unique, number;
+ function parseArgs() {
+ if (params.argString) {
+ var args = new CodeMirror.StringStream(params.argString);
+ if (args.eat('!')) { reverse = true; }
+ if (args.eol()) { return; }
+ if (!args.eatSpace()) { return 'Invalid arguments'; }
+ var opts = args.match(/[a-z]+/);
+ if (opts) {
+ opts = opts[0];
+ ignoreCase = opts.indexOf('i') != -1;
+ unique = opts.indexOf('u') != -1;
+ var decimal = opts.indexOf('d') != -1 && 1;
+ var hex = opts.indexOf('x') != -1 && 1;
+ var octal = opts.indexOf('o') != -1 && 1;
+ if (decimal + hex + octal > 1) { return 'Invalid arguments'; }
+ number = decimal && 'decimal' || hex && 'hex' || octal && 'octal';
+ }
+ if (args.match(/\/.*\//)) { return 'patterns not supported'; }
+ }
+ }
+ var err = parseArgs();
+ if (err) {
+ showConfirm(cm, err + ': ' + params.argString);
+ return;
+ }
+ var lineStart = params.line || cm.firstLine();
+ var lineEnd = params.lineEnd || params.line || cm.lastLine();
+ if (lineStart == lineEnd) { return; }
+ var curStart = Pos(lineStart, 0);
+ var curEnd = Pos(lineEnd, lineLength(cm, lineEnd));
+ var text = cm.getRange(curStart, curEnd).split('\n');
+ var numberRegex = (number == 'decimal') ? /(-?)([\d]+)/ :
+ (number == 'hex') ? /(-?)(?:0x)?([0-9a-f]+)/i :
+ (number == 'octal') ? /([0-7]+)/ : null;
+ var radix = (number == 'decimal') ? 10 : (number == 'hex') ? 16 : (number == 'octal') ? 8 : null;
+ var numPart = [], textPart = [];
+ if (number) {
+ for (var i = 0; i < text.length; i++) {
+ if (numberRegex.exec(text[i])) {
+ numPart.push(text[i]);
+ } else {
+ textPart.push(text[i]);
+ }
+ }
+ } else {
+ textPart = text;
+ }
+ function compareFn(a, b) {
+ if (reverse) { var tmp; tmp = a; a = b; b = tmp; }
+ if (ignoreCase) { a = a.toLowerCase(); b = b.toLowerCase(); }
+ var anum = number && numberRegex.exec(a);
+ var bnum = number && numberRegex.exec(b);
+ if (!anum) { return a < b ? -1 : 1; }
+ anum = parseInt((anum[1] + anum[2]).toLowerCase(), radix);
+ bnum = parseInt((bnum[1] + bnum[2]).toLowerCase(), radix);
+ return anum - bnum;
+ }
+ numPart.sort(compareFn);
+ textPart.sort(compareFn);
+ text = (!reverse) ? textPart.concat(numPart) : numPart.concat(textPart);
+ if (unique) { // Remove duplicate lines
+ var textOld = text;
+ var lastLine;
+ text = [];
+ for (var i = 0; i < textOld.length; i++) {
+ if (textOld[i] != lastLine) {
+ text.push(textOld[i]);
+ }
+ lastLine = textOld[i];
+ }
+ }
+ cm.replaceRange(text.join('\n'), curStart, curEnd);
+ },
+ global: function(cm, params) {
+ // a global command is of the form
+ // :[range]g/pattern/[cmd]
+ // argString holds the string /pattern/[cmd]
+ var argString = params.argString;
+ if (!argString) {
+ showConfirm(cm, 'Regular Expression missing from global');
+ return;
+ }
+ // range is specified here
+ var lineStart = (params.line !== undefined) ? params.line : cm.firstLine();
+ var lineEnd = params.lineEnd || params.line || cm.lastLine();
+ // get the tokens from argString
+ var tokens = splitBySlash(argString);
+ var regexPart = argString, cmd;
+ if (tokens.length) {
+ regexPart = tokens[0];
+ cmd = tokens.slice(1, tokens.length).join('/');
+ }
+ if (regexPart) {
+ // If regex part is empty, then use the previous query. Otherwise
+ // use the regex part as the new query.
+ try {
+ updateSearchQuery(cm, regexPart, true /** ignoreCase */,
+ true /** smartCase */);
+ } catch (e) {
+ showConfirm(cm, 'Invalid regex: ' + regexPart);
+ return;
+ }
+ }
+ // now that we have the regexPart, search for regex matches in the
+ // specified range of lines
+ var query = getSearchState(cm).getQuery();
+ var matchedLines = [], content = '';
+ for (var i = lineStart; i <= lineEnd; i++) {
+ var matched = query.test(cm.getLine(i));
+ if (matched) {
+ matchedLines.push(i+1);
+ content+= cm.getLine(i) + ' ';
+ }
+ }
+ // if there is no [cmd], just display the list of matched lines
+ if (!cmd) {
+ showConfirm(cm, content);
+ return;
+ }
+ var index = 0;
+ var nextCommand = function() {
+ if (index < matchedLines.length) {
+ var command = matchedLines[index] + cmd;
+ exCommandDispatcher.processCommand(cm, command, {
+ callback: nextCommand
+ });
+ }
+ index++;
+ };
+ nextCommand();
+ },
+ substitute: function(cm, params) {
+ if (!cm.getSearchCursor) {
+ throw new Error('Search feature not available. Requires searchcursor.js or ' +
+ 'any other getSearchCursor implementation.');
+ }
+ var argString = params.argString;
+ var tokens = argString ? splitBySlash(argString) : [];
+ var regexPart, replacePart = '', trailing, flagsPart, count;
+ var confirm = false; // Whether to confirm each replace.
+ var global = false; // True to replace all instances on a line, false to replace only 1.
+ if (tokens.length) {
+ regexPart = tokens[0];
+ replacePart = tokens[1];
+ if (replacePart !== undefined) {
+ if (getOption('pcre')) {
+ replacePart = unescapeRegexReplace(replacePart);
+ } else {
+ replacePart = translateRegexReplace(replacePart);
+ }
+ vimGlobalState.lastSubstituteReplacePart = replacePart;
+ }
+ trailing = tokens[2] ? tokens[2].split(' ') : [];
+ } else {
+ // either the argString is empty or its of the form ' hello/world'
+ // actually splitBySlash returns a list of tokens
+ // only if the string starts with a '/'
+ if (argString && argString.length) {
+ showConfirm(cm, 'Substitutions should be of the form ' +
+ ':s/pattern/replace/');
+ return;
+ }
+ }
+ // After the 3rd slash, we can have flags followed by a space followed
+ // by count.
+ if (trailing) {
+ flagsPart = trailing[0];
+ count = parseInt(trailing[1]);
+ if (flagsPart) {
+ if (flagsPart.indexOf('c') != -1) {
+ confirm = true;
+ flagsPart.replace('c', '');
+ }
+ if (flagsPart.indexOf('g') != -1) {
+ global = true;
+ flagsPart.replace('g', '');
+ }
+ regexPart = regexPart + '/' + flagsPart;
+ }
+ }
+ if (regexPart) {
+ // If regex part is empty, then use the previous query. Otherwise use
+ // the regex part as the new query.
+ try {
+ updateSearchQuery(cm, regexPart, true /** ignoreCase */,
+ true /** smartCase */);
+ } catch (e) {
+ showConfirm(cm, 'Invalid regex: ' + regexPart);
+ return;
+ }
+ }
+ replacePart = replacePart || vimGlobalState.lastSubstituteReplacePart;
+ if (replacePart === undefined) {
+ showConfirm(cm, 'No previous substitute regular expression');
+ return;
+ }
+ var state = getSearchState(cm);
+ var query = state.getQuery();
+ var lineStart = (params.line !== undefined) ? params.line : cm.getCursor().line;
+ var lineEnd = params.lineEnd || lineStart;
+ if (lineStart == cm.firstLine() && lineEnd == cm.lastLine()) {
+ lineEnd = Infinity;
+ }
+ if (count) {
+ lineStart = lineEnd;
+ lineEnd = lineStart + count - 1;
+ }
+ var startPos = clipCursorToContent(cm, Pos(lineStart, 0));
+ var cursor = cm.getSearchCursor(query, startPos);
+ doReplace(cm, confirm, global, lineStart, lineEnd, cursor, query, replacePart, params.callback);
+ },
+ redo: CodeMirror.commands.redo,
+ undo: CodeMirror.commands.undo,
+ write: function(cm) {
+ if (CodeMirror.commands.save) {
+ // If a save command is defined, call it.
+ CodeMirror.commands.save(cm);
+ } else {
+ // Saves to text area if no save command is defined.
+ cm.save();
+ }
+ },
+ nohlsearch: function(cm) {
+ clearSearchHighlight(cm);
+ },
+ delmarks: function(cm, params) {
+ if (!params.argString || !trim(params.argString)) {
+ showConfirm(cm, 'Argument required');
+ return;
+ }
+
+ var state = cm.state.vim;
+ var stream = new CodeMirror.StringStream(trim(params.argString));
+ while (!stream.eol()) {
+ stream.eatSpace();
+
+ // Record the streams position at the beginning of the loop for use
+ // in error messages.
+ var count = stream.pos;
+
+ if (!stream.match(/[a-zA-Z]/, false)) {
+ showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
+ return;
+ }
+
+ var sym = stream.next();
+ // Check if this symbol is part of a range
+ if (stream.match('-', true)) {
+ // This symbol is part of a range.
+
+ // The range must terminate at an alphabetic character.
+ if (!stream.match(/[a-zA-Z]/, false)) {
+ showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
+ return;
+ }
+
+ var startMark = sym;
+ var finishMark = stream.next();
+ // The range must terminate at an alphabetic character which
+ // shares the same case as the start of the range.
+ if (isLowerCase(startMark) && isLowerCase(finishMark) ||
+ isUpperCase(startMark) && isUpperCase(finishMark)) {
+ var start = startMark.charCodeAt(0);
+ var finish = finishMark.charCodeAt(0);
+ if (start >= finish) {
+ showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
+ return;
+ }
+
+ // Because marks are always ASCII values, and we have
+ // determined that they are the same case, we can use
+ // their char codes to iterate through the defined range.
+ for (var j = 0; j <= finish - start; j++) {
+ var mark = String.fromCharCode(start + j);
+ delete state.marks[mark];
+ }
+ } else {
+ showConfirm(cm, 'Invalid argument: ' + startMark + '-');
+ return;
+ }
+ } else {
+ // This symbol is a valid mark, and is not part of a range.
+ delete state.marks[sym];
+ }
+ }
+ }
+ };
+
+ var exCommandDispatcher = new ExCommandDispatcher();
+
+ /**
+ * @param {CodeMirror} cm CodeMirror instance we are in.
+ * @param {boolean} confirm Whether to confirm each replace.
+ * @param {Cursor} lineStart Line to start replacing from.
+ * @param {Cursor} lineEnd Line to stop replacing at.
+ * @param {RegExp} query Query for performing matches with.
+ * @param {string} replaceWith Text to replace matches with. May contain $1,
+ * $2, etc for replacing captured groups using Javascript replace.
+ * @param {function()} callback A callback for when the replace is done.
+ */
+ function doReplace(cm, confirm, global, lineStart, lineEnd, searchCursor, query,
+ replaceWith, callback) {
+ // Set up all the functions.
+ cm.state.vim.exMode = true;
+ var done = false;
+ var lastPos = searchCursor.from();
+ function replaceAll() {
+ cm.operation(function() {
+ while (!done) {
+ replace();
+ next();
+ }
+ stop();
+ });
+ }
+ function replace() {
+ var text = cm.getRange(searchCursor.from(), searchCursor.to());
+ var newText = text.replace(query, replaceWith);
+ searchCursor.replace(newText);
+ }
+ function next() {
+ // The below only loops to skip over multiple occurrences on the same
+ // line when 'global' is not true.
+ while(searchCursor.findNext() &&
+ isInRange(searchCursor.from(), lineStart, lineEnd)) {
+ if (!global && lastPos && searchCursor.from().line == lastPos.line) {
+ continue;
+ }
+ cm.scrollIntoView(searchCursor.from(), 30);
+ cm.setSelection(searchCursor.from(), searchCursor.to());
+ lastPos = searchCursor.from();
+ done = false;
+ return;
+ }
+ done = true;
+ }
+ function stop(close) {
+ if (close) { close(); }
+ cm.focus();
+ if (lastPos) {
+ cm.setCursor(lastPos);
+ var vim = cm.state.vim;
+ vim.exMode = false;
+ vim.lastHPos = vim.lastHSPos = lastPos.ch;
+ }
+ if (callback) { callback(); }
+ }
+ function onPromptKeyDown(e, _value, close) {
+ // Swallow all keys.
+ CodeMirror.e_stop(e);
+ var keyName = CodeMirror.keyName(e);
+ switch (keyName) {
+ case 'Y':
+ replace(); next(); break;
+ case 'N':
+ next(); break;
+ case 'A':
+ // replaceAll contains a call to close of its own. We don't want it
+ // to fire too early or multiple times.
+ var savedCallback = callback;
+ callback = undefined;
+ cm.operation(replaceAll);
+ callback = savedCallback;
+ break;
+ case 'L':
+ replace();
+ // fall through and exit.
+ case 'Q':
+ case 'Esc':
+ case 'Ctrl-C':
+ case 'Ctrl-[':
+ stop(close);
+ break;
+ }
+ if (done) { stop(close); }
+ return true;
+ }
+
+ // Actually do replace.
+ next();
+ if (done) {
+ showConfirm(cm, 'No matches for ' + query.source);
+ return;
+ }
+ if (!confirm) {
+ replaceAll();
+ if (callback) { callback(); };
+ return;
+ }
+ showPrompt(cm, {
+ prefix: 'replace with ' + replaceWith + ' (y/n/a/q/l)',
+ onKeyDown: onPromptKeyDown
+ });
+ }
+
+ CodeMirror.keyMap.vim = {
+ attach: attachVimMap,
+ detach: detachVimMap,
+ call: cmKey
+ };
+
+ function exitInsertMode(cm) {
+ var vim = cm.state.vim;
+ var macroModeState = vimGlobalState.macroModeState;
+ var insertModeChangeRegister = vimGlobalState.registerController.getRegister('.');
+ var isPlaying = macroModeState.isPlaying;
+ var lastChange = macroModeState.lastInsertModeChanges;
+ // In case of visual block, the insertModeChanges are not saved as a
+ // single word, so we convert them to a single word
+ // so as to update the ". register as expected in real vim.
+ var text = [];
+ if (!isPlaying) {
+ var selLength = lastChange.inVisualBlock ? vim.lastSelection.visualBlock.height : 1;
+ var changes = lastChange.changes;
+ var text = [];
+ var i = 0;
+ // In case of multiple selections in blockwise visual,
+ // the inserted text, for example: 'foo', is stored as
+ // 'f', 'f', InsertModeKey 'o', 'o', 'o', 'o'. (if you have a block with 2 lines).
+ // We push the contents of the changes array as per the following:
+ // 1. In case of InsertModeKey, just increment by 1.
+ // 2. In case of a character, jump by selLength (2 in the example).
+ while (i < changes.length) {
+ // This loop will convert 'ffoooo' to 'foo'.
+ text.push(changes[i]);
+ if (changes[i] instanceof InsertModeKey) {
+ i++;
+ } else {
+ i+= selLength;
+ }
+ }
+ lastChange.changes = text;
+ cm.off('change', onChange);
+ CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
+ }
+ if (!isPlaying && vim.insertModeRepeat > 1) {
+ // Perform insert mode repeat for commands like 3,a and 3,o.
+ repeatLastEdit(cm, vim, vim.insertModeRepeat - 1,
+ true /** repeatForInsert */);
+ vim.lastEditInputState.repeatOverride = vim.insertModeRepeat;
+ }
+ delete vim.insertModeRepeat;
+ vim.insertMode = false;
+ cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1);
+ cm.setOption('keyMap', 'vim');
+ cm.setOption('disableInput', true);
+ cm.toggleOverwrite(false); // exit replace mode if we were in it.
+ // update the ". register before exiting insert mode
+ insertModeChangeRegister.setText(lastChange.changes.join(''));
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
+ if (macroModeState.isRecording) {
+ logInsertModeChange(macroModeState);
+ }
+ }
+
+ function _mapCommand(command) {
+ defaultKeymap.unshift(command);
+ }
+
+ function mapCommand(keys, type, name, args, extra) {
+ var command = {keys: keys, type: type};
+ command[type] = name;
+ command[type + "Args"] = args;
+ for (var key in extra)
+ command[key] = extra[key];
+ _mapCommand(command);
+ }
+
+ // The timeout in milliseconds for the two-character ESC keymap should be
+ // adjusted according to your typing speed to prevent false positives.
+ defineOption('insertModeEscKeysTimeout', 200, 'number');
+
+ CodeMirror.keyMap['vim-insert'] = {
+ // TODO: override navigation keys so that Esc will cancel automatic
+ // indentation from o, O, i_
+ 'Ctrl-N': 'autocomplete',
+ 'Ctrl-P': 'autocomplete',
+ 'Enter': function(cm) {
+ var fn = CodeMirror.commands.newlineAndIndentContinueComment ||
+ CodeMirror.commands.newlineAndIndent;
+ fn(cm);
+ },
+ fallthrough: ['default'],
+ attach: attachVimMap,
+ detach: detachVimMap,
+ call: cmKey
+ };
+
+ CodeMirror.keyMap['vim-replace'] = {
+ 'Backspace': 'goCharLeft',
+ fallthrough: ['vim-insert'],
+ attach: attachVimMap,
+ detach: detachVimMap,
+ call: cmKey
+ };
+
+ function executeMacroRegister(cm, vim, macroModeState, registerName) {
+ var register = vimGlobalState.registerController.getRegister(registerName);
+ if (registerName == ':') {
+ // Read-only register containing last Ex command.
+ if (register.keyBuffer[0]) {
+ exCommandDispatcher.processCommand(cm, register.keyBuffer[0]);
+ }
+ macroModeState.isPlaying = false;
+ return;
+ }
+ var keyBuffer = register.keyBuffer;
+ var imc = 0;
+ macroModeState.isPlaying = true;
+ macroModeState.replaySearchQueries = register.searchQueries.slice(0);
+ for (var i = 0; i < keyBuffer.length; i++) {
+ var text = keyBuffer[i];
+ var match, key;
+ while (text) {
+ // Pull off one command key, which is either a single character
+ // or a special sequence wrapped in '<' and '>', e.g. ''.
+ match = (/<\w+-.+?>|<\w+>|./).exec(text);
+ key = match[0];
+ text = text.substring(match.index + key.length);
+ CodeMirror.Vim.handleKey(cm, key, 'macro');
+ if (vim.insertMode) {
+ var changes = register.insertModeChanges[imc++].changes;
+ vimGlobalState.macroModeState.lastInsertModeChanges.changes =
+ changes;
+ repeatInsertModeChanges(cm, changes, 1);
+ exitInsertMode(cm);
+ }
+ }
+ };
+ macroModeState.isPlaying = false;
+ }
+
+ function logKey(macroModeState, key) {
+ if (macroModeState.isPlaying) { return; }
+ var registerName = macroModeState.latestRegister;
+ var register = vimGlobalState.registerController.getRegister(registerName);
+ if (register) {
+ register.pushText(key);
+ }
+ }
+
+ function logInsertModeChange(macroModeState) {
+ if (macroModeState.isPlaying) { return; }
+ var registerName = macroModeState.latestRegister;
+ var register = vimGlobalState.registerController.getRegister(registerName);
+ if (register && register.pushInsertModeChanges) {
+ register.pushInsertModeChanges(macroModeState.lastInsertModeChanges);
+ }
+ }
+
+ function logSearchQuery(macroModeState, query) {
+ if (macroModeState.isPlaying) { return; }
+ var registerName = macroModeState.latestRegister;
+ var register = vimGlobalState.registerController.getRegister(registerName);
+ if (register && register.pushSearchQuery) {
+ register.pushSearchQuery(query);
+ }
+ }
+
+ /**
+ * Listens for changes made in insert mode.
+ * Should only be active in insert mode.
+ */
+ function onChange(_cm, changeObj) {
+ var macroModeState = vimGlobalState.macroModeState;
+ var lastChange = macroModeState.lastInsertModeChanges;
+ if (!macroModeState.isPlaying) {
+ while(changeObj) {
+ lastChange.expectCursorActivityForChange = true;
+ if (changeObj.origin == '+input' || changeObj.origin == 'paste'
+ || changeObj.origin === undefined /* only in testing */) {
+ var text = changeObj.text.join('\n');
+ lastChange.changes.push(text);
+ }
+ // Change objects may be chained with next.
+ changeObj = changeObj.next;
+ }
+ }
+ }
+
+ /**
+ * Listens for any kind of cursor activity on CodeMirror.
+ */
+ function onCursorActivity(cm) {
+ var vim = cm.state.vim;
+ if (vim.insertMode) {
+ // Tracking cursor activity in insert mode (for macro support).
+ var macroModeState = vimGlobalState.macroModeState;
+ if (macroModeState.isPlaying) { return; }
+ var lastChange = macroModeState.lastInsertModeChanges;
+ if (lastChange.expectCursorActivityForChange) {
+ lastChange.expectCursorActivityForChange = false;
+ } else {
+ // Cursor moved outside the context of an edit. Reset the change.
+ lastChange.changes = [];
+ }
+ } else if (!cm.curOp.isVimOp) {
+ handleExternalSelection(cm, vim);
+ }
+ if (vim.visualMode) {
+ updateFakeCursor(cm);
+ }
+ }
+ function updateFakeCursor(cm) {
+ var vim = cm.state.vim;
+ var from = clipCursorToContent(cm, copyCursor(vim.sel.head));
+ var to = offsetCursor(from, 0, 1);
+ if (vim.fakeCursor) {
+ vim.fakeCursor.clear();
+ }
+ vim.fakeCursor = cm.markText(from, to, {className: 'cm-animate-fat-cursor'});
+ }
+ function handleExternalSelection(cm, vim) {
+ var anchor = cm.getCursor('anchor');
+ var head = cm.getCursor('head');
+ // Enter or exit visual mode to match mouse selection.
+ if (vim.visualMode && !cm.somethingSelected()) {
+ exitVisualMode(cm, false);
+ } else if (!vim.visualMode && !vim.insertMode && cm.somethingSelected()) {
+ vim.visualMode = true;
+ vim.visualLine = false;
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"});
+ }
+ if (vim.visualMode) {
+ // Bind CodeMirror selection model to vim selection model.
+ // Mouse selections are considered visual characterwise.
+ var headOffset = !cursorIsBefore(head, anchor) ? -1 : 0;
+ var anchorOffset = cursorIsBefore(head, anchor) ? -1 : 0;
+ head = offsetCursor(head, 0, headOffset);
+ anchor = offsetCursor(anchor, 0, anchorOffset);
+ vim.sel = {
+ anchor: anchor,
+ head: head
+ };
+ updateMark(cm, vim, '<', cursorMin(head, anchor));
+ updateMark(cm, vim, '>', cursorMax(head, anchor));
+ } else if (!vim.insertMode) {
+ // Reset lastHPos if selection was modified by something outside of vim mode e.g. by mouse.
+ vim.lastHPos = cm.getCursor().ch;
+ }
+ }
+
+ /** Wrapper for special keys pressed in insert mode */
+ function InsertModeKey(keyName) {
+ this.keyName = keyName;
+ }
+
+ /**
+ * Handles raw key down events from the text area.
+ * - Should only be active in insert mode.
+ * - For recording deletes in insert mode.
+ */
+ function onKeyEventTargetKeyDown(e) {
+ var macroModeState = vimGlobalState.macroModeState;
+ var lastChange = macroModeState.lastInsertModeChanges;
+ var keyName = CodeMirror.keyName(e);
+ if (!keyName) { return; }
+ function onKeyFound() {
+ lastChange.changes.push(new InsertModeKey(keyName));
+ return true;
+ }
+ if (keyName.indexOf('Delete') != -1 || keyName.indexOf('Backspace') != -1) {
+ CodeMirror.lookupKey(keyName, 'vim-insert', onKeyFound);
+ }
+ }
+
+ /**
+ * Repeats the last edit, which includes exactly 1 command and at most 1
+ * insert. Operator and motion commands are read from lastEditInputState,
+ * while action commands are read from lastEditActionCommand.
+ *
+ * If repeatForInsert is true, then the function was called by
+ * exitInsertMode to repeat the insert mode changes the user just made. The
+ * corresponding enterInsertMode call was made with a count.
+ */
+ function repeatLastEdit(cm, vim, repeat, repeatForInsert) {
+ var macroModeState = vimGlobalState.macroModeState;
+ macroModeState.isPlaying = true;
+ var isAction = !!vim.lastEditActionCommand;
+ var cachedInputState = vim.inputState;
+ function repeatCommand() {
+ if (isAction) {
+ commandDispatcher.processAction(cm, vim, vim.lastEditActionCommand);
+ } else {
+ commandDispatcher.evalInput(cm, vim);
+ }
+ }
+ function repeatInsert(repeat) {
+ if (macroModeState.lastInsertModeChanges.changes.length > 0) {
+ // For some reason, repeat cw in desktop VIM does not repeat
+ // insert mode changes. Will conform to that behavior.
+ repeat = !vim.lastEditActionCommand ? 1 : repeat;
+ var changeObject = macroModeState.lastInsertModeChanges;
+ repeatInsertModeChanges(cm, changeObject.changes, repeat);
+ }
+ }
+ vim.inputState = vim.lastEditInputState;
+ if (isAction && vim.lastEditActionCommand.interlaceInsertRepeat) {
+ // o and O repeat have to be interlaced with insert repeats so that the
+ // insertions appear on separate lines instead of the last line.
+ for (var i = 0; i < repeat; i++) {
+ repeatCommand();
+ repeatInsert(1);
+ }
+ } else {
+ if (!repeatForInsert) {
+ // Hack to get the cursor to end up at the right place. If I is
+ // repeated in insert mode repeat, cursor will be 1 insert
+ // change set left of where it should be.
+ repeatCommand();
+ }
+ repeatInsert(repeat);
+ }
+ vim.inputState = cachedInputState;
+ if (vim.insertMode && !repeatForInsert) {
+ // Don't exit insert mode twice. If repeatForInsert is set, then we
+ // were called by an exitInsertMode call lower on the stack.
+ exitInsertMode(cm);
+ }
+ macroModeState.isPlaying = false;
+ };
+
+ function repeatInsertModeChanges(cm, changes, repeat) {
+ function keyHandler(binding) {
+ if (typeof binding == 'string') {
+ CodeMirror.commands[binding](cm);
+ } else {
+ binding(cm);
+ }
+ return true;
+ }
+ var head = cm.getCursor('head');
+ var inVisualBlock = vimGlobalState.macroModeState.lastInsertModeChanges.inVisualBlock;
+ if (inVisualBlock) {
+ // Set up block selection again for repeating the changes.
+ var vim = cm.state.vim;
+ var lastSel = vim.lastSelection;
+ var offset = getOffset(lastSel.anchor, lastSel.head);
+ selectForInsert(cm, head, offset.line + 1);
+ repeat = cm.listSelections().length;
+ cm.setCursor(head);
+ }
+ for (var i = 0; i < repeat; i++) {
+ if (inVisualBlock) {
+ cm.setCursor(offsetCursor(head, i, 0));
+ }
+ for (var j = 0; j < changes.length; j++) {
+ var change = changes[j];
+ if (change instanceof InsertModeKey) {
+ CodeMirror.lookupKey(change.keyName, 'vim-insert', keyHandler);
+ } else {
+ var cur = cm.getCursor();
+ cm.replaceRange(change, cur, cur);
+ }
+ }
+ }
+ if (inVisualBlock) {
+ cm.setCursor(offsetCursor(head, 0, 1));
+ }
+ }
+
+ resetVimGlobalState();
+ return vimApi;
+ };
+ // Initialize Vim and make it available as an API.
+ CodeMirror.Vim = Vim();
+});
diff --git a/www/code/codemirror.css b/www/code/codemirror-5.13.2/lib/codemirror.css
similarity index 97%
rename from www/code/codemirror.css
rename to www/code/codemirror-5.13.2/lib/codemirror.css
index 3543523e6..1cf66a9fa 100644
--- a/www/code/codemirror.css
+++ b/www/code/codemirror-5.13.2/lib/codemirror.css
@@ -165,7 +165,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
- before actuall scrolling happens, thus preventing shaking and
+ before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
@@ -191,12 +191,14 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
+ min-height: 100%;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
display: inline-block;
+ vertical-align: top;
margin-bottom: -30px;
/* Hack to make IE7 behave */
*zoom:1;
@@ -244,6 +246,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
+ -webkit-font-variant-ligatures: none;
+ font-variant-ligatures: none;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
diff --git a/www/code/codemirror.js b/www/code/codemirror-5.13.2/lib/codemirror.js
similarity index 97%
rename from www/code/codemirror.js
rename to www/code/codemirror-5.13.2/lib/codemirror.js
index c6f9af859..ec49e3a33 100644
--- a/www/code/codemirror.js
+++ b/www/code/codemirror-5.13.2/lib/codemirror.js
@@ -13,7 +13,7 @@
else if (typeof define == "function" && define.amd) // AMD
return define([], mod);
else // Plain browser env
- this.CodeMirror = mod();
+ (this || window).CodeMirror = mod();
})(function() {
"use strict";
@@ -21,27 +21,29 @@
// Kludges for bugs and behavior differences that can't be feature
// detected are enabled based on userAgent etc sniffing.
+ var userAgent = navigator.userAgent;
+ var platform = navigator.platform;
- var gecko = /gecko\/\d/i.test(navigator.userAgent);
- var ie_upto10 = /MSIE \d/.test(navigator.userAgent);
- var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);
+ var gecko = /gecko\/\d/i.test(userAgent);
+ var ie_upto10 = /MSIE \d/.test(userAgent);
+ var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent);
var ie = ie_upto10 || ie_11up;
var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]);
- var webkit = /WebKit\//.test(navigator.userAgent);
- var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
- var chrome = /Chrome\//.test(navigator.userAgent);
- var presto = /Opera\//.test(navigator.userAgent);
+ var webkit = /WebKit\//.test(userAgent);
+ var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent);
+ var chrome = /Chrome\//.test(userAgent);
+ var presto = /Opera\//.test(userAgent);
var safari = /Apple Computer/.test(navigator.vendor);
- var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);
- var phantom = /PhantomJS/.test(navigator.userAgent);
+ var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent);
+ var phantom = /PhantomJS/.test(userAgent);
- var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
+ var ios = /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent);
// This is woefully incomplete. Suggestions for alternative methods welcome.
- var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
- var mac = ios || /Mac/.test(navigator.platform);
- var windows = /win/i.test(navigator.platform);
+ var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent);
+ var mac = ios || /Mac/.test(platform);
+ var windows = /win/i.test(platform);
- var presto_version = presto && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
+ var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/);
if (presto_version) presto_version = Number(presto_version[1]);
if (presto_version && presto_version >= 15) { presto = false; webkit = true; }
// Some browsers use the wrong event properties to signal cmd/ctrl on OS X
@@ -408,7 +410,7 @@
if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal");
});
- this.checkedOverlay = false;
+ this.checkedZeroWidth = false;
// Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWidth = "18px";
}
@@ -443,29 +445,43 @@
this.horiz.firstChild.style.width = "0";
}
- if (!this.checkedOverlay && measure.clientHeight > 0) {
- if (sWidth == 0) this.overlayHack();
- this.checkedOverlay = true;
+ if (!this.checkedZeroWidth && measure.clientHeight > 0) {
+ if (sWidth == 0) this.zeroWidthHack();
+ this.checkedZeroWidth = true;
}
return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0};
},
setScrollLeft: function(pos) {
if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos;
+ if (this.disableHoriz) this.enableZeroWidthBar(this.horiz, this.disableHoriz);
},
setScrollTop: function(pos) {
if (this.vert.scrollTop != pos) this.vert.scrollTop = pos;
+ if (this.disableVert) this.enableZeroWidthBar(this.vert, this.disableVert);
},
- overlayHack: function() {
+ zeroWidthHack: function() {
var w = mac && !mac_geMountainLion ? "12px" : "18px";
- this.horiz.style.minHeight = this.vert.style.minWidth = w;
- var self = this;
- var barMouseDown = function(e) {
- if (e_target(e) != self.vert && e_target(e) != self.horiz)
- operation(self.cm, onMouseDown)(e);
- };
- on(this.vert, "mousedown", barMouseDown);
- on(this.horiz, "mousedown", barMouseDown);
+ this.horiz.style.height = this.vert.style.width = w;
+ this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none";
+ this.disableHoriz = new Delayed;
+ this.disableVert = new Delayed;
+ },
+ enableZeroWidthBar: function(bar, delay) {
+ bar.style.pointerEvents = "auto";
+ function maybeDisable() {
+ // To find out whether the scrollbar is still visible, we
+ // check whether the element under the pixel in the bottom
+ // left corner of the scrollbar box is the scrollbar box
+ // itself (when the bar is still visible) or its filler child
+ // (when the bar is hidden). If it is still visible, we keep
+ // it enabled, if it's hidden, we disable pointer events.
+ var box = bar.getBoundingClientRect();
+ var elt = document.elementFromPoint(box.left + 1, box.bottom - 1);
+ if (elt != bar) bar.style.pointerEvents = "none";
+ else delay.set(1000, maybeDisable);
+ }
+ delay.set(1000, maybeDisable);
},
clear: function() {
var parent = this.horiz.parentNode;
@@ -527,6 +543,7 @@
d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px";
d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px";
+ d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent"
if (sizes.right && sizes.bottom) {
d.scrollbarFiller.style.display = "block";
@@ -730,6 +747,7 @@
function postUpdateDisplay(cm, update) {
var viewport = update.viewport;
+
for (var first = true;; first = false) {
if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) {
// Clip forced viewport to actual scrollable area.
@@ -745,8 +763,8 @@
updateHeightsInViewport(cm);
var barMeasure = measureForScrollbars(cm);
updateSelection(cm);
- setDocumentHeight(cm, barMeasure);
updateScrollbars(cm, barMeasure);
+ setDocumentHeight(cm, barMeasure);
}
update.signal(cm, "update", cm);
@@ -763,17 +781,16 @@
postUpdateDisplay(cm, update);
var barMeasure = measureForScrollbars(cm);
updateSelection(cm);
- setDocumentHeight(cm, barMeasure);
updateScrollbars(cm, barMeasure);
+ setDocumentHeight(cm, barMeasure);
update.finish();
}
}
function setDocumentHeight(cm, measure) {
cm.display.sizer.style.minHeight = measure.docHeight + "px";
- var total = measure.docHeight + cm.display.barHeight;
- cm.display.heightForcer.style.top = total + "px";
- cm.display.gutters.style.height = Math.max(total + scrollGap(cm), measure.clientHeight) + "px";
+ cm.display.heightForcer.style.top = measure.docHeight + "px";
+ cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px";
}
// Read the actual heights of the rendered lines, and update their
@@ -807,7 +824,7 @@
// given line.
function updateWidgetHeight(line) {
if (line.widgets) for (var i = 0; i < line.widgets.length; ++i)
- line.widgets[i].height = line.widgets[i].node.offsetHeight;
+ line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight;
}
// Do a bulk-read of the DOM positions and sizes needed to draw the
@@ -1078,10 +1095,6 @@
if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); }
}
- function isReadOnly(cm) {
- return cm.options.readOnly || cm.doc.cantEdit;
- }
-
// This will be set to an array of strings when copying, so that,
// when pasting, we know what kind of selections the copied text
// was made out of.
@@ -1136,7 +1149,7 @@
var pasted = e.clipboardData && e.clipboardData.getData("text/plain");
if (pasted) {
e.preventDefault();
- if (!isReadOnly(cm) && !cm.options.disableInput)
+ if (!cm.isReadOnly() && !cm.options.disableInput)
runInOp(cm, function() { applyTextInput(cm, pasted, 0, null, "paste"); });
return true;
}
@@ -1239,13 +1252,14 @@
});
on(te, "paste", function(e) {
- if (handlePaste(e, cm)) return true;
+ if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return
cm.state.pasteIncoming = true;
input.fastPoll();
});
function prepareCopyCut(e) {
+ if (signalDOMEvent(cm, e)) return
if (cm.somethingSelected()) {
lastCopied = cm.getSelections();
if (input.inaccurateSelection) {
@@ -1273,7 +1287,7 @@
on(te, "copy", prepareCopyCut);
on(display.scroller, "paste", function(e) {
- if (eventInWidget(display, e)) return;
+ if (eventInWidget(display, e) || signalDOMEvent(cm, e)) return;
cm.state.pasteIncoming = true;
input.focus();
});
@@ -1407,7 +1421,7 @@
// in which case reading its value would be expensive.
if (this.contextMenuPending || !cm.state.focused ||
(hasSelection(input) && !prevInput && !this.composing) ||
- isReadOnly(cm) || cm.options.disableInput || cm.state.keySeq)
+ cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq)
return false;
var text = input.value;
@@ -1469,10 +1483,11 @@
if (reset && cm.doc.sel.contains(pos) == -1)
operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll);
- var oldCSS = te.style.cssText;
- input.wrapper.style.position = "absolute";
- te.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
- "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " +
+ var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText;
+ input.wrapper.style.cssText = "position: absolute"
+ var wrapperBox = input.wrapper.getBoundingClientRect()
+ te.style.cssText = "position: absolute; width: 30px; height: 30px; top: " + (e.clientY - wrapperBox.top - 5) +
+ "px; left: " + (e.clientX - wrapperBox.left - 5) + "px; z-index: 1000; background: " +
(ie ? "rgba(255, 255, 255, .05)" : "transparent") +
"; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712)
@@ -1503,7 +1518,7 @@
}
function rehide() {
input.contextMenuPending = false;
- input.wrapper.style.position = "relative";
+ input.wrapper.style.cssText = oldWrapperCSS
te.style.cssText = oldCSS;
if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos);
@@ -1558,7 +1573,9 @@
var div = input.div = display.lineDiv;
disableBrowserMagic(div);
- on(div, "paste", function(e) { handlePaste(e, cm); })
+ on(div, "paste", function(e) {
+ if (!signalDOMEvent(cm, e)) handlePaste(e, cm);
+ })
on(div, "compositionstart", function(e) {
var data = e.data;
@@ -1596,11 +1613,12 @@
on(div, "input", function() {
if (input.composing) return;
- if (isReadOnly(cm) || !input.pollContent())
+ if (cm.isReadOnly() || !input.pollContent())
runInOp(input.cm, function() {regChange(cm);});
});
function onCopyCut(e) {
+ if (signalDOMEvent(cm, e)) return
if (cm.somethingSelected()) {
lastCopied = cm.getSelections();
if (e.type == "cut") cm.replaceSelection("", null, "cut");
@@ -1676,8 +1694,13 @@
try { var rng = range(start.node, start.offset, end.offset, end.node); }
catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible
if (rng) {
- sel.removeAllRanges();
- sel.addRange(rng);
+ if (!gecko && this.cm.state.focused) {
+ sel.collapse(start.node, start.offset);
+ if (!rng.collapsed) sel.addRange(rng);
+ } else {
+ sel.removeAllRanges();
+ sel.addRange(rng);
+ }
if (old && sel.anchorNode == null) sel.addRange(old);
else if (gecko) this.startGracePeriod();
}
@@ -1821,7 +1844,7 @@
this.div.focus();
},
applyComposition: function(composing) {
- if (isReadOnly(this.cm))
+ if (this.cm.isReadOnly())
operation(this.cm, regChange)(this.cm)
else if (composing.data && composing.data != composing.startData)
operation(this.cm, applyTextInput)(this.cm, composing.data, 0, composing.sel);
@@ -1833,7 +1856,7 @@
onKeyPress: function(e) {
e.preventDefault();
- if (!isReadOnly(this.cm))
+ if (!this.cm.isReadOnly())
operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0);
},
@@ -2138,7 +2161,7 @@
// Give beforeSelectionChange handlers a change to influence a
// selection update.
- function filterSelectionChange(doc, sel) {
+ function filterSelectionChange(doc, sel, options) {
var obj = {
ranges: sel.ranges,
update: function(ranges) {
@@ -2146,7 +2169,8 @@
for (var i = 0; i < ranges.length; i++)
this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),
clipPos(doc, ranges[i].head));
- }
+ },
+ origin: options && options.origin
};
signal(doc, "beforeSelectionChange", doc, obj);
if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
@@ -2172,7 +2196,7 @@
function setSelectionNoUndo(doc, sel, options) {
if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange"))
- sel = filterSelectionChange(doc, sel);
+ sel = filterSelectionChange(doc, sel, options);
var bias = options && options.bias ||
(cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1);
@@ -2206,8 +2230,9 @@
var out;
for (var i = 0; i < sel.ranges.length; i++) {
var range = sel.ranges[i];
- var newAnchor = skipAtomic(doc, range.anchor, bias, mayClear);
- var newHead = skipAtomic(doc, range.head, bias, mayClear);
+ var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i];
+ var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear);
+ var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear);
if (out || newAnchor != range.anchor || newHead != range.head) {
if (!out) out = sel.ranges.slice(0, i);
out[i] = new Range(newAnchor, newHead);
@@ -2216,54 +2241,61 @@
return out ? normalizeSelection(out, sel.primIndex) : sel;
}
- // Ensure a given position is not inside an atomic range.
- function skipAtomic(doc, pos, bias, mayClear) {
- var flipped = false, curPos = pos;
- var dir = bias || 1;
- doc.cantEdit = false;
- search: for (;;) {
- var line = getLine(doc, curPos.line);
- if (line.markedSpans) {
- for (var i = 0; i < line.markedSpans.length; ++i) {
- var sp = line.markedSpans[i], m = sp.marker;
- if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
- (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
- if (mayClear) {
- signal(m, "beforeCursorEnter");
- if (m.explicitlyCleared) {
- if (!line.markedSpans) break;
- else {--i; continue;}
- }
- }
- if (!m.atomic) continue;
- var newPos = m.find(dir < 0 ? -1 : 1);
- if (cmp(newPos, curPos) == 0) {
- newPos.ch += dir;
- if (newPos.ch < 0) {
- if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));
- else newPos = null;
- } else if (newPos.ch > line.text.length) {
- if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);
- else newPos = null;
- }
- if (!newPos) {
- if (flipped) {
- // Driven in a corner -- no valid cursor position found at all
- // -- try again *with* clearing, if we didn't already
- if (!mayClear) return skipAtomic(doc, pos, bias, true);
- // Otherwise, turn off editing until further notice, and return the start of the doc
- doc.cantEdit = true;
- return Pos(doc.first, 0);
- }
- flipped = true; newPos = pos; dir = -dir;
- }
- }
- curPos = newPos;
- continue search;
+ function skipAtomicInner(doc, pos, oldPos, dir, mayClear) {
+ var line = getLine(doc, pos.line);
+ if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) {
+ var sp = line.markedSpans[i], m = sp.marker;
+ if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) &&
+ (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) {
+ if (mayClear) {
+ signal(m, "beforeCursorEnter");
+ if (m.explicitlyCleared) {
+ if (!line.markedSpans) break;
+ else {--i; continue;}
}
}
+ if (!m.atomic) continue;
+
+ if (oldPos) {
+ var near = m.find(dir < 0 ? 1 : -1), diff;
+ if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft)
+ near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null);
+ if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0))
+ return skipAtomicInner(doc, near, pos, dir, mayClear);
+ }
+
+ var far = m.find(dir < 0 ? -1 : 1);
+ if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight)
+ far = movePos(doc, far, dir, far.line == pos.line ? line : null);
+ return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null;
}
- return curPos;
+ }
+ return pos;
+ }
+
+ // Ensure a given position is not inside an atomic range.
+ function skipAtomic(doc, pos, oldPos, bias, mayClear) {
+ var dir = bias || 1;
+ var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) ||
+ (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) ||
+ skipAtomicInner(doc, pos, oldPos, -dir, mayClear) ||
+ (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true));
+ if (!found) {
+ doc.cantEdit = true;
+ return Pos(doc.first, 0);
+ }
+ return found;
+ }
+
+ function movePos(doc, pos, dir, line) {
+ if (dir < 0 && pos.ch == 0) {
+ if (pos.line > doc.first) return clipPos(doc, Pos(pos.line - 1));
+ else return null;
+ } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) {
+ if (pos.line < doc.first + doc.size - 1) return Pos(pos.line + 1, 0);
+ else return null;
+ } else {
+ return new Pos(pos.line, pos.ch + dir);
}
}
@@ -2281,6 +2313,7 @@
for (var i = 0; i < doc.sel.ranges.length; i++) {
if (primary === false && i == doc.sel.primIndex) continue;
var range = doc.sel.ranges[i];
+ if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) continue;
var collapsed = range.empty();
if (collapsed || cm.options.showCursorWhenSelecting)
drawSelectionCursor(cm, range.head, curFragment);
@@ -3082,16 +3115,17 @@
if (op.preparedSelection)
cm.display.input.showSelection(op.preparedSelection);
- if (op.updatedDisplay)
- setDocumentHeight(cm, op.barMeasure);
if (op.updatedDisplay || op.startHeight != cm.doc.height)
updateScrollbars(cm, op.barMeasure);
+ if (op.updatedDisplay)
+ setDocumentHeight(cm, op.barMeasure);
if (op.selectionChanged) restartBlink(cm);
if (cm.state.focused && op.updateInput)
cm.display.input.reset(op.typing);
- if (op.focus && op.focus == activeElt()) ensureFocus(op.cm);
+ if (op.focus && op.focus == activeElt() && (!document.hasFocus || document.hasFocus()))
+ ensureFocus(op.cm);
}
function endOperation_finish(op) {
@@ -3110,7 +3144,7 @@
display.scroller.scrollTop = doc.scrollTop;
}
if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) {
- doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - displayWidth(cm), op.scrollLeft));
+ doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft));
display.scrollbars.setScrollLeft(doc.scrollLeft);
display.scroller.scrollLeft = doc.scrollLeft;
alignHorizontally(cm);
@@ -3406,7 +3440,7 @@
return dx * dx + dy * dy > 20 * 20;
}
on(d.scroller, "touchstart", function(e) {
- if (!isMouseLikeTouchEvent(e)) {
+ if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) {
clearTimeout(touchFinished);
var now = +new Date;
d.activeTouch = {start: now, moved: false,
@@ -3461,7 +3495,7 @@
over: function(e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e); }},
start: function(e){onDragStart(cm, e);},
drop: operation(cm, onDrop),
- leave: function() {clearDragCursor(cm);}
+ leave: function(e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm); }}
};
var inp = d.input.getField();
@@ -3535,7 +3569,7 @@
// not interfere with, such as a scrollbar or widget.
function onMouseDown(e) {
var cm = this, display = cm.display;
- if (display.activeTouch && display.input.supportsTouch() || signalDOMEvent(cm, e)) return;
+ if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) return;
display.shift = e.shiftKey;
if (eventInWidget(display, e)) {
@@ -3591,7 +3625,7 @@
}
var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained;
- if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) &&
+ if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() &&
type == "single" && (contained = sel.contains(start)) > -1 &&
(cmp((contained = sel.ranges[contained]).from(), start) < 0 || start.xRel > 0) &&
(cmp(contained.to(), start) > 0 || start.xRel < 0))
@@ -3776,7 +3810,7 @@
// Determines whether an event happened in the gutter, and fires the
// handlers for the corresponding event.
- function gutterEvent(cm, e, type, prevent, signalfn) {
+ function gutterEvent(cm, e, type, prevent) {
try { var mX = e.clientX, mY = e.clientY; }
catch(e) { return false; }
if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false;
@@ -3793,14 +3827,14 @@
if (g && g.getBoundingClientRect().right >= mX) {
var line = lineAtHeight(cm.doc, mY);
var gutter = cm.options.gutters[i];
- signalfn(cm, type, cm, line, gutter, e);
+ signal(cm, type, cm, line, gutter, e);
return e_defaultPrevented(e);
}
}
}
function clickInGutter(cm, e) {
- return gutterEvent(cm, e, "gutterClick", true, signalLater);
+ return gutterEvent(cm, e, "gutterClick", true);
}
// Kludge to work around strange IE behavior where it'll sometimes
@@ -3815,15 +3849,21 @@
e_preventDefault(e);
if (ie) lastDrop = +new Date;
var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
- if (!pos || isReadOnly(cm)) return;
+ if (!pos || cm.isReadOnly()) return;
// Might be a file drop, in which case we simply extract the text
// and insert it.
if (files && files.length && window.FileReader && window.File) {
var n = files.length, text = Array(n), read = 0;
var loadFile = function(file, i) {
+ if (cm.options.allowDropFileTypes &&
+ indexOf(cm.options.allowDropFileTypes, file.type) == -1)
+ return;
+
var reader = new FileReader;
reader.onload = operation(cm, function() {
- text[i] = reader.result;
+ var content = reader.result;
+ if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) content = "";
+ text[i] = content;
if (++read == n) {
pos = clipPos(cm.doc, pos);
var change = {from: pos, to: pos,
@@ -3965,8 +4005,9 @@
var display = cm.display, scroll = display.scroller;
// Quit if there's nothing to scroll here
- if (!(dx && scroll.scrollWidth > scroll.clientWidth ||
- dy && scroll.scrollHeight > scroll.clientHeight)) return;
+ var canScrollX = scroll.scrollWidth > scroll.clientWidth;
+ var canScrollY = scroll.scrollHeight > scroll.clientHeight;
+ if (!(dx && canScrollX || dy && canScrollY)) return;
// Webkit browsers on OS X abort momentum scrolls when the target
// of the scroll event is removed from the scrollable element.
@@ -3990,10 +4031,15 @@
// scrolling entirely here. It'll be slightly off from native, but
// better than glitching out.
if (dx && !gecko && !presto && wheelPixelsPerUnit != null) {
- if (dy)
+ if (dy && canScrollY)
setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));
setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));
- e_preventDefault(e);
+ // Only prevent default scrolling if vertical scrolling is
+ // actually possible. Otherwise, it causes vertical scroll
+ // jitter on OSX trackpads when deltaX is small and deltaY
+ // is large (issue #3579)
+ if (!dy || (dy && canScrollY))
+ e_preventDefault(e);
display.wheelStartX = null; // Abort measurement, if in progress
return;
}
@@ -4042,7 +4088,7 @@
cm.display.input.ensurePolled();
var prevShift = cm.display.shift, done = false;
try {
- if (isReadOnly(cm)) cm.state.suppressEdits = true;
+ if (cm.isReadOnly()) cm.state.suppressEdits = true;
if (dropShift) cm.display.shift = false;
done = bound(cm) != Pass;
} finally {
@@ -4221,12 +4267,13 @@
// right-click take effect on it.
function onContextMenu(cm, e) {
if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) return;
+ if (signalDOMEvent(cm, e, "contextmenu")) return;
cm.display.input.onContextMenu(e);
}
function contextMenuInGutter(cm, e) {
if (!hasHandler(cm, "gutterContextMenu")) return false;
- return gutterEvent(cm, e, "gutterContextMenu", false, signal);
+ return gutterEvent(cm, e, "gutterContextMenu", false);
}
// UPDATING
@@ -4774,10 +4821,9 @@
function findPosH(doc, pos, dir, unit, visually) {
var line = pos.line, ch = pos.ch, origDir = dir;
var lineObj = getLine(doc, line);
- var possible = true;
function findNextLine() {
var l = line + dir;
- if (l < doc.first || l >= doc.first + doc.size) return (possible = false);
+ if (l < doc.first || l >= doc.first + doc.size) return false
line = l;
return lineObj = getLine(doc, l);
}
@@ -4787,14 +4833,16 @@
if (!boundToLine && findNextLine()) {
if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
else ch = dir < 0 ? lineObj.text.length : 0;
- } else return (possible = false);
+ } else return false
} else ch = next;
return true;
}
- if (unit == "char") moveOnce();
- else if (unit == "column") moveOnce(true);
- else if (unit == "word" || unit == "group") {
+ if (unit == "char") {
+ moveOnce()
+ } else if (unit == "column") {
+ moveOnce(true)
+ } else if (unit == "word" || unit == "group") {
var sawType = null, group = unit == "group";
var helper = doc.cm && doc.cm.getHelper(pos, "wordChars");
for (var first = true;; first = false) {
@@ -4814,8 +4862,8 @@
if (dir > 0 && !moveOnce(!first)) break;
}
}
- var result = skipAtomic(doc, Pos(line, ch), origDir, true);
- if (!possible) result.hitSide = true;
+ var result = skipAtomic(doc, Pos(line, ch), pos, origDir, true);
+ if (!cmp(pos, result)) result.hitSide = true;
return result;
}
@@ -5202,6 +5250,7 @@
signal(this, "overwriteToggle", this, this.state.overwrite);
},
hasFocus: function() { return this.display.input.getField() == activeElt(); },
+ isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit); },
scrollTo: methodOp(function(x, y) {
if (x != null || y != null) resolveScrollToPos(this);
@@ -5404,6 +5453,7 @@
});
option("disableInput", false, function(cm, val) {if (!val) cm.display.input.reset();}, true);
option("dragDrop", true, dragDropChanged);
+ option("allowDropFileTypes", null);
option("cursorBlinkRate", 530);
option("cursorScrollMargin", 0);
@@ -5708,8 +5758,8 @@
var range = cm.listSelections()[i];
cm.replaceRange(cm.doc.lineSeparator(), range.anchor, range.head, "+input");
cm.indentLine(range.from().line + 1, null, true);
- ensureCursorVisible(cm);
}
+ ensureCursorVisible(cm);
});
},
toggleOverwrite: function(cm) {cm.toggleOverwrite();}
@@ -6637,7 +6687,7 @@
parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;";
removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle));
}
- return widget.height = widget.node.offsetHeight;
+ return widget.height = widget.node.parentNode.offsetHeight;
}
function addLineWidget(doc, handle, node, options) {
@@ -7047,7 +7097,7 @@
if (nextChange == pos) { // Update current marker set
spanStyle = spanEndStyle = spanStartStyle = title = css = "";
collapsed = null; nextChange = Infinity;
- var foundBookmarks = [];
+ var foundBookmarks = [], endStyles
for (var j = 0; j < spans.length; ++j) {
var sp = spans[j], m = sp.marker;
if (m.type == "bookmark" && sp.from == pos && m.widgetNode) {
@@ -7058,9 +7108,9 @@
spanEndStyle = "";
}
if (m.className) spanStyle += " " + m.className;
- if (m.css) css = m.css;
+ if (m.css) css = (css ? css + ";" : "") + m.css;
if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
- if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle;
+ if (m.endStyle && sp.to == nextChange) (endStyles || (endStyles = [])).push(m.endStyle, sp.to)
if (m.title && !title) title = m.title;
if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0))
collapsed = sp;
@@ -7068,14 +7118,17 @@
nextChange = sp.from;
}
}
+ if (endStyles) for (var j = 0; j < endStyles.length; j += 2)
+ if (endStyles[j + 1] == nextChange) spanEndStyle += " " + endStyles[j]
+
+ if (!collapsed || collapsed.from == pos) for (var j = 0; j < foundBookmarks.length; ++j)
+ buildCollapsedSpan(builder, 0, foundBookmarks[j]);
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]);
}
if (pos >= len) break;
@@ -7327,6 +7380,7 @@
this.id = ++nextDocId;
this.modeOption = mode;
this.lineSep = lineSep;
+ this.extend = false;
if (typeof text == "string") text = this.splitLines(text);
updateDoc(this, {from: start, to: start, text: text});
@@ -7414,10 +7468,11 @@
extendSelection(this, clipPos(this, head), other && clipPos(this, other), options);
}),
extendSelections: docMethodOp(function(heads, options) {
- extendSelections(this, clipPosArray(this, heads, options));
+ extendSelections(this, clipPosArray(this, heads), options);
}),
extendSelectionsBy: docMethodOp(function(f, options) {
- extendSelections(this, map(this.sel.ranges, f), options);
+ var heads = map(this.sel.ranges, f);
+ extendSelections(this, clipPosArray(this, heads), options);
}),
setSelections: docMethodOp(function(ranges, primary, options) {
if (!ranges.length) return;
@@ -7542,7 +7597,7 @@
removeLineWidget: function(widget) { widget.clear(); },
markText: function(from, to, options) {
- return markText(this, clipPos(this, from), clipPos(this, to), options, "range");
+ return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range");
},
setBookmark: function(pos, options) {
var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
@@ -7570,9 +7625,9 @@
var spans = line.markedSpans;
if (spans) for (var i = 0; i < spans.length; i++) {
var span = spans[i];
- if (!(lineNo == from.line && from.ch > span.to ||
- span.from == null && lineNo != from.line||
- lineNo == to.line && span.from > to.ch) &&
+ if (!(span.to != null && lineNo == from.line && from.ch > span.to ||
+ span.from == null && lineNo != from.line ||
+ span.from != null && lineNo == to.line && span.from > to.ch) &&
(!filter || filter(span.marker)))
found.push(span.marker.parent || span.marker);
}
@@ -8103,24 +8158,30 @@
}
};
+ var noHandlers = []
+ function getHandlers(emitter, type, copy) {
+ var arr = emitter._handlers && emitter._handlers[type]
+ if (copy) return arr && arr.length > 0 ? arr.slice() : noHandlers
+ else return arr || noHandlers
+ }
+
var off = CodeMirror.off = function(emitter, type, f) {
if (emitter.removeEventListener)
emitter.removeEventListener(type, f, false);
else if (emitter.detachEvent)
emitter.detachEvent("on" + type, f);
else {
- var arr = emitter._handlers && emitter._handlers[type];
- if (!arr) return;
- for (var i = 0; i < arr.length; ++i)
- if (arr[i] == f) { arr.splice(i, 1); break; }
+ var handlers = getHandlers(emitter, type, false)
+ for (var i = 0; i < handlers.length; ++i)
+ if (handlers[i] == f) { handlers.splice(i, 1); break; }
}
};
var signal = CodeMirror.signal = function(emitter, type /*, values...*/) {
- var arr = emitter._handlers && emitter._handlers[type];
- if (!arr) return;
+ var handlers = getHandlers(emitter, type, true)
+ if (!handlers.length) return;
var args = Array.prototype.slice.call(arguments, 2);
- for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
+ for (var i = 0; i < handlers.length; ++i) handlers[i].apply(null, args);
};
var orphanDelayedCallbacks = null;
@@ -8133,8 +8194,8 @@
// them to be executed when the last operation ends, or, if no
// operation is active, when a timeout fires.
function signalLater(emitter, type /*, values...*/) {
- var arr = emitter._handlers && emitter._handlers[type];
- if (!arr) return;
+ var arr = getHandlers(emitter, type, false)
+ if (!arr.length) return;
var args = Array.prototype.slice.call(arguments, 2), list;
if (operationGroup) {
list = operationGroup.delayedCallbacks;
@@ -8174,8 +8235,7 @@
}
function hasHandler(emitter, type) {
- var arr = emitter._handlers && emitter._handlers[type];
- return arr && arr.length > 0;
+ return getHandlers(emitter, type).length > 0
}
// Add on and off methods to a constructor's prototype, to make
@@ -8829,7 +8889,7 @@
// THE END
- CodeMirror.version = "5.7.0";
+ CodeMirror.version = "5.13.2";
return CodeMirror;
});
diff --git a/www/code/mode/apl/apl.js b/www/code/codemirror-5.13.2/mode/apl/apl.js
similarity index 100%
rename from www/code/mode/apl/apl.js
rename to www/code/codemirror-5.13.2/mode/apl/apl.js
diff --git a/www/code/mode/apl/index.html b/www/code/codemirror-5.13.2/mode/apl/index.html
similarity index 100%
rename from www/code/mode/apl/index.html
rename to www/code/codemirror-5.13.2/mode/apl/index.html
diff --git a/www/code/mode/asciiarmor/asciiarmor.js b/www/code/codemirror-5.13.2/mode/asciiarmor/asciiarmor.js
similarity index 100%
rename from www/code/mode/asciiarmor/asciiarmor.js
rename to www/code/codemirror-5.13.2/mode/asciiarmor/asciiarmor.js
diff --git a/www/code/mode/asciiarmor/index.html b/www/code/codemirror-5.13.2/mode/asciiarmor/index.html
similarity index 100%
rename from www/code/mode/asciiarmor/index.html
rename to www/code/codemirror-5.13.2/mode/asciiarmor/index.html
diff --git a/www/code/mode/asn.1/asn.1.js b/www/code/codemirror-5.13.2/mode/asn.1/asn.1.js
similarity index 100%
rename from www/code/mode/asn.1/asn.1.js
rename to www/code/codemirror-5.13.2/mode/asn.1/asn.1.js
diff --git a/www/code/mode/asn.1/index.html b/www/code/codemirror-5.13.2/mode/asn.1/index.html
similarity index 98%
rename from www/code/mode/asn.1/index.html
rename to www/code/codemirror-5.13.2/mode/asn.1/index.html
index 8346f8e50..699fd4473 100644
--- a/www/code/mode/asn.1/index.html
+++ b/www/code/codemirror-5.13.2/mode/asn.1/index.html
@@ -1,4 +1,4 @@
-
+
CodeMirror: ASN.1 mode
@@ -73,6 +73,5 @@
The development of this mode has been sponsored by Ericsson
.
Coded by Asmelash Tsegay Gebretsadkan
-
diff --git a/www/code/mode/asterisk/asterisk.js b/www/code/codemirror-5.13.2/mode/asterisk/asterisk.js
similarity index 100%
rename from www/code/mode/asterisk/asterisk.js
rename to www/code/codemirror-5.13.2/mode/asterisk/asterisk.js
diff --git a/www/code/mode/asterisk/index.html b/www/code/codemirror-5.13.2/mode/asterisk/index.html
similarity index 100%
rename from www/code/mode/asterisk/index.html
rename to www/code/codemirror-5.13.2/mode/asterisk/index.html
diff --git a/www/code/mode/brainfuck/brainfuck.js b/www/code/codemirror-5.13.2/mode/brainfuck/brainfuck.js
similarity index 100%
rename from www/code/mode/brainfuck/brainfuck.js
rename to www/code/codemirror-5.13.2/mode/brainfuck/brainfuck.js
diff --git a/www/code/mode/brainfuck/index.html b/www/code/codemirror-5.13.2/mode/brainfuck/index.html
similarity index 100%
rename from www/code/mode/brainfuck/index.html
rename to www/code/codemirror-5.13.2/mode/brainfuck/index.html
diff --git a/www/code/mode/clike/clike.js b/www/code/codemirror-5.13.2/mode/clike/clike.js
similarity index 70%
rename from www/code/mode/clike/clike.js
rename to www/code/codemirror-5.13.2/mode/clike/clike.js
index 50f1ea734..34d3a5afd 100644
--- a/www/code/mode/clike/clike.js
+++ b/www/code/codemirror-5.13.2/mode/clike/clike.js
@@ -11,6 +11,42 @@
})(function(CodeMirror) {
"use strict";
+function Context(indented, column, type, align, prev) {
+ this.indented = indented;
+ this.column = column;
+ this.type = type;
+ this.align = align;
+ this.prev = prev;
+}
+function isStatement(type) {
+ return type == "statement" || type == "switchstatement" || type == "namespace";
+}
+function pushContext(state, col, type) {
+ var indent = state.indented;
+ if (state.context && isStatement(state.context.type) && !isStatement(type))
+ indent = state.context.indented;
+ return state.context = new Context(indent, col, type, null, state.context);
+}
+function popContext(state) {
+ var t = state.context.type;
+ if (t == ")" || t == "]" || t == "}")
+ state.indented = state.context.indented;
+ return state.context = state.context.prev;
+}
+
+function typeBefore(stream, state) {
+ if (state.prevToken == "variable" || state.prevToken == "variable-3") return true;
+ if (/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(stream.string.slice(0, stream.start))) return true;
+}
+
+function isTopScope(context) {
+ for (;;) {
+ if (!context || context.type == "top") return true;
+ if (context.type == "}" && context.prev.type != "namespace") return false;
+ context = context.prev;
+ }
+}
+
CodeMirror.defineMode("clike", function(config, parserConfig) {
var indentUnit = config.indentUnit,
statementIndentUnit = parserConfig.statementIndentUnit || indentUnit,
@@ -25,8 +61,12 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
multiLineStrings = parserConfig.multiLineStrings,
indentStatements = parserConfig.indentStatements !== false,
indentSwitch = parserConfig.indentSwitch !== false,
- namespaceSeparator = parserConfig.namespaceSeparator;
- var isOperatorChar = /[+\-*&%=<>!?|\/]/;
+ namespaceSeparator = parserConfig.namespaceSeparator,
+ isPunctuationChar = parserConfig.isPunctuationChar || /[\[\]{}\(\),;\:\.]/,
+ numberStart = parserConfig.numberStart || /[\d\.]/,
+ number = parserConfig.number || /^(?:0x[a-f\d]+|0b[01]+|(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)(u|ll?|l|f)?/i,
+ isOperatorChar = parserConfig.isOperatorChar || /[+\-*&%=<>!?|\/]/,
+ endStatement = parserConfig.endStatement || /^[;:,]$/;
var curPunc, isDefKeyword;
@@ -40,13 +80,14 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
}
- if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
+ if (isPunctuationChar.test(ch)) {
curPunc = ch;
return null;
}
- if (/\d/.test(ch)) {
- stream.eatWhile(/[\w\.]/);
- return "number";
+ if (numberStart.test(ch)) {
+ stream.backUp(1)
+ if (stream.match(number)) return "number"
+ stream.next()
}
if (ch == "/") {
if (stream.eat("*")) {
@@ -59,7 +100,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
}
}
if (isOperatorChar.test(ch)) {
- stream.eatWhile(isOperatorChar);
+ while (!stream.match(/^\/[\/*]/, false) && stream.eat(isOperatorChar)) {}
return "operator";
}
stream.eatWhile(/[\w\$_\xa1-\uffff]/);
@@ -67,17 +108,17 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
stream.eatWhile(/[\w\$_\xa1-\uffff]/);
var cur = stream.current();
- if (keywords.propertyIsEnumerable(cur)) {
- if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
- if (defKeywords.propertyIsEnumerable(cur)) isDefKeyword = true;
+ if (contains(keywords, cur)) {
+ if (contains(blockKeywords, cur)) curPunc = "newstatement";
+ if (contains(defKeywords, cur)) isDefKeyword = true;
return "keyword";
}
- if (types.propertyIsEnumerable(cur)) return "variable-3";
- if (builtin.propertyIsEnumerable(cur)) {
- if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
+ if (contains(types, cur)) return "variable-3";
+ if (contains(builtin, cur)) {
+ if (contains(blockKeywords, cur)) curPunc = "newstatement";
return "builtin";
}
- if (atoms.propertyIsEnumerable(cur)) return "atom";
+ if (contains(atoms, cur)) return "atom";
return "variable";
}
@@ -106,42 +147,6 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
return "comment";
}
- function Context(indented, column, type, align, prev) {
- this.indented = indented;
- this.column = column;
- this.type = type;
- this.align = align;
- this.prev = prev;
- }
- function isStatement(type) {
- return type == "statement" || type == "switchstatement" || type == "namespace";
- }
- function pushContext(state, col, type) {
- var indent = state.indented;
- if (state.context && isStatement(state.context.type) && !isStatement(type))
- indent = state.context.indented;
- return state.context = new Context(indent, col, type, null, state.context);
- }
- function popContext(state) {
- var t = state.context.type;
- if (t == ")" || t == "]" || t == "}")
- state.indented = state.context.indented;
- return state.context = state.context.prev;
- }
-
- function typeBefore(stream, state) {
- if (state.prevToken == "variable" || state.prevToken == "variable-3") return true;
- if (/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(stream.string.slice(0, stream.start))) return true;
- }
-
- function isTopScope(context) {
- for (;;) {
- if (!context || context.type == "top") return true;
- if (context.type == "}" && context.prev.type != "namespace") return false;
- context = context.prev;
- }
- }
-
// Interface
return {
@@ -168,8 +173,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
if (style == "comment" || style == "meta") return style;
if (ctx.align == null) ctx.align = true;
- if ((curPunc == ";" || curPunc == ":" || curPunc == ","))
- while (isStatement(state.context.type)) popContext(state);
+ if (endStatement.test(curPunc)) while (isStatement(state.context.type)) popContext(state);
else if (curPunc == "{") pushContext(state, stream.column(), "}");
else if (curPunc == "[") pushContext(state, stream.column(), "]");
else if (curPunc == "(") pushContext(state, stream.column(), ")");
@@ -212,8 +216,16 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass;
var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
if (isStatement(ctx.type) && firstChar == "}") ctx = ctx.prev;
+ if (hooks.indent) {
+ var hook = hooks.indent(state, ctx, textAfter);
+ if (typeof hook == "number") return hook
+ }
var closing = firstChar == ctx.type;
var switchBlock = ctx.prev && ctx.prev.type == "switchstatement";
+ if (parserConfig.allmanIndentation && /[{(]/.test(firstChar)) {
+ while (ctx.type != "top" && ctx.type != "}") ctx = ctx.prev
+ return ctx.indented
+ }
if (isStatement(ctx.type))
return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit);
if (ctx.align && (!dontAlignCalls || ctx.type != ")"))
@@ -238,27 +250,30 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
return obj;
}
+ function contains(words, word) {
+ if (typeof words === "function") {
+ return words(word);
+ } else {
+ return words.propertyIsEnumerable(word);
+ }
+ }
var cKeywords = "auto if break case register continue return default do sizeof " +
- "static else struct switch extern typedef float union for " +
- "goto while enum const volatile";
+ "static else struct switch extern typedef union for goto while enum const volatile";
var cTypes = "int long char short double float unsigned signed void size_t ptrdiff_t";
function cppHook(stream, state) {
- if (!state.startOfLine) return false;
- for (;;) {
- if (stream.skipTo("\\")) {
- stream.next();
- if (stream.eol()) {
- state.tokenize = cppHook;
- break;
- }
- } else {
- stream.skipToEnd();
- state.tokenize = null;
- break;
+ if (!state.startOfLine) return false
+ for (var ch, next = null; ch = stream.peek();) {
+ if (ch == "\\" && stream.match(/^.$/)) {
+ next = cppHook
+ break
+ } else if (ch == "/" && stream.match(/^\/[\/\*]/, false)) {
+ break
}
+ stream.next()
}
- return "meta";
+ state.tokenize = next
+ return "meta"
}
function pointerHook(_stream, state) {
@@ -413,6 +428,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
defKeywords: words("class interface package enum"),
typeFirstDefinitions: true,
atoms: words("true false null"),
+ endStatement: /^[;:]$/,
hooks: {
"@": function(stream) {
stream.eatWhile(/[\w\$_]/);
@@ -468,7 +484,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
keywords: words(
/* scala */
- "abstract case catch class def do else extends false final finally for forSome if " +
+ "abstract case catch class def do else extends final finally for forSome if " +
"implicit import lazy match new null object override package private protected return " +
"sealed super this throw trait try type val var while with yield _ : = => <- <: " +
"<% >: # @ " +
@@ -481,7 +497,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
),
types: words(
"AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either " +
- "Enumeration Equiv Error Exception Fractional Function IndexedSeq Integral Iterable " +
+ "Enumeration Equiv Error Exception Fractional Function IndexedSeq Int Integral Iterable " +
"Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering " +
"Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder " +
"StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector " +
@@ -511,6 +527,68 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
"'": function(stream) {
stream.eatWhile(/[\w\$_\xa1-\uffff]/);
return "atom";
+ },
+ "=": function(stream, state) {
+ var cx = state.context
+ if (cx.type == "}" && cx.align && stream.eat(">")) {
+ state.context = new Context(cx.indented, cx.column, cx.type, null, cx.prev)
+ return "operator"
+ } else {
+ return false
+ }
+ }
+ },
+ modeProps: {closeBrackets: {triples: '"'}}
+ });
+
+ function tokenKotlinString(tripleString){
+ return function (stream, state) {
+ var escaped = false, next, end = false;
+ while (!stream.eol()) {
+ if (!tripleString && !escaped && stream.match('"') ) {end = true; break;}
+ if (tripleString && stream.match('"""')) {end = true; break;}
+ next = stream.next();
+ if(!escaped && next == "$" && stream.match('{'))
+ stream.skipTo("}");
+ escaped = !escaped && next == "\\" && !tripleString;
+ }
+ if (end || !tripleString)
+ state.tokenize = null;
+ return "string";
+ }
+ }
+
+ def("text/x-kotlin", {
+ name: "clike",
+ keywords: words(
+ /*keywords*/
+ "package as typealias class interface this super val " +
+ "var fun for is in This throw return " +
+ "break continue object if else while do try when !in !is as? " +
+
+ /*soft keywords*/
+ "file import where by get set abstract enum open inner override private public internal " +
+ "protected catch finally out final vararg reified dynamic companion constructor init " +
+ "sealed field property receiver param sparam lateinit data inline noinline tailrec " +
+ "external annotation crossinline const operator infix"
+ ),
+ types: words(
+ /* package java.lang */
+ "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " +
+ "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " +
+ "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " +
+ "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void"
+ ),
+ intendSwitch: false,
+ indentStatements: false,
+ multiLineStrings: true,
+ blockKeywords: words("catch class do else finally for if where try while enum"),
+ defKeywords: words("class val var object package interface fun"),
+ atoms: words("true false null this"),
+ hooks: {
+ '"': function(stream, state) {
+ state.tokenize = tokenKotlinString(stream.match('""'));
+ return state.tokenize(stream, state);
}
},
modeProps: {closeBrackets: {triples: '"'}}
@@ -598,7 +676,10 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
stream.eatWhile(/[\w\$]/);
return "keyword";
},
- "#": cppHook
+ "#": cppHook,
+ indent: function(_state, ctx, textAfter) {
+ if (ctx.type == "statement" && /^@\w/.test(textAfter)) return ctx.indented
+ }
},
modeProps: {fold: "brace"}
});
@@ -616,4 +697,85 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
modeProps: {fold: ["brace", "include"]}
});
+ // Ceylon Strings need to deal with interpolation
+ var stringTokenizer = null;
+ function tokenCeylonString(type) {
+ return function(stream, state) {
+ var escaped = false, next, end = false;
+ while (!stream.eol()) {
+ if (!escaped && stream.match('"') &&
+ (type == "single" || stream.match('""'))) {
+ end = true;
+ break;
+ }
+ if (!escaped && stream.match('``')) {
+ stringTokenizer = tokenCeylonString(type);
+ end = true;
+ break;
+ }
+ next = stream.next();
+ escaped = type == "single" && !escaped && next == "\\";
+ }
+ if (end)
+ state.tokenize = null;
+ return "string";
+ }
+ }
+
+ def("text/x-ceylon", {
+ name: "clike",
+ keywords: words("abstracts alias assembly assert assign break case catch class continue dynamic else" +
+ " exists extends finally for function given if import in interface is let module new" +
+ " nonempty object of out outer package return satisfies super switch then this throw" +
+ " try value void while"),
+ types: function(word) {
+ // In Ceylon all identifiers that start with an uppercase are types
+ var first = word.charAt(0);
+ return (first === first.toUpperCase() && first !== first.toLowerCase());
+ },
+ blockKeywords: words("case catch class dynamic else finally for function if interface module new object switch try while"),
+ defKeywords: words("class dynamic function interface module object package value"),
+ builtin: words("abstract actual aliased annotation by default deprecated doc final formal late license" +
+ " native optional sealed see serializable shared suppressWarnings tagged throws variable"),
+ isPunctuationChar: /[\[\]{}\(\),;\:\.`]/,
+ isOperatorChar: /[+\-*&%=<>!?|^~:\/]/,
+ numberStart: /[\d#$]/,
+ number: /^(?:#[\da-fA-F_]+|\$[01_]+|[\d_]+[kMGTPmunpf]?|[\d_]+\.[\d_]+(?:[eE][-+]?\d+|[kMGTPmunpf]|)|)/i,
+ multiLineStrings: true,
+ typeFirstDefinitions: true,
+ atoms: words("true false null larger smaller equal empty finished"),
+ indentSwitch: false,
+ styleDefs: false,
+ hooks: {
+ "@": function(stream) {
+ stream.eatWhile(/[\w\$_]/);
+ return "meta";
+ },
+ '"': function(stream, state) {
+ state.tokenize = tokenCeylonString(stream.match('""') ? "triple" : "single");
+ return state.tokenize(stream, state);
+ },
+ '`': function(stream, state) {
+ if (!stringTokenizer || !stream.match('`')) return false;
+ state.tokenize = stringTokenizer;
+ stringTokenizer = null;
+ return state.tokenize(stream, state);
+ },
+ "'": function(stream) {
+ stream.eatWhile(/[\w\$_\xa1-\uffff]/);
+ return "atom";
+ },
+ token: function(_stream, state, style) {
+ if ((style == "variable" || style == "variable-3") &&
+ state.prevToken == ".") {
+ return "variable-2";
+ }
+ }
+ },
+ modeProps: {
+ fold: ["brace", "import"],
+ closeBrackets: {triples: '"'}
+ }
+ });
+
});
diff --git a/www/code/mode/clike/index.html b/www/code/codemirror-5.13.2/mode/clike/index.html
similarity index 65%
rename from www/code/mode/clike/index.html
rename to www/code/codemirror-5.13.2/mode/clike/index.html
index 226c6ce84..45c670ae5 100644
--- a/www/code/mode/clike/index.html
+++ b/www/code/codemirror-5.13.2/mode/clike/index.html
@@ -206,6 +206,103 @@ object FilterTest extends App {
}
+
+
+
diff --git a/www/code/codemirror-5.13.2/mode/protobuf/protobuf.js b/www/code/codemirror-5.13.2/mode/protobuf/protobuf.js
new file mode 100644
index 000000000..bcae276e8
--- /dev/null
+++ b/www/code/codemirror-5.13.2/mode/protobuf/protobuf.js
@@ -0,0 +1,68 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ function wordRegexp(words) {
+ return new RegExp("^((" + words.join(")|(") + "))\\b", "i");
+ };
+
+ var keywordArray = [
+ "package", "message", "import", "syntax",
+ "required", "optional", "repeated", "reserved", "default", "extensions", "packed",
+ "bool", "bytes", "double", "enum", "float", "string",
+ "int32", "int64", "uint32", "uint64", "sint32", "sint64", "fixed32", "fixed64", "sfixed32", "sfixed64"
+ ];
+ var keywords = wordRegexp(keywordArray);
+
+ CodeMirror.registerHelper("hintWords", "protobuf", keywordArray);
+
+ var identifiers = new RegExp("^[_A-Za-z\xa1-\uffff][_A-Za-z0-9\xa1-\uffff]*");
+
+ function tokenBase(stream) {
+ // whitespaces
+ if (stream.eatSpace()) return null;
+
+ // Handle one line Comments
+ if (stream.match("//")) {
+ stream.skipToEnd();
+ return "comment";
+ }
+
+ // Handle Number Literals
+ if (stream.match(/^[0-9\.+-]/, false)) {
+ if (stream.match(/^[+-]?0x[0-9a-fA-F]+/))
+ return "number";
+ if (stream.match(/^[+-]?\d*\.\d+([EeDd][+-]?\d+)?/))
+ return "number";
+ if (stream.match(/^[+-]?\d+([EeDd][+-]?\d+)?/))
+ return "number";
+ }
+
+ // Handle Strings
+ if (stream.match(/^"([^"]|(""))*"/)) { return "string"; }
+ if (stream.match(/^'([^']|(''))*'/)) { return "string"; }
+
+ // Handle words
+ if (stream.match(keywords)) { return "keyword"; }
+ if (stream.match(identifiers)) { return "variable"; } ;
+
+ // Handle non-detected items
+ stream.next();
+ return null;
+ };
+
+ CodeMirror.defineMode("protobuf", function() {
+ return {token: tokenBase};
+ });
+
+ CodeMirror.defineMIME("text/x-protobuf", "protobuf");
+});
diff --git a/www/code/mode/puppet/index.html b/www/code/codemirror-5.13.2/mode/puppet/index.html
similarity index 100%
rename from www/code/mode/puppet/index.html
rename to www/code/codemirror-5.13.2/mode/puppet/index.html
diff --git a/www/code/mode/puppet/puppet.js b/www/code/codemirror-5.13.2/mode/puppet/puppet.js
similarity index 100%
rename from www/code/mode/puppet/puppet.js
rename to www/code/codemirror-5.13.2/mode/puppet/puppet.js
diff --git a/www/code/mode/python/index.html b/www/code/codemirror-5.13.2/mode/python/index.html
similarity index 100%
rename from www/code/mode/python/index.html
rename to www/code/codemirror-5.13.2/mode/python/index.html
diff --git a/www/code/mode/python/python.js b/www/code/codemirror-5.13.2/mode/python/python.js
similarity index 78%
rename from www/code/mode/python/python.js
rename to www/code/codemirror-5.13.2/mode/python/python.js
index e5a09719b..196b026c6 100644
--- a/www/code/mode/python/python.js
+++ b/www/code/codemirror-5.13.2/mode/python/python.js
@@ -48,29 +48,29 @@
CodeMirror.defineMode("python", function(conf, parserConf) {
var ERRORCLASS = "error";
- var singleDelimiters = parserConf.singleDelimiters || new RegExp("^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]");
- var doubleOperators = parserConf.doubleOperators || new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))");
- var doubleDelimiters = parserConf.doubleDelimiters || new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
- var tripleDelimiters = parserConf.tripleDelimiters || new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))");
+ var singleDelimiters = parserConf.singleDelimiters || /^[\(\)\[\]\{\}@,:`=;\.]/;
+ var doubleOperators = parserConf.doubleOperators || /^([!<>]==|<>|<<|>>|\/\/|\*\*)/;
+ var doubleDelimiters = parserConf.doubleDelimiters || /^(\+=|\-=|\*=|%=|\/=|&=|\|=|\^=)/;
+ var tripleDelimiters = parserConf.tripleDelimiters || /^(\/\/=|>>=|<<=|\*\*=)/;
- if (parserConf.version && parseInt(parserConf.version, 10) == 3){
+ if (parserConf.version && parseInt(parserConf.version, 10) == 3) {
// since http://legacy.python.org/dev/peps/pep-0465/ @ is also an operator
- var singleOperators = parserConf.singleOperators || new RegExp("^[\\+\\-\\*/%&|\\^~<>!@]");
- var identifiers = parserConf.identifiers|| new RegExp("^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*");
+ var singleOperators = parserConf.singleOperators || /^[\+\-\*\/%&|\^~<>!@]/;
+ var identifiers = parserConf.identifiers|| /^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*/;
} else {
- var singleOperators = parserConf.singleOperators || new RegExp("^[\\+\\-\\*/%&|\\^~<>!]");
- var identifiers = parserConf.identifiers|| new RegExp("^[_A-Za-z][_A-Za-z0-9]*");
+ var singleOperators = parserConf.singleOperators || /^[\+\-\*\/%&|\^~<>!]/;
+ var identifiers = parserConf.identifiers|| /^[_A-Za-z][_A-Za-z0-9]*/;
}
var hangingIndent = parserConf.hangingIndent || conf.indentUnit;
var myKeywords = commonKeywords, myBuiltins = commonBuiltins;
- if(parserConf.extra_keywords != undefined){
+ if (parserConf.extra_keywords != undefined)
myKeywords = myKeywords.concat(parserConf.extra_keywords);
- }
- if(parserConf.extra_builtins != undefined){
+
+ if (parserConf.extra_builtins != undefined)
myBuiltins = myBuiltins.concat(parserConf.extra_builtins);
- }
+
if (parserConf.version && parseInt(parserConf.version, 10) == 3) {
myKeywords = myKeywords.concat(py3.keywords);
myBuiltins = myBuiltins.concat(py3.builtins);
@@ -85,13 +85,14 @@
// tokenizers
function tokenBase(stream, state) {
+ if (stream.sol()) state.indent = stream.indentation()
// Handle scope changes
if (stream.sol() && top(state).type == "py") {
var scopeOffset = top(state).offset;
if (stream.eatSpace()) {
var lineOffset = stream.indentation();
if (lineOffset > scopeOffset)
- pushScope(stream, state, "py");
+ pushPyScope(state);
else if (lineOffset < scopeOffset && dedent(stream, state))
state.errorToken = true;
return null;
@@ -160,13 +161,16 @@
// Handle operators and Delimiters
if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters))
- return null;
+ return "punctuation";
if (stream.match(doubleOperators) || stream.match(singleOperators))
return "operator";
if (stream.match(singleDelimiters))
- return null;
+ return "punctuation";
+
+ if (state.lastToken == "." && stream.match(identifiers))
+ return "property";
if (stream.match(keywords) || stream.match(wordOperators))
return "keyword";
@@ -221,16 +225,18 @@
return tokenString;
}
- function pushScope(stream, state, type) {
- var offset = 0, align = null;
- if (type == "py") {
- while (top(state).type != "py")
- state.scopes.pop();
- }
- offset = top(state).offset + (type == "py" ? conf.indentUnit : hangingIndent);
- if (type != "py" && !stream.match(/^(\s|#.*)*$/, false))
- align = stream.column() + 1;
- state.scopes.push({offset: offset, type: type, align: align});
+ function pushPyScope(state) {
+ while (top(state).type != "py") state.scopes.pop()
+ state.scopes.push({offset: top(state).offset + conf.indentUnit,
+ type: "py",
+ align: null})
+ }
+
+ function pushBracketScope(stream, state, type) {
+ var align = stream.match(/^([\s\[\{\(]|#.*)*$/, false) ? null : stream.column() + 1
+ state.scopes.push({offset: state.indent + hangingIndent,
+ type: type,
+ align: align})
}
function dedent(stream, state) {
@@ -246,28 +252,16 @@
var style = state.tokenize(stream, state);
var current = stream.current();
- // Handle '.' connected identifiers
- if (current == ".") {
- style = stream.match(identifiers, false) ? null : ERRORCLASS;
- if (style == null && state.lastStyle == "meta") {
- // Apply 'meta' style to '.' connected identifiers when
- // appropriate.
- style = "meta";
- }
- return style;
- }
-
// Handle decorators
- if (current == "@"){
- if(parserConf.version && parseInt(parserConf.version, 10) == 3){
- return stream.match(identifiers, false) ? "meta" : "operator";
- } else {
- return stream.match(identifiers, false) ? "meta" : ERRORCLASS;
- }
+ if (current == "@") {
+ if (parserConf.version && parseInt(parserConf.version, 10) == 3)
+ return stream.match(identifiers, false) ? "meta" : "operator";
+ else
+ return stream.match(identifiers, false) ? "meta" : ERRORCLASS;
}
if ((style == "variable" || style == "builtin")
- && state.lastStyle == "meta")
+ && state.lastToken == "meta")
style = "meta";
// Handle scope changes.
@@ -276,15 +270,15 @@
if (current == "lambda") state.lambda = true;
if (current == ":" && !state.lambda && top(state).type == "py")
- pushScope(stream, state, "py");
+ pushPyScope(state);
var delimiter_index = current.length == 1 ? "[({".indexOf(current) : -1;
if (delimiter_index != -1)
- pushScope(stream, state, "])}".slice(delimiter_index, delimiter_index+1));
+ pushBracketScope(stream, state, "])}".slice(delimiter_index, delimiter_index+1));
delimiter_index = "])}".indexOf(current);
if (delimiter_index != -1) {
- if (top(state).type == current) state.scopes.pop();
+ if (top(state).type == current) state.indent = state.scopes.pop().offset - hangingIndent
else return ERRORCLASS;
}
if (state.dedent > 0 && stream.eol() && top(state).type == "py") {
@@ -300,7 +294,7 @@
return {
tokenize: tokenBase,
scopes: [{offset: basecolumn || 0, type: "py", align: null}],
- lastStyle: null,
+ indent: basecolumn || 0,
lastToken: null,
lambda: false,
dedent: 0
@@ -312,11 +306,9 @@
if (addErr) state.errorToken = false;
var style = tokenLexer(stream, state);
- state.lastStyle = style;
-
- var current = stream.current();
- if (current && style)
- state.lastToken = current;
+ if (style && style != "comment")
+ state.lastToken = (style == "keyword" || style == "punctuation") ? stream.current() : style;
+ if (style == "punctuation") style = null;
if (stream.eol() && state.lambda)
state.lambda = false;
@@ -327,16 +319,14 @@
if (state.tokenize != tokenBase)
return state.tokenize.isString ? CodeMirror.Pass : 0;
- var scope = top(state);
- var closing = textAfter && textAfter.charAt(0) == scope.type;
+ var scope = top(state), closing = scope.type == textAfter.charAt(0)
if (scope.align != null)
- return scope.align - (closing ? 1 : 0);
- else if (closing && state.scopes.length > 1)
- return state.scopes[state.scopes.length - 2].offset;
+ return scope.align - (closing ? 1 : 0)
else
- return scope.offset;
+ return scope.offset - (closing ? hangingIndent : 0)
},
+ electricInput: /^\s*[\}\]\)]$/,
closeBrackets: {triples: "'\""},
lineComment: "#",
fold: "indent"
diff --git a/www/code/mode/q/index.html b/www/code/codemirror-5.13.2/mode/q/index.html
similarity index 100%
rename from www/code/mode/q/index.html
rename to www/code/codemirror-5.13.2/mode/q/index.html
diff --git a/www/code/mode/q/q.js b/www/code/codemirror-5.13.2/mode/q/q.js
similarity index 100%
rename from www/code/mode/q/q.js
rename to www/code/codemirror-5.13.2/mode/q/q.js
diff --git a/www/code/mode/r/index.html b/www/code/codemirror-5.13.2/mode/r/index.html
similarity index 100%
rename from www/code/mode/r/index.html
rename to www/code/codemirror-5.13.2/mode/r/index.html
diff --git a/www/code/mode/r/r.js b/www/code/codemirror-5.13.2/mode/r/r.js
similarity index 99%
rename from www/code/mode/r/r.js
rename to www/code/codemirror-5.13.2/mode/r/r.js
index 1ab4a9565..d41d1c54c 100644
--- a/www/code/mode/r/r.js
+++ b/www/code/codemirror-5.13.2/mode/r/r.js
@@ -11,6 +11,8 @@
})(function(CodeMirror) {
"use strict";
+CodeMirror.registerHelper("wordChars", "r", /[\w.]/);
+
CodeMirror.defineMode("r", function(config) {
function wordObj(str) {
var words = str.split(" "), res = {};
diff --git a/www/code/mode/rpm/changes/index.html b/www/code/codemirror-5.13.2/mode/rpm/changes/index.html
similarity index 100%
rename from www/code/mode/rpm/changes/index.html
rename to www/code/codemirror-5.13.2/mode/rpm/changes/index.html
diff --git a/www/code/mode/rpm/index.html b/www/code/codemirror-5.13.2/mode/rpm/index.html
similarity index 100%
rename from www/code/mode/rpm/index.html
rename to www/code/codemirror-5.13.2/mode/rpm/index.html
diff --git a/www/code/mode/rpm/rpm.js b/www/code/codemirror-5.13.2/mode/rpm/rpm.js
similarity index 80%
rename from www/code/mode/rpm/rpm.js
rename to www/code/codemirror-5.13.2/mode/rpm/rpm.js
index 3bb7cd2f6..87cde591a 100644
--- a/www/code/mode/rpm/rpm.js
+++ b/www/code/codemirror-5.13.2/mode/rpm/rpm.js
@@ -34,10 +34,10 @@ CodeMirror.defineMIME("text/x-rpm-changes", "rpm-changes");
// Quick and dirty spec file highlighting
CodeMirror.defineMode("rpm-spec", function() {
- var arch = /^(i386|i586|i686|x86_64|ppc64|ppc|ia64|s390x|s390|sparc64|sparcv9|sparc|noarch|alphaev6|alpha|hppa|mipsel)/;
+ var arch = /^(i386|i586|i686|x86_64|ppc64le|ppc64|ppc|ia64|s390x|s390|sparc64|sparcv9|sparc|noarch|alphaev6|alpha|hppa|mipsel)/;
- var preamble = /^(Name|Version|Release|License|Summary|Url|Group|Source|BuildArch|BuildRequires|BuildRoot|AutoReqProv|Provides|Requires(\(\w+\))?|Obsoletes|Conflicts|Recommends|Source\d*|Patch\d*|ExclusiveArch|NoSource|Supplements):/;
- var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preinstall|preun|postinstall|postun|pre|post|triggerin|triggerun|pretrans|posttrans|verifyscript|check|triggerpostun|triggerprein|trigger)/;
+ var preamble = /^[a-zA-Z0-9()]+:/;
+ var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preinstall|preun|postinstall|postun|pretrans|posttrans|pre|post|triggerin|triggerun|verifyscript|check|triggerpostun|triggerprein|trigger)/;
var control_flow_complex = /^%(ifnarch|ifarch|if)/; // rpm control flow macros
var control_flow_simple = /^%(else|endif)/; // rpm control flow macros
var operators = /^(\!|\?|\<\=|\<|\>\=|\>|\=\=|\&\&|\|\|)/; // operators in control flow macros
@@ -55,8 +55,8 @@ CodeMirror.defineMode("rpm-spec", function() {
if (ch == "#") { stream.skipToEnd(); return "comment"; }
if (stream.sol()) {
- if (stream.match(preamble)) { return "preamble"; }
- if (stream.match(section)) { return "section"; }
+ if (stream.match(preamble)) { return "header"; }
+ if (stream.match(section)) { return "atom"; }
}
if (stream.match(/^\$\w+/)) { return "def"; } // Variables like '$RPM_BUILD_ROOT'
@@ -73,21 +73,29 @@ CodeMirror.defineMode("rpm-spec", function() {
if (stream.eol()) { state.controlFlow = false; }
}
- if (stream.match(arch)) { return "number"; }
+ if (stream.match(arch)) {
+ if (stream.eol()) { state.controlFlow = false; }
+ return "number";
+ }
// Macros like '%make_install' or '%attr(0775,root,root)'
if (stream.match(/^%[\w]+/)) {
if (stream.match(/^\(/)) { state.macroParameters = true; }
- return "macro";
+ return "keyword";
}
if (state.macroParameters) {
if (stream.match(/^\d+/)) { return "number";}
if (stream.match(/^\)/)) {
state.macroParameters = false;
- return "macro";
+ return "keyword";
}
}
- if (stream.match(/^%\{\??[\w \-]+\}/)) { return "macro"; } // Macros like '%{defined fedora}'
+
+ // Macros like '%{defined fedora}'
+ if (stream.match(/^%\{\??[\w \-\:\!]+\}/)) {
+ if (stream.eol()) { state.controlFlow = false; }
+ return "def";
+ }
//TODO: Include bash script sub-parser (CodeMirror supports that)
stream.next();
diff --git a/www/code/mode/rst/index.html b/www/code/codemirror-5.13.2/mode/rst/index.html
similarity index 100%
rename from www/code/mode/rst/index.html
rename to www/code/codemirror-5.13.2/mode/rst/index.html
diff --git a/www/code/mode/rst/rst.js b/www/code/codemirror-5.13.2/mode/rst/rst.js
similarity index 100%
rename from www/code/mode/rst/rst.js
rename to www/code/codemirror-5.13.2/mode/rst/rst.js
diff --git a/www/code/mode/ruby/index.html b/www/code/codemirror-5.13.2/mode/ruby/index.html
similarity index 100%
rename from www/code/mode/ruby/index.html
rename to www/code/codemirror-5.13.2/mode/ruby/index.html
diff --git a/www/code/mode/ruby/ruby.js b/www/code/codemirror-5.13.2/mode/ruby/ruby.js
similarity index 99%
rename from www/code/mode/ruby/ruby.js
rename to www/code/codemirror-5.13.2/mode/ruby/ruby.js
index 089a971d4..10cad8d9f 100644
--- a/www/code/mode/ruby/ruby.js
+++ b/www/code/codemirror-5.13.2/mode/ruby/ruby.js
@@ -275,7 +275,7 @@ CodeMirror.defineMode("ruby", function(config) {
(state.continuedLine ? config.indentUnit : 0);
},
- electricChars: "}de", // enD and rescuE
+ electricInput: /^\s*(?:end|rescue|\})$/,
lineComment: "#"
};
});
diff --git a/www/code/mode/ruby/test.js b/www/code/codemirror-5.13.2/mode/ruby/test.js
similarity index 100%
rename from www/code/mode/ruby/test.js
rename to www/code/codemirror-5.13.2/mode/ruby/test.js
diff --git a/www/code/mode/rust/index.html b/www/code/codemirror-5.13.2/mode/rust/index.html
similarity index 100%
rename from www/code/mode/rust/index.html
rename to www/code/codemirror-5.13.2/mode/rust/index.html
diff --git a/www/code/mode/rust/rust.js b/www/code/codemirror-5.13.2/mode/rust/rust.js
similarity index 82%
rename from www/code/mode/rust/rust.js
rename to www/code/codemirror-5.13.2/mode/rust/rust.js
index 1ce0c01ee..8558b53fe 100644
--- a/www/code/mode/rust/rust.js
+++ b/www/code/codemirror-5.13.2/mode/rust/rust.js
@@ -12,11 +12,12 @@
"use strict";
CodeMirror.defineSimpleMode("rust",{
- start:[
+ start: [
// string and byte string
- {regex: /b?"(?:[^\\]|\\.)*?"/, token: "string"},
+ {regex: /b?"/, token: "string", next: "string"},
// raw string and raw byte string
- {regex: /(b?r)(#*)(".*?)("\2)/, token: ["string", "string", "string", "string"]},
+ {regex: /b?r"/, token: "string", next: "string_raw"},
+ {regex: /b?r#+"/, token: "string", next: "string_raw_hash"},
// character
{regex: /'(?:[^'\\]|\\(?:[nrt0'"]|x[\da-fA-F]{2}|u\{[\da-fA-F]{6}\}))'/, token: "string-2"},
// byte
@@ -39,6 +40,18 @@ CodeMirror.defineSimpleMode("rust",{
{regex: /[\{\[\(]/, indent: true},
{regex: /[\}\]\)]/, dedent: true}
],
+ string: [
+ {regex: /"/, token: "string", next: "start"},
+ {regex: /(?:[^\\"]|\\(?:.|$))*/, token: "string"}
+ ],
+ string_raw: [
+ {regex: /"/, token: "string", next: "start"},
+ {regex: /[^"]*/, token: "string"}
+ ],
+ string_raw_hash: [
+ {regex: /"#+/, token: "string", next: "start"},
+ {regex: /(?:[^"]|"(?!#))*/, token: "string"}
+ ],
comment: [
{regex: /.*?\*\//, token: "comment", next: "start"},
{regex: /.*/, token: "comment"}
diff --git a/www/code/mode/rust/test.js b/www/code/codemirror-5.13.2/mode/rust/test.js
similarity index 96%
rename from www/code/mode/rust/test.js
rename to www/code/codemirror-5.13.2/mode/rust/test.js
index 1a3c6e720..eb256c47e 100644
--- a/www/code/mode/rust/test.js
+++ b/www/code/codemirror-5.13.2/mode/rust/test.js
@@ -26,7 +26,6 @@
'[string "\\"foo\\""]',
'[string r#""foo""#]',
'[string "foo #\\"# bar"]',
- '[string r##"foo #"# bar"##]',
'[string b"foo"]',
'[string br"foo"]',
diff --git a/www/code/mode/sass/index.html b/www/code/codemirror-5.13.2/mode/sass/index.html
similarity index 100%
rename from www/code/mode/sass/index.html
rename to www/code/codemirror-5.13.2/mode/sass/index.html
diff --git a/www/code/mode/sass/sass.js b/www/code/codemirror-5.13.2/mode/sass/sass.js
similarity index 100%
rename from www/code/mode/sass/sass.js
rename to www/code/codemirror-5.13.2/mode/sass/sass.js
diff --git a/www/code/mode/scheme/index.html b/www/code/codemirror-5.13.2/mode/scheme/index.html
similarity index 100%
rename from www/code/mode/scheme/index.html
rename to www/code/codemirror-5.13.2/mode/scheme/index.html
diff --git a/www/code/mode/scheme/scheme.js b/www/code/codemirror-5.13.2/mode/scheme/scheme.js
similarity index 100%
rename from www/code/mode/scheme/scheme.js
rename to www/code/codemirror-5.13.2/mode/scheme/scheme.js
diff --git a/www/code/mode/shell/index.html b/www/code/codemirror-5.13.2/mode/shell/index.html
similarity index 100%
rename from www/code/mode/shell/index.html
rename to www/code/codemirror-5.13.2/mode/shell/index.html
diff --git a/www/code/mode/shell/shell.js b/www/code/codemirror-5.13.2/mode/shell/shell.js
similarity index 100%
rename from www/code/mode/shell/shell.js
rename to www/code/codemirror-5.13.2/mode/shell/shell.js
diff --git a/www/code/mode/shell/test.js b/www/code/codemirror-5.13.2/mode/shell/test.js
similarity index 100%
rename from www/code/mode/shell/test.js
rename to www/code/codemirror-5.13.2/mode/shell/test.js
diff --git a/www/code/mode/sieve/index.html b/www/code/codemirror-5.13.2/mode/sieve/index.html
similarity index 100%
rename from www/code/mode/sieve/index.html
rename to www/code/codemirror-5.13.2/mode/sieve/index.html
diff --git a/www/code/mode/sieve/sieve.js b/www/code/codemirror-5.13.2/mode/sieve/sieve.js
similarity index 100%
rename from www/code/mode/sieve/sieve.js
rename to www/code/codemirror-5.13.2/mode/sieve/sieve.js
diff --git a/www/code/mode/slim/index.html b/www/code/codemirror-5.13.2/mode/slim/index.html
similarity index 100%
rename from www/code/mode/slim/index.html
rename to www/code/codemirror-5.13.2/mode/slim/index.html
diff --git a/www/code/mode/slim/slim.js b/www/code/codemirror-5.13.2/mode/slim/slim.js
similarity index 100%
rename from www/code/mode/slim/slim.js
rename to www/code/codemirror-5.13.2/mode/slim/slim.js
diff --git a/www/code/mode/slim/test.js b/www/code/codemirror-5.13.2/mode/slim/test.js
similarity index 100%
rename from www/code/mode/slim/test.js
rename to www/code/codemirror-5.13.2/mode/slim/test.js
diff --git a/www/code/mode/smalltalk/index.html b/www/code/codemirror-5.13.2/mode/smalltalk/index.html
similarity index 100%
rename from www/code/mode/smalltalk/index.html
rename to www/code/codemirror-5.13.2/mode/smalltalk/index.html
diff --git a/www/code/mode/smalltalk/smalltalk.js b/www/code/codemirror-5.13.2/mode/smalltalk/smalltalk.js
similarity index 100%
rename from www/code/mode/smalltalk/smalltalk.js
rename to www/code/codemirror-5.13.2/mode/smalltalk/smalltalk.js
diff --git a/www/code/mode/smarty/index.html b/www/code/codemirror-5.13.2/mode/smarty/index.html
similarity index 100%
rename from www/code/mode/smarty/index.html
rename to www/code/codemirror-5.13.2/mode/smarty/index.html
diff --git a/www/code/mode/smarty/smarty.js b/www/code/codemirror-5.13.2/mode/smarty/smarty.js
similarity index 100%
rename from www/code/mode/smarty/smarty.js
rename to www/code/codemirror-5.13.2/mode/smarty/smarty.js
diff --git a/www/code/mode/solr/index.html b/www/code/codemirror-5.13.2/mode/solr/index.html
similarity index 100%
rename from www/code/mode/solr/index.html
rename to www/code/codemirror-5.13.2/mode/solr/index.html
diff --git a/www/code/mode/solr/solr.js b/www/code/codemirror-5.13.2/mode/solr/solr.js
similarity index 100%
rename from www/code/mode/solr/solr.js
rename to www/code/codemirror-5.13.2/mode/solr/solr.js
diff --git a/www/code/mode/soy/index.html b/www/code/codemirror-5.13.2/mode/soy/index.html
similarity index 100%
rename from www/code/mode/soy/index.html
rename to www/code/codemirror-5.13.2/mode/soy/index.html
diff --git a/www/code/mode/soy/soy.js b/www/code/codemirror-5.13.2/mode/soy/soy.js
similarity index 100%
rename from www/code/mode/soy/soy.js
rename to www/code/codemirror-5.13.2/mode/soy/soy.js
diff --git a/www/code/mode/sparql/index.html b/www/code/codemirror-5.13.2/mode/sparql/index.html
similarity index 100%
rename from www/code/mode/sparql/index.html
rename to www/code/codemirror-5.13.2/mode/sparql/index.html
diff --git a/www/code/mode/sparql/sparql.js b/www/code/codemirror-5.13.2/mode/sparql/sparql.js
similarity index 96%
rename from www/code/mode/sparql/sparql.js
rename to www/code/codemirror-5.13.2/mode/sparql/sparql.js
index bbf8a76a0..90b08e18f 100644
--- a/www/code/mode/sparql/sparql.js
+++ b/www/code/codemirror-5.13.2/mode/sparql/sparql.js
@@ -135,7 +135,11 @@ CodeMirror.defineMode("sparql", function(config) {
else if (curPunc == "{") pushContext(state, "}", stream.column());
else if (/[\]\}\)]/.test(curPunc)) {
while (state.context && state.context.type == "pattern") popContext(state);
- if (state.context && curPunc == state.context.type) popContext(state);
+ if (state.context && curPunc == state.context.type) {
+ popContext(state);
+ if (curPunc == "}" && state.context && state.context.type == "pattern")
+ popContext(state);
+ }
}
else if (curPunc == "." && state.context && state.context.type == "pattern") popContext(state);
else if (/atom|string|variable/.test(style) && state.context) {
@@ -165,7 +169,9 @@ CodeMirror.defineMode("sparql", function(config) {
return context.col + (closing ? 0 : 1);
else
return context.indent + (closing ? 0 : indentUnit);
- }
+ },
+
+ lineComment: "#"
};
});
diff --git a/www/code/mode/spreadsheet/index.html b/www/code/codemirror-5.13.2/mode/spreadsheet/index.html
similarity index 100%
rename from www/code/mode/spreadsheet/index.html
rename to www/code/codemirror-5.13.2/mode/spreadsheet/index.html
diff --git a/www/code/mode/spreadsheet/spreadsheet.js b/www/code/codemirror-5.13.2/mode/spreadsheet/spreadsheet.js
similarity index 97%
rename from www/code/mode/spreadsheet/spreadsheet.js
rename to www/code/codemirror-5.13.2/mode/spreadsheet/spreadsheet.js
index 6fab00fdd..222f29766 100644
--- a/www/code/mode/spreadsheet/spreadsheet.js
+++ b/www/code/codemirror-5.13.2/mode/spreadsheet/spreadsheet.js
@@ -70,7 +70,10 @@
return "operator";
case "\\":
if (stream.match(/\\[a-z]+/)) return "string-2";
- else return null;
+ else {
+ stream.next();
+ return "atom";
+ }
case ".":
case ",":
case ";":
diff --git a/www/code/mode/sql/index.html b/www/code/codemirror-5.13.2/mode/sql/index.html
similarity index 97%
rename from www/code/mode/sql/index.html
rename to www/code/codemirror-5.13.2/mode/sql/index.html
index a0d8d9e1b..7ea4b6934 100644
--- a/www/code/mode/sql/index.html
+++ b/www/code/codemirror-5.13.2/mode/sql/index.html
@@ -57,6 +57,7 @@ SELECT SQL_NO_CACHE DISTINCT
text/x-plsql,
text/x-mssql,
text/x-hive.
+ text/x-pgsql.
-
The XML mode supports two configuration parameters:
+
The XML mode supports these configuration parameters:
htmlMode (boolean)
This switches the mode to parse HTML instead of XML. This
means attributes do not have to be quoted, and some elements
(such as br) do not require a closing tag.
+
matchClosing (boolean)
+
Controls whether the mode checks that close tags match the
+ corresponding opening tag, and highlights mismatches as errors.
+ Defaults to true.
alignCDATA (boolean)
Setting this to true will force the opening tag of CDATA
blocks to not be indented.
Defines a mode that parses
+a YAML frontmatter
+at the start of a file, switching to a base mode at the end of that.
+Takes a mode configuration option base to configure the
+base mode, which defaults to "gfm".
A limited set of programmatic sanity tests for CodeMirror.
+
+
+
Ran 0 of 0 tests
+
+
Please enable JavaScript...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/www/code/codemirror-5.13.2/test/lint.js b/www/code/codemirror-5.13.2/test/lint.js
new file mode 100644
index 000000000..35777562e
--- /dev/null
+++ b/www/code/codemirror-5.13.2/test/lint.js
@@ -0,0 +1,11 @@
+var blint = require("blint");
+
+["mode", "lib", "addon", "keymap"].forEach(function(dir) {
+ blint.checkDir(dir, {
+ browser: true,
+ allowedGlobals: ["CodeMirror", "define", "test", "requirejs"],
+ blob: "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http:\/\/codemirror.net\/LICENSE\n\n"
+ });
+});
+
+module.exports = {ok: blint.success()};
diff --git a/www/code/codemirror-5.13.2/test/mode_test.css b/www/code/codemirror-5.13.2/test/mode_test.css
new file mode 100644
index 000000000..f83271b4e
--- /dev/null
+++ b/www/code/codemirror-5.13.2/test/mode_test.css
@@ -0,0 +1,23 @@
+.mt-output .mt-token {
+ border: 1px solid #ddd;
+ white-space: pre;
+ font-family: "Consolas", monospace;
+ text-align: center;
+}
+
+.mt-output .mt-style {
+ font-size: x-small;
+}
+
+.mt-output .mt-state {
+ font-size: x-small;
+ vertical-align: top;
+}
+
+.mt-output .mt-state-row {
+ display: none;
+}
+
+.mt-state-unhide .mt-output .mt-state-row {
+ display: table-row;
+}
diff --git a/www/code/codemirror-5.13.2/test/mode_test.js b/www/code/codemirror-5.13.2/test/mode_test.js
new file mode 100644
index 000000000..0aed50f7d
--- /dev/null
+++ b/www/code/codemirror-5.13.2/test/mode_test.js
@@ -0,0 +1,192 @@
+/**
+ * Helper to test CodeMirror highlighting modes. It pretty prints output of the
+ * highlighter and can check against expected styles.
+ *
+ * Mode tests are registered by calling test.mode(testName, mode,
+ * tokens), where mode is a mode object as returned by
+ * CodeMirror.getMode, and tokens is an array of lines that make up
+ * the test.
+ *
+ * These lines are strings, in which styled stretches of code are
+ * enclosed in brackets `[]`, and prefixed by their style. For
+ * example, `[keyword if]`. Brackets in the code itself must be
+ * duplicated to prevent them from being interpreted as token
+ * boundaries. For example `a[[i]]` for `a[i]`. If a token has
+ * multiple styles, the styles must be separated by ampersands, for
+ * example `[tag&error ]`.
+ *
+ * See the test.js files in the css, markdown, gfm, and stex mode
+ * directories for examples.
+ */
+(function() {
+ function findSingle(str, pos, ch) {
+ for (;;) {
+ var found = str.indexOf(ch, pos);
+ if (found == -1) return null;
+ if (str.charAt(found + 1) != ch) return found;
+ pos = found + 2;
+ }
+ }
+
+ var styleName = /[\w&-_]+/g;
+ function parseTokens(strs) {
+ var tokens = [], plain = "";
+ for (var i = 0; i < strs.length; ++i) {
+ if (i) plain += "\n";
+ var str = strs[i], pos = 0;
+ while (pos < str.length) {
+ var style = null, text;
+ if (str.charAt(pos) == "[" && str.charAt(pos+1) != "[") {
+ styleName.lastIndex = pos + 1;
+ var m = styleName.exec(str);
+ style = m[0].replace(/&/g, " ");
+ var textStart = pos + style.length + 2;
+ var end = findSingle(str, textStart, "]");
+ if (end == null) throw new Error("Unterminated token at " + pos + " in '" + str + "'" + style);
+ text = str.slice(textStart, end);
+ pos = end + 1;
+ } else {
+ var end = findSingle(str, pos, "[");
+ if (end == null) end = str.length;
+ text = str.slice(pos, end);
+ pos = end;
+ }
+ text = text.replace(/\[\[|\]\]/g, function(s) {return s.charAt(0);});
+ tokens.push({style: style, text: text});
+ plain += text;
+ }
+ }
+ return {tokens: tokens, plain: plain};
+ }
+
+ test.mode = function(name, mode, tokens, modeName) {
+ var data = parseTokens(tokens);
+ return test((modeName || mode.name) + "_" + name, function() {
+ return compare(data.plain, data.tokens, mode);
+ });
+ };
+
+ function esc(str) {
+ return str.replace('&', '&').replace('<', '<').replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
+;
+ }
+
+ function compare(text, expected, mode) {
+
+ var expectedOutput = [];
+ for (var i = 0; i < expected.length; ++i) {
+ var sty = expected[i].style;
+ if (sty && sty.indexOf(" ")) sty = sty.split(' ').sort().join(' ');
+ expectedOutput.push({style: sty, text: expected[i].text});
+ }
+
+ var observedOutput = highlight(text, mode);
+
+ var s = "";
+ var diff = highlightOutputsDifferent(expectedOutput, observedOutput);
+ if (diff != null) {
+ s += '
';
+ s += '
' + esc(text) + '
';
+ s += '
';
+ s += 'expected:';
+ s += prettyPrintOutputTable(expectedOutput, diff);
+ s += 'observed: [display states]';
+ s += prettyPrintOutputTable(observedOutput, diff);
+ s += '
';
+ s += '
';
+ }
+ if (observedOutput.indentFailures) {
+ for (var i = 0; i < observedOutput.indentFailures.length; i++)
+ s += "
" + esc(observedOutput.indentFailures[i]) + "
";
+ }
+ if (s) throw new Failure(s);
+ }
+
+ function stringify(obj) {
+ function replacer(key, obj) {
+ if (typeof obj == "function") {
+ var m = obj.toString().match(/function\s*[^\s(]*/);
+ return m ? m[0] : "function";
+ }
+ return obj;
+ }
+ if (window.JSON && JSON.stringify)
+ return JSON.stringify(obj, replacer, 2);
+ return "[unsupported]"; // Fail safely if no native JSON.
+ }
+
+ function highlight(string, mode) {
+ var state = mode.startState();
+
+ var lines = string.replace(/\r\n/g,'\n').split('\n');
+ var st = [], pos = 0;
+ for (var i = 0; i < lines.length; ++i) {
+ var line = lines[i], newLine = true;
+ if (mode.indent) {
+ var ws = line.match(/^\s*/)[0];
+ var indent = mode.indent(state, line.slice(ws.length));
+ if (indent != CodeMirror.Pass && indent != ws.length)
+ (st.indentFailures || (st.indentFailures = [])).push(
+ "Indentation of line " + (i + 1) + " is " + indent + " (expected " + ws.length + ")");
+ }
+ var stream = new CodeMirror.StringStream(line);
+ if (line == "" && mode.blankLine) mode.blankLine(state);
+ /* Start copied code from CodeMirror.highlight */
+ while (!stream.eol()) {
+ for (var j = 0; j < 10 && stream.start >= stream.pos; j++)
+ var compare = mode.token(stream, state);
+ if (j == 10)
+ throw new Failure("Failed to advance the stream." + stream.string + " " + stream.pos);
+ var substr = stream.current();
+ if (compare && compare.indexOf(" ") > -1) compare = compare.split(' ').sort().join(' ');
+ stream.start = stream.pos;
+ if (pos && st[pos-1].style == compare && !newLine) {
+ st[pos-1].text += substr;
+ } else if (substr) {
+ st[pos++] = {style: compare, text: substr, state: stringify(state)};
+ }
+ // Give up when line is ridiculously long
+ if (stream.pos > 5000) {
+ st[pos++] = {style: null, text: this.text.slice(stream.pos)};
+ break;
+ }
+ newLine = false;
+ }
+ }
+
+ return st;
+ }
+
+ function highlightOutputsDifferent(o1, o2) {
+ var minLen = Math.min(o1.length, o2.length);
+ for (var i = 0; i < minLen; ++i)
+ if (o1[i].style != o2[i].style || o1[i].text != o2[i].text) return i;
+ if (o1.length > minLen || o2.length > minLen) return minLen;
+ }
+
+ function prettyPrintOutputTable(output, diffAt) {
+ var s = '
';
+ s += '
';
+ for (var i = 0; i < output.length; ++i) {
+ var style = output[i].style, val = output[i].text;
+ s +=
+ '