commit 5fcd5dc646e7b669ca28125c37d6b6c00caf5553
Author: smshine Styling the current cursor line. Shows a visual representation of the b-tree that CodeMirror
+ uses to store its document. See
+ the corresponding
+ blog post for a description of this format. The gray blocks
+ under each leaf show the lines it holds (with their width
+ representing the line height). Add and remove content to see how
+ the nodes are split and merged to keep the tree balanced. 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. Press ctrl-space to activate autocompletion. See
+the code (here
+and here) to figure out
+how it works. The emacs keybindings are enabled by
+including keymap/emacs.js and setting
+the 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 code folding using the code
+ in Select a piece of code and click one of the links below to apply automatic formatting to the selected text or comment/uncomment the selected text. Note that the formatting behavior depends on the current block's mode.
+ CodeMirror: Active Line Demo
+
+
+
+
+
+ CodeMirror: B-Tree visualization
+
+ CodeMirror: Mode-Changing demo
+
+
+
+Close-Tag Demo
+
+
+
+
+
+
+
+
diff --git a/codemirror/demo/complete.html b/codemirror/demo/complete.html
new file mode 100644
index 0000000..13569ed
--- /dev/null
+++ b/codemirror/demo/complete.html
@@ -0,0 +1,70 @@
+
+
+
+
+ CodeMirror: Autocomplete demo
+
+
+
+CodeMirror: Emacs bindings demo
+
+
+
+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.CodeMirror: Code Folding Demo
+
+ foldcode.js.
+ Press ctrl-q or click on the gutter to fold a block, again
+ to unfold.CodeMirror: Formatting demo
+
+
+
+
+
+
+
+
+
+ Autoformat Selected
+
+
+
+
+ Comment Selected
+
+
+
+
+ Uncomment Selected
+
+
+
Press F11 when cursor is in the editor to toggle full screen editing. Esc can also be used to exit full screen editing.
+ + diff --git a/codemirror/demo/loadmode.html b/codemirror/demo/loadmode.html new file mode 100644 index 0000000..20f86cf --- /dev/null +++ b/codemirror/demo/loadmode.html @@ -0,0 +1,71 @@ + + + + +Click the line-number gutter to add or remove 'breakpoints'.
+ + + + + diff --git a/codemirror/demo/matchhighlighter.html b/codemirror/demo/matchhighlighter.html new file mode 100644 index 0000000..c8a4bdf --- /dev/null +++ b/codemirror/demo/matchhighlighter.html @@ -0,0 +1,36 @@ + + + + +Highlight matches of selected text on select
+ + + diff --git a/codemirror/demo/multiplex.html b/codemirror/demo/multiplex.html new file mode 100644 index 0000000..25fffd3 --- /dev/null +++ b/codemirror/demo/multiplex.html @@ -0,0 +1,60 @@ + + + + +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.
By setting a few CSS properties, CodeMirror can be made to +automatically resize to fit its content.
+ + + + + diff --git a/codemirror/demo/runmode.html b/codemirror/demo/runmode.html new file mode 100644 index 0000000..53ac04f --- /dev/null +++ b/codemirror/demo/runmode.html @@ -0,0 +1,50 @@ + + + + +Running a CodeMirror mode outside of the editor.
+ The CodeMirror.runMode function, defined
+ in lib/runmode.js takes the following arguments:
text (string)mode (mode spec)output (function or DOM node)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).Demonstration of primitive search/replace functionality. The + keybindings (which can be overridden by custom keymaps) are:
+Searching is enabled by + including lib/util/search.js + and lib/util/searchcursor.js. + For good-looking input dialogs, you also want to include + lib/util/dialog.js + and lib/util/dialog.css.
+ + diff --git a/codemirror/demo/theme.html b/codemirror/demo/theme.html new file mode 100644 index 0000000..42a1b0c --- /dev/null +++ b/codemirror/demo/theme.html @@ -0,0 +1,85 @@ + + + + +Select a theme: +
+ + + + diff --git a/codemirror/demo/variableheight.html b/codemirror/demo/variableheight.html new file mode 100644 index 0000000..8523027 --- /dev/null +++ b/codemirror/demo/variableheight.html @@ -0,0 +1,52 @@ + + + + +The vim keybindings are enabled by
+including keymap/vim.js and setting
+the keyMap option to "vim". Because
+CodeMirror's internal API is quite different from Vim, they are only
+a loose approximation of actual vim bindings, though.
Tabs inside the editor are spans with the
+class cm-tab, and can be styled.
+
+
+
+
+
diff --git a/codemirror/demo/widget.html b/codemirror/demo/widget.html
new file mode 100644
index 0000000..a3b27a9
--- /dev/null
+++ b/codemirror/demo/widget.html
@@ -0,0 +1,74 @@
+
+
+
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.
+ + diff --git a/codemirror/demo/xmlcomplete.html b/codemirror/demo/xmlcomplete.html new file mode 100644 index 0000000..f7cca8a --- /dev/null +++ b/codemirror/demo/xmlcomplete.html @@ -0,0 +1,74 @@ + + + + +Type '<' or space inside tag or + press ctrl-space to activate autocompletion. See + the code (here + and here) to figure out how + it works.
+ + + + diff --git a/codemirror/doc/baboon.png b/codemirror/doc/baboon.png new file mode 100644 index 0000000..55d97f7 Binary files /dev/null and b/codemirror/doc/baboon.png differ diff --git a/codemirror/doc/baboon_vector.svg b/codemirror/doc/baboon_vector.svg new file mode 100644 index 0000000..dc1667a --- /dev/null +++ b/codemirror/doc/baboon_vector.svg @@ -0,0 +1,153 @@ + + + + \ No newline at end of file diff --git a/codemirror/doc/compress.html b/codemirror/doc/compress.html new file mode 100644 index 0000000..94df0c5 --- /dev/null +++ b/codemirror/doc/compress.html @@ -0,0 +1,174 @@ + + + + +
++/* Script 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.
+ + + + + + + diff --git a/codemirror/doc/docs.css b/codemirror/doc/docs.css new file mode 100644 index 0000000..0ca959a --- /dev/null +++ b/codemirror/doc/docs.css @@ -0,0 +1,167 @@ +body { + font-family: Droid Sans, Arial, sans-serif; +/* line-height: 1.5; + max-width: 64.3em;*/ + margin: 3em auto; + padding: 0 1em; +} + +h1 { + letter-spacing: -3px; + font-size: 3.23em; + font-weight: bold; + margin: 0; +} + +h2 { + font-size: 1.23em; + font-weight: bold; + margin: .5em 0; + letter-spacing: -1px; +} + +h3 { + font-size: 1em; + font-weight: bold; + margin: .4em 0; +} + +pre { + background-color: #eee; + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + border-radius: 6px; + padding: 1em; +} + +pre.code { + margin: 0 1em; +} + +.grey { + background-color: #eee; + border-radius: 6px; + margin-bottom: 1.65em; + margin-top: 0.825em; + padding: 0.825em 1.65em; + position: relative; +} + +img.logo { + position: absolute; + right: -1em; + bottom: 4px; + max-width: 23.6875em; /* Scale image down with text to prevent clipping */ +} + +.grey > pre { + background:none; + border-radius:0; + padding:0; + margin:0; + font-size:2.2em; + line-height:1.2em; +} + +a:link, a:visited, .quasilink { + color: #df0019; + cursor: pointer; + text-decoration: none; +} + +a:hover, .quasilink:hover { + color: #800004; +} + +h1 a:link, h1 a:visited, h1 a:hover { + color: black; +} + +ul { + margin: 0; + padding-left: 1.2em; +} + +a.download { + color: white; + background-color: #df0019; + width: 100%; + display: block; + text-align: center; + font-size: 1.23em; + font-weight: bold; + text-decoration: none; + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + border-radius: 6px; + padding: .5em 0; + margin-bottom: 1em; +} + +a.download:hover { + background-color: #bb0010; +} + +.rel { + margin-bottom: 0; +} + +.rel-note { + color: #777; + font-size: .9em; + margin-top: .1em; +} + +.logo-braces { + color: #df0019; + position: relative; + top: -4px; +} + +.blk { + float: left; +} + +.left { + margin-right: 20.68em; + max-width: 37em; + padding-right: 6.53em; + padding-bottom: 1em; +} + +.left1 { + width: 15.24em; + padding-right: 6.45em; +} + +.left2 { + max-width: 15.24em; +} + +.right { + width: 20.68em; + margin-left: -20.68em; +} + +.leftbig { + width: 42.44em; + padding-right: 6.53em; +} + +.rightsmall { + width: 15.24em; +} + +.clear:after { + visibility: hidden; + display: block; + font-size: 0; + content: " "; + clear: both; + height: 0; +} +.clear { display: inline-block; } +/* start commented backslash hack \*/ +* html .clear { height: 1%; } +.clear { display: block; } +/* close commented backslash hack */ diff --git a/codemirror/doc/internals.html b/codemirror/doc/internals.html new file mode 100644 index 0000000..9139528 --- /dev/null +++ b/codemirror/doc/internals.html @@ -0,0 +1,505 @@ + + + + +
++/* (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.
+ +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.)
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.
+ +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.
+ +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.
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.
+ +Given all this, what can you expect from CodeMirror 2?
+ +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.
+ +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.
+ +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 */ ++
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 add-ons included in the distribution, + and + the CodeMirror + UI project, 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.
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:
<script src="lib/codemirror.js"></script> +<link rel="stylesheet" href="../lib/codemirror.css"> +<script src="mode/javascript/javascript.js"></script>+ +
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:
var myCodeMirror = CodeMirror(document.body, {
+ value: "function myScript(){return 100;}\n",
+ mode: "javascript"
+});
+
+ 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:
var myCodeMirror = CodeMirror(function(elt) {
+ myTextArea.parentNode.replaceChild(elt, myTextArea);
+}, {value: myTextArea.value});
+
+ 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.
+ +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)mode (string or object)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.theme (string).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)smartIndent (boolean)tabSize (integer)indentWithTabs (boolean)tabSize
+ spaces should be replaced by N tabs. Default is false.electricChars (boolean)rtlMoveVisually (boolean)false
+ on Windows, and true on other platforms.keyMap (string)"default", which is the only keymap defined
+ in codemirror.js itself. Extra keymaps are found in
+ the keymap directory. See
+ the section on keymaps for more
+ information.extraKeys (object)keyMap. Should be
+ either null, or a valid keymap value.lineWrapping (boolean)false (scroll).lineNumbers (boolean)firstLineNumber (integer)lineNumberFormatter (function)gutters (array)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.readOnly (boolean)"nocursor" is given (instead of
+ simply true), focusing of the editor is also
+ disallowed.showCursorWhenSelecting (boolean)undoDepth (integer)tabindex (integer)autofocus (boolean)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)onDragEvent (function)dragenter, dragover,
+ or drop event. It will be passed the editor instance
+ and the event object as arguments. The callback can choose to
+ handle the event itself, in which case it should
+ return true to indicate that CodeMirror should not
+ do anything further.onKeyEvent (function)keydown, keyup,
+ and keypress event that CodeMirror captures. It
+ will be passed two arguments, the editor instance and the key
+ event. This key event is pretty much the raw key event, except
+ that a stop() method is always added to it. You
+ could feed it to, for example, jQuery.Event to
+ further normalize it.keydown does not stop
+ the keypress from firing, whereas on others it
+ does. If you respond to an event, you should probably inspect
+ its type property and only do something when it
+ is keydown (or keypress for actions
+ that need character data).cursorBlinkRate (number)cursorHeight (number)0.85),
+ which causes the cursor to not reach all the way to the bottom
+ of the line, looks betterworkTime, workDelay (number)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)flattenSpans (boolean)viewportMargin (integer)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.A CodeMirror instance emits a number of events, which allow
+ client code to react to various situations. These are registered
+ with the on method (and
+ removed with the off
+ method). These are the events that fire on the instance object.
+ The name of the event is followed by the arguments that will be
+ passed to the handler. The instance argument always
+ refers to the editor instance.
"change" (instance, changeObj)changeObj is a {from, to, text,
+ next} 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). If multiple changes happened
+ during a single operation, the object will have
+ a next property pointing to another change object
+ (which may point to another, etc)."cursorActivity" (instance)"viewportChange" (instance, from, to)from and to arguments
+ give the new start and end of the viewport."gutterClick" (instance, line, gutter, clickEvent)mousedown event object as
+ fourth argument."focus", "blur" (instance)"scroll" (instance)"update" (instance)It is also possible to register events
+ on other objects. Line handles (as returned by, for
+ example, getLineHandle)
+ can be listened on with CodeMirror.on(handle, "delete",
+ myFunc). They support the following events:
"delete" ()"change" ()Marked range handles, as returned
+ by markText, emit the
+ following event:
"clear" ()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.Keymaps are ways to associate keys with functionality. A keymap + is an object mapping strings that identify the keys to functions + that implement their functionality.
+ +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- (in that order!) to specify a modifier. So
+ for example, Shift-Ctrl-Space would be a valid key
+ identifier.
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.
The CodeMirror.keyMap object associates keymaps
+ with names. User code and keymap definitions can assign extra
+ properties to this object. Anywhere where a keymap is expected, a
+ string can be given, which will be looked up in this object. It
+ also contains the "default" keymap holding the
+ default bindings.
The values of properties in keymaps can be either functions of
+ a single argument (the CodeMirror instance), strings, or
+ false. Such strings refer to properties of the
+ CodeMirror.commands object, which defines a number of
+ common commands that are used by the default keybindings, and maps
+ them to functions. If the property is set to false,
+ CodeMirror leaves handling of the key up to the browser. A key
+ handler function may throw 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" (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.
Keymaps 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 keymap or an array of
+ keymaps.
When a keymap contains a nofallthrough property
+ set to true, keys matched against that map will be
+ ignored if they don't match any of the bindings in the map (no
+ further child maps will be tried, and the default effect of
+ inserting a character will not occur).
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:
+ +CodeMirrorCodeMirror-scrolloverflow: auto +
+ fixed height). By default, it does. Setting
+ the CodeMirror class to have height:
+ auto and giving this class overflow-x: auto;
+ overflow-y: hidden; will cause the editor
+ to resize to fit its
+ content.CodeMirror-focusedCodeMirror-guttersCodeMirror-linenumbersCodeMirror-linenumberCodeMirror-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-linesCodeMirror-cursorCodeMirror-selectedspan elements
+ with this class.CodeMirror-matchingbracket,
+ CodeMirror-nonmatchingbracketIf 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.
A lot of CodeMirror features are only available through its + API. Thus, you need to write code (or + use add-ons) 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.
getValue() → string"\n").setValue(string)lineCount() → numbergetRange(from, to) → string
+ {line, ch} objects. An optional third
+ argument can be given to indicate the line separator string to
+ use (defaults to "\n").replaceRange(string, from, to)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.getSelection() → stringreplaceSelection(string)getCursor(start) → objectstart is a boolean indicating whether the start
+ or the end of the selection must be retrieved. If it is not
+ given, the current cursor pos, i.e. the side of the selection
+ that would move if you pressed an arrow key, is chosen.
+ Alternatively, you can pass one of the
+ strings "start", "end", "head"
+ (same as not passing anything), or "anchor" (the
+ opposite). A {line, ch} object will be
+ returned.somethingSelected() → booleansetCursor(pos){line, ch} object, or the line and the
+ character as two separate parameters.setSelection(anchor, head)anchor
+ and head should be {line, ch}
+ objects. head defaults to anchor when
+ not givne.getLine(n) → stringn.setLine(n, text)n.removeLine(n)setSize(width, height)width and height 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.focus()scrollTo(x, y)null
+ or undefined to have no effect.getScrollInfo(){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).scrollIntoView(pos)pos may be
+ either a {line, ch} position, referring to a given
+ character, null, to refer to the cursor, or
+ a {left, top, right, bottom} object, in
+ editor-local coordinates.setOption(option, value)option
+ should the name of an option,
+ and value should be a valid value for that
+ option.getOption(option) → valuegetMode() → objectgetOption("mode"), which gives you
+ the mode specification, rather than the resolved, instantiated
+ mode object.addKeyMap(map)extraKeys
+ option. Maps added in this way have a lower precedence
+ than extraKeys, a higher precedence than the
+ base keyMap, and
+ between them, the maps added earlier have a higher precedence
+ than those added later.removeKeyMap(map)addKeyMap. Either
+ pass in the keymap object itself, or a string, which will be
+ compared against the name property of the active
+ keymaps.addOverlay(mode, options)mode can be a mode
+ spec or a mode object (an object with
+ a token method).
+ The option 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.removeOverlay(mode)mode parameter
+ to addOverlay to remove
+ an overlay again.on(type, func)CodeMirror.on(object, type, func) version
+ that allows registering of events on any object.off(type, func)CodeMirror.off(object, type,
+ func) also exists.cursorCoords(where, mode) → object{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. 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.charCoords(pos, mode) → objectpos 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.coordsChar(object) → pos{left, top} object (in page coordinates),
+ returns the {line, ch} position that corresponds to
+ it.defaultTextHeight() → numbermarkClean()isClean() → booleanmarkClean).undo()redo()historySize() → object{undo, redo} properties,
+ both of which hold integers, indicating the amount of stored
+ undo and redo operations.clearHistory()getHistory() → objectsetHistory(object)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.indentLine(line, dir)"smart") may be one of:
+ "prev""smart""prev" otherwise."add""subtract"getTokenAt(pos) → object{line, ch} object). The
+ returned object has the following properties:
+ startendstringtype"keyword"
+ or "comment" (may also be null).statemarkText(from, to, options) → objectfrom 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)inclusiveLeft (boolean)inclusiveRight (boolean)inclusiveLeft,
+ but for the right side.atomic (boolean)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)clearOnEnter (boolean)"clear" event
+ fired on the range handle can be used to be notified when this
+ happens.replacedWith (dom node)readOnly
+ 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).startStyleendStylestartStyle, but for the rightmost span.clear(), which removes the mark,
+ and find(), which returns a {from, to}
+ (both document positions), indicating the current position of
+ the marked range, or undefined if the marker is no
+ longer in the document.setBookmark(pos, widget) → objectfind() 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 widget argument is optional, and can be used to display a
+ DOM node at the current location of the bookmark (analogous to
+ the replacedWith
+ option to markText).findMarksAt(pos) → arraysetGutterMarker(line, gutterID, value) → lineHandlegutters 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.clearGutter(gutterID)addLineClass(line, where, class) → lineHandleline
+ 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), 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.removeLineClass(line, where, class) → lineHandleline 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.lineInfo(line) → object{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.getLineHandle(num) → lineHandlegetLineNumber(handle) → integernull when it is no longer in the
+ document).getViewport() → object{from, to} object indicating the
+ start (inclusive) and end (exclusive) of the currently displayed
+ 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.addWidget(pos, node, scrollIntoView)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).addLineWidget(line, node, options) → objectline 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)noHScroll (boolean)above (boolean)line property
+ pointing at the line handle that it is associated with, and it
+ can be passed to removeLineWidget to remove the
+ widget.removeLineWidget(widget)posFromIndex(index) → object{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.indexFromPos(object) → numberposFromIndex.The following are more low-level methods:
+ +operation(func) → resultrefresh()extendSelection(pos, pos2)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. pos2 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).setExtending(bool)extendSelection
+ to leave the selection anchor in place.getInputField() → textareagetWrapperElement() → nodegetScrollerElement() → nodegetGutterElement() → nodegetStateAfter(line) → stateThe CodeMirror object itself provides
+ several useful properties. Firstly, its version
+ property contains a string that indicates the version of the
+ library. For releases, this simply
+ contains "major.minor" (for
+ example "2.33". For beta versions, " B"
+ (space, capital B) is added at the end of the string, for
+ development snapshots, " +" (space, plus) is
+ added.
The CodeMirror.fromTextArea
+ 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. A CodeMirror instance created this way has three
+ additional methods:
save()toTextArea()getTextArea() → textareaIf you want to define extra methods in terms
+ of the CodeMirror API, it is possible to
+ use CodeMirror.defineExtension(name, value). This
+ will cause the given value (usually a method) to be added to all
+ CodeMirror instances created from then on.
Similarly, CodeMirror.defineOption(name,
+ default, updateFunc) 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.
If your extention 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.
The lib/util directory in the distribution
+ contains a number of reusable components that implement extra
+ editor functionality. In brief, they are:
dialog.jsopenDialog method to CodeMirror instances,
+ which can be called with an HTML fragment that provides the
+ prompt (should include an input tag), and a
+ callback function that is called when text has been entered.
+ Depends on lib/util/dialog.css.searchcursor.jsgetSearchCursor(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(), findPrevious() → booleanmatch method, in case you
+ want to extract matched groups.from(), to() → objectfindNext or findPrevious did
+ not return false. They will return {line, ch}
+ objects pointing at the start and end of the match.replace(text)search.jssearchcursor.js, and will make use
+ of openDialog when
+ available to make prompting for search queries less ugly.matchbrackets.jsmatchBrackets 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.foldcode.jsCodeMirror.newFoldFunction with a range-finder
+ helper function to create a function that will, when applied to
+ a CodeMirror instance and a line number, attempt to fold or
+ unfold the block starting at the given line. A range-finder is a
+ language-specific function that also takes an instance and a
+ line number, and returns an range to be folded, or null if
+ no block is started on that line. This file
+ provides CodeMirror.braceRangeFinder, which finds
+ blocks in brace languages (JavaScript, C, Java,
+ etc), CodeMirror.indentRangeFinder, for languages
+ where indentation determines block structure (Python, Haskell),
+ and CodeMirror.tagRangeFinder, for XML-style
+ languages.runmode.jsoverlay.jsCodeMirror.overlayMode, which is used to
+ create such a mode. See this
+ demo for a detailed example.multiplex.jsCodeMirror.multiplexingMode which, when
+ given as first argument a mode object, and as other arguments
+ any number of {open, close, mode [, delimStyle]}
+ 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. The outer mode will not
+ see the content between the delimiters.
+ See this demo for an
+ example.simple-hint.jsCodeMirror.simpleHint, which takes a
+ CodeMirror instance and a hinting function, and pops up a widget
+ that allows the user to select a completion. Hinting functions
+ are function that take an editor instance, and return
+ a {list, from, to} object, where list
+ is an array of strings (the completions), and from
+ and to give the start and end of the token that is
+ being completed. Depends
+ on lib/util/simple-hint.css.javascript-hint.jsCodeMirror.javascriptHint
+ and CodeMirror.coffeescriptHint, which are simple
+ hinting functions for the JavaScript and CoffeeScript
+ modes.match-highlighter.jsmatchHighlight method to CodeMirror
+ instances that can be called (typically from
+ a cursorActivity
+ handler) to highlight all instances of a currently selected word
+ with the a classname given as a first argument to the method.
+ Depends on
+ the searchcursor
+ add-on. Demo here.formatting.jscommentRange, autoIndentRange,
+ and autoFormatRange methods that, respectively,
+ comment (or uncomment), indent, or format (add line breaks) a
+ range of code. Demo here.closetag.jsloadmode.jsCodeMirror.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.continuecomment.jsnewlineAndIndentContinueComment that you can
+ bind Enter to in order to have the editor prefix
+ new lines inside C-like block comments with an asterisk.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.
+ +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.
+ +
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() → booleansol() → booleanpeek() → characternull at the end of the
+ line.next() → characternull when no more characters are
+ available.eat(match) → charactermatch 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.eatWhile(match) → booleaneat with the given argument,
+ until it fails. Returns true if any characters were eaten.eatSpace() → booleaneatWhile when matching
+ white-space.skipToEnd()skipTo(ch) → booleanmatch(pattern, consume, caseFold) → booleaneat—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)n characters. Backing it up
+ further than the start of the current token will cause things to
+ break, so be careful.column() → integerindentation() → integercurrent() → stringBy 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, which keys can be
+ bound to), 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.
Finally, a mode may define
+ an electricChars property, which should hold a string
+ containing all the characters that should trigger the behaviour
+ described for
+ the electricChars
+ option.
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 methods, 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 autoformatter
+ and 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.
Sometimes, it is useful to add or override mode
+ object properties from external code.
+ The CodeMirror.extendMode 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.
++/* Old release + history */ ++
22-06-2012: Version 2.3:
+ +getScrollInfo method.23-05-2012: Version 2.25:
+ +23-04-2012: Version 2.24:
+ +dragDrop
+ and onDragEvent
+ options.compoundChange API method.catchall in key maps,
+ add nofallthrough boolean field instead.26-03-2012: Version 2.23:
+ +setLineClass.charCoords
+ and cursorCoords with a mode argument.autofocus option.findMarksAt method.27-02-2012: Version 2.22:
+ +autoClearEmptyLines option.27-01-2012: Version 2.21:
+ +smartIndent
+ option.readOnly-mode.scrollTo method.20-12-2011: Version 2.2:
+ +coordsFromIndex
+ to posFromIndex,
+ add indexFromPos
+ method.21-11-2011: Version 2.18:
+Fixes TextMarker.clear, which is broken in 2.17.
21-11-2011: Version 2.17:
+setBookmark method.lib/util.27-10-2011: Version 2.16:
+coordsFromIndex method.setValue now no longer clears history. Use clearHistory for that.markText now
+ returns an object with clear and find
+ methods. Marked text is now more robust when edited.26-09-2011: Version 2.15:
+Fix bug that snuck into 2.14: Clicking the + character that currently has the cursor didn't re-focus the + editor.
+ +26-09-2011: Version 2.14:
+fixedGutter option.setValue breaking cursor movement.23-08-2011: Version 2.13:
+getGutterElement to API.smartHome option.25-07-2011: Version 2.12:
+innerHTML for HTML-escaping.04-07-2011: Version 2.11:
+replace method to search cursors, for cursor-preserving replacements.getStateAfter API and compareState mode API methods for finer-grained mode magic.getScrollerElement API method to manipulate the scrolling DIV.07-06-2011: Version 2.1:
+Add + a theme system + (demo). Note that this is not + backwards-compatible—you'll have to update your styles and + modes!
+ +07-06-2011: Version 2.02:
+26-05-2011: Version 2.01:
+coordsChar now worksonCursorActivity interfered with onChange.onChange."nocursor" mode for readOnly option.onHighlightComplete option.28-03-2011: Version 2.0:
+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. + +
28-03-2011: Version 1.0:
+22-02-2011: Version 2.0 beta 2:
+Somewhat more mature API, lots of bugs shaken out. + +
17-02-2011: Version 0.94:
+tabMode: "spaces" was modified slightly (now indents when something is selected).08-02-2011: Version 2.0 beta 1:
+CodeMirror 2 is a complete rewrite of + CodeMirror, no longer depending on an editable frame.
+ +19-01-2011: Version 0.93:
+save method to instances created with fromTextArea.17-12-2010: Version 0.92:
+styleNumbers option is now officially
+ supported and documented.onLineNumberClick option added.onLoad and
+ onCursorActivity callbacks. Old names still work, but
+ are deprecated.11-11-2010: Version 0.91:
+toTextArea to update the code in the textarea.noScriptCaching option (hack to ease development).02-10-2010: Version 0.9:
+height: "dynamic" more robust.enterMode and electricChars options to make indentation even more customizable.firstLineNumber option.@media rules by the CSS parser.22-07-2010: Version 0.8:
+cursorCoords method to find the screen
+ coordinates of the cursor.height: dynamic mode, where the editor's
+ height will adjust to the size of its content.toTextArea method in instances created with
+ fromTextArea.27-04-2010: Version + 0.67:
+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.
01-03-2010: Version + 0.66:
+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.
12-11-2009: Version + 0.65:
+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.
23-10-2009: Version + 0.64:
+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.
31-08-2009: Version +0.63:
+Overhaul of paste-handling (less fragile), fixes for several +serious IE8 issues (cursor jumping, end-of-document bugs) and a number +of small problems.
+ +30-05-2009: Version +0.62:
+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).
++/* Real world uses, + full list */ ++
Contact me if you'd like + your project to be added to this list.
+ +
++/* Reporting bugs + effectively */ ++
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:
+ +
++/* Upgrading to + v2.2 */ ++
There are a few things in the 2.2 release that require some care +when upgrading.
+ +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.)
+
+
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:
extraKeys: {
+ "Ctrl-S": function(instance) { saveText(instance.getValue()); },
+ "Ctrl-/": "undo"
+ }
+
+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.
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.
++/* Upgrading to + version 3 */ ++
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.
+ +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.
+ +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>
+
+
+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.)
+cm.on("change", function(cm, change) {
+ console.log("something changed! (" + change.origin + ")");
+});
+
+
+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
+});
+
+
+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");
+});
+
+
+The setLineClass method has been replaced
+by addLineClass
+and removeLineClass,
+which allow more modular control over the classes attached to a line.
+var marked = cm.addLineClass(10, "background", "highlighted-line");
+setTimeout(function() {
+ cm.removeLineClass(marked, "background", "highlighted-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.
Affected methods
+are cursorCoords, charCoords, coordsChar,
+and getScrollInfo.
The matchBrackets
+option is no longer defined in the core editor.
+Load lib/util/matchbrackets.js to enable it.
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).
Some more reasons to upgrade to version 3.
+ +CodeMirror.defineOption.
++/* In-browser code editing + made bearable */ ++
CodeMirror is a JavaScript component that + provides a code editor in the browser. When a mode is available for + the language you are coding in, it will color your code, and + optionally help with indentation.
+ +A rich programming API and a CSS + theming system are available for customizing CodeMirror to fit your + application, and extending it with new functionality.
+ +All of CodeMirror is released under a MIT-style license. To get it, you can download + the latest + release or the current development + snapshot as zip files. To create a custom minified script file, + you can use the compression API.
+ +We use git for version control. + The main repository can be fetched in this way:
+ +git clone http://marijnhaverbeke.nl/git/codemirror+ +
CodeMirror can also be found on GitHub at marijnh/CodeMirror. + If you plan to hack on the code and contribute patches, the best way + to do it is to create a GitHub fork, and send pull requests.
+ +The manual is your first stop for + learning how to use this library. It starts with a quick explanation + of how to use the editor, and then describes the API in detail.
+ +For those who want to learn more about the code, there is + a series of + posts on CodeMirror on my blog, and the + old overview of the editor + internals. + The source code + itself is, for the most part, also very readable.
+ +Community discussion, questions, and informal bug reporting is + done on + the CodeMirror + Google group. There is a separate + group, CodeMirror-announce, + which is lower-volume, and is only used for major announcements—new + versions and such. These will be cross-posted to both groups, so you + don't need to subscribe to both.
+ +Though bug reports through e-mail are responded to, the preferred + way to report bugs is to use + the GitHub + issue tracker. Before reporting a + bug, read these pointers. Also, + the issue tracker is for bugs, not requests for help.
+ +When none of these seem fitting, you can + simply e-mail the maintainer + directly.
+ +The following desktop browsers are able to run CodeMirror:
+ +<!doctype
+ html> is recommended.)I am not actively testing against every new browser release, and + vendors have a habit of introducing bugs all the time, so I am + relying on the community to tell me when something breaks. + See here for information on how to contact + me.
+ +Mobile browsers mostly kind of work, but, because of limitations + and their fundamentally different UI assumptions, show a lot of + quirks that are hard to work around.
+ +CodeMirror is developed and maintained by me, Marijn Haverbeke, + in my own time. If your company is getting value out of CodeMirror, + please consider purchasing a support contract.
+ +CodeMirror support contracts exist in two + forms—basic at €100 per month, + and premium at €500 per + month. Contact me for further + information.
+ +20-12-2012: Version 2.37:
+ +10-12-2012: Version 3.0:
+ +New major version. Only + partially backwards-compatible. See + the upgrading guide for more + information. Changes since release candidate 2:
+ +20-11-2012: Version 3.0, release candidate 2:
+ +addKeyMap and removeKeyMap methods.formatting and closetag add-ons.20-11-2012: Version 2.36:
+ +scrollIntoView public.defaultTextHeight method.20-11-2012: Version 3.0, release candidate 1:
+ +addLineClass
+ and removeLineClass,
+ drop setLineClass.isClean/markClean methods.compoundChange method, use better undo-event-combining heuristic.22-10-2012: Version 2.35:
+ +markText/undo interaction.defineInitHook function.22-10-2012: Version 3.0, beta 2:
+ +gutterClick event.cursorHeight option.viewportMargin option.flattenSpans option.19-09-2012: Version 2.34:
+ +compareStates is no longer needed.onHighlightComplete no longer works.CodeMirror.version property.19-09-2012: Version 3.0, beta 1:
+ +23-08-2012: Version 2.33:
+ +getViewPort and onViewportChange API.false disabling handling (again).innerHTML. Remove CodeMirror.htmlEscape.23-07-2012: Version 2.32:
+ +Emergency fix for a bug where an editor with + line wrapping on IE will break when there is no + scrollbar.
+ +20-07-2012: Version 2.31:
+ +setSize method for programmatic resizing.getHistory and setHistory methods.getValue and getRange.Simple mode that tries to handle C-like languages as well as it
+ can. Takes two configuration parameters: keywords, an
+ object whose property names are the keywords in the language,
+ and useCPP, which determines whether C preprocessor
+ directives are recognized.
MIME types defined: text/x-csrc
+ (C code), text/x-c++src (C++
+ code), text/x-java (Java
+ code), text/x-csharp (C#).
MIME types defined: text/x-clojure.
MIME types defined: text/x-coffeescript.
The CoffeeScript mode was written by Jeff Pickhardt (license).
+ + + diff --git a/codemirror/mode/commonlisp/commonlisp.js b/codemirror/mode/commonlisp/commonlisp.js new file mode 100644 index 0000000..eeba759 --- /dev/null +++ b/codemirror/mode/commonlisp/commonlisp.js @@ -0,0 +1,101 @@ +CodeMirror.defineMode("commonlisp", function (config) { + var assumeBody = /^with|^def|^do|^prog|case$|^cond$|bind$|when$|unless$/; + var numLiteral = /^(?:[+\-]?(?:\d+|\d*\.\d+)(?:[efd][+\-]?\d+)?|[+\-]?\d+(?:\/[+\-]?\d+)?|#b[+\-]?[01]+|#o[+\-]?[0-7]+|#x[+\-]?[\da-f]+)/; + var symbol = /[^\s'`,@()\[\]";]/; + var type; + + function readSym(stream) { + var ch; + while (ch = stream.next()) { + if (ch == "\\") stream.next(); + else if (!symbol.test(ch)) { stream.backUp(1); break; } + } + return stream.current(); + } + + function base(stream, state) { + if (stream.eatSpace()) {type = "ws"; return null;} + if (stream.match(numLiteral)) return "number"; + var ch = stream.next(); + if (ch == "\\") ch = stream.next(); + + if (ch == '"') return (state.tokenize = inString)(stream, state); + else if (ch == "(") { type = "open"; return "bracket"; } + else if (ch == ")" || ch == "]") { type = "close"; return "bracket"; } + else if (ch == ";") { stream.skipToEnd(); type = "ws"; return "comment"; } + else if (/['`,@]/.test(ch)) return null; + else if (ch == "|") { + if (stream.skipTo("|")) { stream.next(); return "symbol"; } + else { stream.skipToEnd(); return "error"; } + } else if (ch == "#") { + var ch = stream.next(); + if (ch == "[") { type = "open"; return "bracket"; } + else if (/[+\-=\.']/.test(ch)) return null; + else if (/\d/.test(ch) && stream.match(/^\d*#/)) return null; + else if (ch == "|") return (state.tokenize = inComment)(stream, state); + else if (ch == ":") { readSym(stream); return "meta"; } + else return "error"; + } else { + var name = readSym(stream); + if (name == ".") return null; + type = "symbol"; + if (name == "nil" || name == "t") return "atom"; + if (name.charAt(0) == ":") return "keyword"; + if (name.charAt(0) == "&") return "variable-2"; + return "variable"; + } + } + + function inString(stream, state) { + var escaped = false, next; + while (next = stream.next()) { + if (next == '"' && !escaped) { state.tokenize = base; break; } + escaped = !escaped && next == "\\"; + } + return "string"; + } + + function inComment(stream, state) { + var next, last; + while (next = stream.next()) { + if (next == "#" && last == "|") { state.tokenize = base; break; } + last = next; + } + type = "ws"; + return "comment"; + } + + return { + startState: function () { + return {ctx: {prev: null, start: 0, indentTo: 0}, tokenize: base}; + }, + + token: function (stream, state) { + if (stream.sol() && typeof state.ctx.indentTo != "number") + state.ctx.indentTo = state.ctx.start + 1; + + type = null; + var style = state.tokenize(stream, state); + if (type != "ws") { + if (state.ctx.indentTo == null) { + if (type == "symbol" && assumeBody.test(stream.current())) + state.ctx.indentTo = state.ctx.start + config.indentUnit; + else + state.ctx.indentTo = "next"; + } else if (state.ctx.indentTo == "next") { + state.ctx.indentTo = stream.column(); + } + } + if (type == "open") state.ctx = {prev: state.ctx, start: stream.column(), indentTo: null}; + else if (type == "close") state.ctx = state.ctx.prev || state.ctx; + return style; + }, + + indent: function (state, _textAfter) { + var i = state.ctx.indentTo; + return typeof i == "number" ? i : state.ctx.start + 1; + } + }; +}); + +CodeMirror.defineMIME("text/x-common-lisp", "commonlisp"); diff --git a/codemirror/mode/commonlisp/index.html b/codemirror/mode/commonlisp/index.html new file mode 100644 index 0000000..f9766a8 --- /dev/null +++ b/codemirror/mode/commonlisp/index.html @@ -0,0 +1,165 @@ + + + + +MIME types defined: text/x-common-lisp.
MIME types defined: text/css.
Parsing/Highlighting Tests: normal, verbose.
+ + + diff --git a/codemirror/mode/css/test.js b/codemirror/mode/css/test.js new file mode 100644 index 0000000..fd6a4b8 --- /dev/null +++ b/codemirror/mode/css/test.js @@ -0,0 +1,501 @@ +// Initiate ModeTest and set defaults +var MT = ModeTest; +MT.modeName = 'css'; +MT.modeOptions = {}; + +// Requires at least one media query +MT.testMode( + 'atMediaEmpty', + '@media { }', + [ + 'def', '@media', + null, ' ', + 'error', '{', + null, ' }' + ] +); + +MT.testMode( + 'atMediaMultiple', + '@media not screen and (color), not print and (color) { }', + [ + 'def', '@media', + null, ' ', + 'keyword', 'not', + null, ' ', + 'attribute', 'screen', + null, ' ', + 'operator', 'and', + null, ' (', + 'property', 'color', + null, '), ', + 'keyword', 'not', + null, ' ', + 'attribute', 'print', + null, ' ', + 'operator', 'and', + null, ' (', + 'property', 'color', + null, ') { }' + ] +); + +MT.testMode( + 'atMediaCheckStack', + '@media screen { } foo { }', + [ + 'def', '@media', + null, ' ', + 'attribute', 'screen', + null, ' { } ', + 'tag', 'foo', + null, ' { }' + ] +); + +MT.testMode( + 'atMediaCheckStack', + '@media screen (color) { } foo { }', + [ + 'def', '@media', + null, ' ', + 'attribute', 'screen', + null, ' (', + 'property', 'color', + null, ') { } ', + 'tag', 'foo', + null, ' { }' + ] +); + +MT.testMode( + 'atMediaCheckStackInvalidAttribute', + '@media foobarhello { } foo { }', + [ + 'def', '@media', + null, ' ', + 'attribute error', 'foobarhello', + null, ' { } ', + 'tag', 'foo', + null, ' { }' + ] +); + +// Error, because "and" is only allowed immediately preceding a media expression +MT.testMode( + 'atMediaInvalidAttribute', + '@media foobarhello { }', + [ + 'def', '@media', + null, ' ', + 'attribute error', 'foobarhello', + null, ' { }' + ] +); + +// Error, because "and" is only allowed immediately preceding a media expression +MT.testMode( + 'atMediaInvalidAnd', + '@media and screen { }', + [ + 'def', '@media', + null, ' ', + 'error', 'and', + null, ' ', + 'attribute', 'screen', + null, ' { }' + ] +); + +// Error, because "not" is only allowed as the first item in each media query +MT.testMode( + 'atMediaInvalidNot', + '@media screen not (not) { }', + [ + 'def', '@media', + null, ' ', + 'attribute', 'screen', + null, ' ', + 'error', 'not', + null, ' (', + 'error', 'not', + null, ') { }' + ] +); + +// Error, because "only" is only allowed as the first item in each media query +MT.testMode( + 'atMediaInvalidOnly', + '@media screen only (only) { }', + [ + 'def', '@media', + null, ' ', + 'attribute', 'screen', + null, ' ', + 'error', 'only', + null, ' (', + 'error', 'only', + null, ') { }' + ] +); + +// Error, because "foobarhello" is neither a known type or property, but +// property was expected (after "and"), and it should be in parenthese. +MT.testMode( + 'atMediaUnknownType', + '@media screen and foobarhello { }', + [ + 'def', '@media', + null, ' ', + 'attribute', 'screen', + null, ' ', + 'operator', 'and', + null, ' ', + 'error', 'foobarhello', + null, ' { }' + ] +); + +// Error, because "color" is not a known type, but is a known property, and +// should be in parentheses. +MT.testMode( + 'atMediaInvalidType', + '@media screen and color { }', + [ + 'def', '@media', + null, ' ', + 'attribute', 'screen', + null, ' ', + 'operator', 'and', + null, ' ', + 'error', 'color', + null, ' { }' + ] +); + +// Error, because "print" is not a known property, but is a known type, +// and should not be in parenthese. +MT.testMode( + 'atMediaInvalidProperty', + '@media screen and (print) { }', + [ + 'def', '@media', + null, ' ', + 'attribute', 'screen', + null, ' ', + 'operator', 'and', + null, ' (', + 'error', 'print', + null, ') { }' + ] +); + +// Soft error, because "foobarhello" is not a known property or type. +MT.testMode( + 'atMediaUnknownProperty', + '@media screen and (foobarhello) { }', + [ + 'def', '@media', + null, ' ', + 'attribute', 'screen', + null, ' ', + 'operator', 'and', + null, ' (', + 'property error', 'foobarhello', + null, ') { }' + ] +); + +MT.testMode( + 'tagSelector', + 'foo { }', + [ + 'tag', 'foo', + null, ' { }' + ] +); + +MT.testMode( + 'classSelector', + '.foo-bar_hello { }', + [ + 'qualifier', '.foo-bar_hello', + null, ' { }' + ] +); + +MT.testMode( + 'idSelector', + '#foo { #foo }', + [ + 'builtin', '#foo', + null, ' { ', + 'error', '#foo', + null, ' }' + ] +); + +MT.testMode( + 'tagSelectorUnclosed', + 'foo { margin: 0 } bar { }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'margin', + 'operator', ':', + null, ' ', + 'number', '0', + null, ' } ', + 'tag', 'bar', + null, ' { }' + ] +); + +MT.testMode( + 'tagStringNoQuotes', + 'foo { font-family: hello world; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'font-family', + 'operator', ':', + null, ' ', + 'variable-2', 'hello', + null, ' ', + 'variable-2', 'world', + null, '; }' + ] +); + +MT.testMode( + 'tagStringDouble', + 'foo { font-family: "hello world"; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'font-family', + 'operator', ':', + null, ' ', + 'string', '"hello world"', + null, '; }' + ] +); + +MT.testMode( + 'tagStringSingle', + 'foo { font-family: \'hello world\'; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'font-family', + 'operator', ':', + null, ' ', + 'string', '\'hello world\'', + null, '; }' + ] +); + +MT.testMode( + 'tagColorKeyword', + 'foo { color: black; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'color', + 'operator', ':', + null, ' ', + 'keyword', 'black', + null, '; }' + ] +); + +MT.testMode( + 'tagColorHex3', + 'foo { background: #fff; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'background', + 'operator', ':', + null, ' ', + 'atom', '#fff', + null, '; }' + ] +); + +MT.testMode( + 'tagColorHex6', + 'foo { background: #ffffff; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'background', + 'operator', ':', + null, ' ', + 'atom', '#ffffff', + null, '; }' + ] +); + +MT.testMode( + 'tagColorHex4', + 'foo { background: #ffff; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'background', + 'operator', ':', + null, ' ', + 'atom error', '#ffff', + null, '; }' + ] +); + +MT.testMode( + 'tagColorHexInvalid', + 'foo { background: #ffg; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'background', + 'operator', ':', + null, ' ', + 'atom error', '#ffg', + null, '; }' + ] +); + +MT.testMode( + 'tagNegativeNumber', + 'foo { margin: -5px; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'margin', + 'operator', ':', + null, ' ', + 'number', '-5px', + null, '; }' + ] +); + +MT.testMode( + 'tagPositiveNumber', + 'foo { padding: 5px; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'padding', + 'operator', ':', + null, ' ', + 'number', '5px', + null, '; }' + ] +); + +MT.testMode( + 'tagVendor', + 'foo { -foo-box-sizing: -foo-border-box; }', + [ + 'tag', 'foo', + null, ' { ', + 'meta', '-foo-', + 'property', 'box-sizing', + 'operator', ':', + null, ' ', + 'meta', '-foo-', + 'string-2', 'border-box', + null, '; }' + ] +); + +MT.testMode( + 'tagBogusProperty', + 'foo { barhelloworld: 0; }', + [ + 'tag', 'foo', + null, ' { ', + 'property error', 'barhelloworld', + 'operator', ':', + null, ' ', + 'number', '0', + null, '; }' + ] +); + +MT.testMode( + 'tagTwoProperties', + 'foo { margin: 0; padding: 0; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'margin', + 'operator', ':', + null, ' ', + 'number', '0', + null, '; ', + 'property', 'padding', + 'operator', ':', + null, ' ', + 'number', '0', + null, '; }' + ] +); +// +//MT.testMode( +// 'tagClass', +// '@media only screen and (min-width: 500px), print {foo.bar#hello { color: black !important; background: #f00; margin: -5px; padding: 5px; -foo-box-sizing: border-box; } /* world */}', +// [ +// 'def', '@media', +// null, ' ', +// 'keyword', 'only', +// null, ' ', +// 'attribute', 'screen', +// null, ' ', +// 'operator', 'and', +// null, ' ', +// 'bracket', '(', +// 'property', 'min-width', +// 'operator', ':', +// null, ' ', +// 'number', '500px', +// 'bracket', ')', +// null, ', ', +// 'attribute', 'print', +// null, ' {', +// 'tag', 'foo', +// 'qualifier', '.bar', +// 'header', '#hello', +// null, ' { ', +// 'property', 'color', +// 'operator', ':', +// null, ' ', +// 'keyword', 'black', +// null, ' ', +// 'keyword', '!important', +// null, '; ', +// 'property', 'background', +// 'operator', ':', +// null, ' ', +// 'atom', '#f00', +// null, '; ', +// 'property', 'padding', +// 'operator', ':', +// null, ' ', +// 'number', '5px', +// null, '; ', +// 'property', 'margin', +// 'operator', ':', +// null, ' ', +// 'number', '-5px', +// null, '; ', +// 'meta', '-foo-', +// 'property', 'box-sizing', +// 'operator', ':', +// null, ' ', +// 'string-2', 'border-box', +// null, '; } ', +// 'comment', '/* world */', +// null, '}' +// ] +//); \ No newline at end of file diff --git a/codemirror/mode/diff/diff.js b/codemirror/mode/diff/diff.js new file mode 100644 index 0000000..9a0d90e --- /dev/null +++ b/codemirror/mode/diff/diff.js @@ -0,0 +1,32 @@ +CodeMirror.defineMode("diff", function() { + + var TOKEN_NAMES = { + '+': 'positive', + '-': 'negative', + '@': 'meta' + }; + + return { + token: function(stream) { + var tw_pos = stream.string.search(/[\t ]+?$/); + + if (!stream.sol() || tw_pos === 0) { + stream.skipToEnd(); + return ("error " + ( + TOKEN_NAMES[stream.string.charAt(0)] || '')).replace(/ $/, ''); + } + + var token_name = TOKEN_NAMES[stream.peek()] || stream.skipToEnd(); + + if (tw_pos === -1) { + stream.skipToEnd(); + } else { + stream.pos = tw_pos; + } + + return token_name; + } + }; +}); + +CodeMirror.defineMIME("text/x-diff", "diff"); diff --git a/codemirror/mode/diff/index.html b/codemirror/mode/diff/index.html new file mode 100644 index 0000000..5560252 --- /dev/null +++ b/codemirror/mode/diff/index.html @@ -0,0 +1,105 @@ + + + + +MIME types defined: text/x-diff.
Based on CodeMirror's clike mode. For more information see HPCC Systems web site.
+MIME types defined: text/x-ecl.
MIME types defined: text/x-erlang.
Optionally depends on other modes for properly highlighted code blocks.
+ +Parsing/Highlighting Tests: normal, verbose.
+ + + diff --git a/codemirror/mode/gfm/test.js b/codemirror/mode/gfm/test.js new file mode 100644 index 0000000..3a261f8 --- /dev/null +++ b/codemirror/mode/gfm/test.js @@ -0,0 +1,225 @@ +// Initiate ModeTest and set defaults +var MT = ModeTest; +MT.modeName = 'gfm'; +MT.modeOptions = {}; + +// Emphasis characters within a word +MT.testMode( + 'emInWordAsterisk', + 'foo*bar*hello', + [ + null, 'foo', + 'em', '*bar*', + null, 'hello' + ] +); +MT.testMode( + 'emInWordUnderscore', + 'foo_bar_hello', + [ + null, 'foo_bar_hello' + ] +); +MT.testMode( + 'emStrongUnderscore', + '___foo___ bar', + [ + 'strong', '__', + 'emstrong', '_foo__', + 'em', '_', + null, ' bar' + ] +); + +// Fenced code blocks +MT.testMode( + 'fencedCodeBlocks', + '```\nfoo\n\n```\nbar', + [ + 'comment', '```', + 'comment', 'foo', + 'comment', '```', + null, 'bar' + ] +); +// Fenced code block mode switching +MT.testMode( + 'fencedCodeBlockModeSwitching', + '```javascript\nfoo\n\n```\nbar', + [ + 'comment', '```javascript', + 'variable', 'foo', + 'comment', '```', + null, 'bar' + ] +); + +// SHA +MT.testMode( + 'SHA', + 'foo be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2 bar', + [ + null, 'foo ', + 'link', 'be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2', + null, ' bar' + ] +); +// GitHub highlights hashes 7-40 chars in length +MT.testMode( + 'shortSHA', + 'foo be6a8cc bar', + [ + null, 'foo ', + 'link', 'be6a8cc', + null, ' bar' + ] +); +// Invalid SHAs +// +// GitHub does not highlight hashes shorter than 7 chars +MT.testMode( + 'tooShortSHA', + 'foo be6a8c bar', + [ + null, 'foo be6a8c bar' + ] +); +// GitHub does not highlight hashes longer than 40 chars +MT.testMode( + 'longSHA', + 'foo be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd22 bar', + [ + null, 'foo be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd22 bar' + ] +); +MT.testMode( + 'badSHA', + 'foo be6a8cc1c1ecfe9489fb51e4869af15a13fc2cg2 bar', + [ + null, 'foo be6a8cc1c1ecfe9489fb51e4869af15a13fc2cg2 bar' + ] +); +// User@SHA +MT.testMode( + 'userSHA', + 'foo bar@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2 hello', + [ + null, 'foo ', + 'link', 'bar@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2', + null, ' hello' + ] +); +// User/Project@SHA +MT.testMode( + 'userProjectSHA', + 'foo bar/hello@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2 world', + [ + null, 'foo ', + 'link', 'bar/hello@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2', + null, ' world' + ] +); + +// #Num +MT.testMode( + 'num', + 'foo #1 bar', + [ + null, 'foo ', + 'link', '#1', + null, ' bar' + ] +); +// bad #Num +MT.testMode( + 'badNum', + 'foo #1bar hello', + [ + null, 'foo #1bar hello' + ] +); +// User#Num +MT.testMode( + 'userNum', + 'foo bar#1 hello', + [ + null, 'foo ', + 'link', 'bar#1', + null, ' hello' + ] +); +// User/Project#Num +MT.testMode( + 'userProjectNum', + 'foo bar/hello#1 world', + [ + null, 'foo ', + 'link', 'bar/hello#1', + null, ' world' + ] +); + +// Vanilla links +MT.testMode( + 'vanillaLink', + 'foo http://www.example.com/ bar', + [ + null, 'foo ', + 'link', 'http://www.example.com/', + null, ' bar' + ] +); +MT.testMode( + 'vanillaLinkPunctuation', + 'foo http://www.example.com/. bar', + [ + null, 'foo ', + 'link', 'http://www.example.com/', + null, '. bar' + ] +); +MT.testMode( + 'vanillaLinkExtension', + 'foo http://www.example.com/index.html bar', + [ + null, 'foo ', + 'link', 'http://www.example.com/index.html', + null, ' bar' + ] +); +// Not a link +MT.testMode( + 'notALink', + '```css\nfoo {color:black;}\n```http://www.example.com/', + [ + 'comment', '```css', + 'tag', 'foo', + null, ' {', + 'property', 'color', + 'operator', ':', + 'keyword', 'black', + null, ';}', + 'comment', '```', + 'link', 'http://www.example.com/' + ] +); +// Not a link +MT.testMode( + 'notALink', + '``foo `bar` http://www.example.com/`` hello', + [ + 'comment', '``foo `bar` http://www.example.com/``', + null, ' hello' + ] +); +// Not a link +MT.testMode( + 'notALink', + '`foo\nhttp://www.example.com/\n`foo\n\nhttp://www.example.com/', + [ + 'comment', '`foo', + 'link', 'http://www.example.com/', + 'comment', '`foo', + 'link', 'http://www.example.com/' + ] +); \ No newline at end of file diff --git a/codemirror/mode/go/go.js b/codemirror/mode/go/go.js new file mode 100644 index 0000000..8b84a5c --- /dev/null +++ b/codemirror/mode/go/go.js @@ -0,0 +1,165 @@ +CodeMirror.defineMode("go", function(config) { + var indentUnit = config.indentUnit; + + var keywords = { + "break":true, "case":true, "chan":true, "const":true, "continue":true, + "default":true, "defer":true, "else":true, "fallthrough":true, "for":true, + "func":true, "go":true, "goto":true, "if":true, "import":true, + "interface":true, "map":true, "package":true, "range":true, "return":true, + "select":true, "struct":true, "switch":true, "type":true, "var":true, + "bool":true, "byte":true, "complex64":true, "complex128":true, + "float32":true, "float64":true, "int8":true, "int16":true, "int32":true, + "int64":true, "string":true, "uint8":true, "uint16":true, "uint32":true, + "uint64":true, "int":true, "uint":true, "uintptr":true + }; + + var atoms = { + "true":true, "false":true, "iota":true, "nil":true, "append":true, + "cap":true, "close":true, "complex":true, "copy":true, "imag":true, + "len":true, "make":true, "new":true, "panic":true, "print":true, + "println":true, "real":true, "recover":true + }; + + var isOperatorChar = /[+\-*&^%:=<>!|\/]/; + + var curPunc; + + function tokenBase(stream, state) { + var ch = stream.next(); + if (ch == '"' || ch == "'" || ch == "`") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + if (/[\d\.]/.test(ch)) { + if (ch == ".") { + stream.match(/^[0-9]+([eE][\-+]?[0-9]+)?/); + } else if (ch == "0") { + stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/); + } else { + stream.match(/^[0-9]*\.?[0-9]*([eE][\-+]?[0-9]+)?/); + } + return "number"; + } + if (/[\[\]{}\(\),;\:\.]/.test(ch)) { + curPunc = ch; + return null; + } + if (ch == "/") { + if (stream.eat("*")) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + } + if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return "operator"; + } + stream.eatWhile(/[\w\$_]/); + var cur = stream.current(); + if (keywords.propertyIsEnumerable(cur)) { + if (cur == "case" || cur == "default") curPunc = "case"; + return "keyword"; + } + if (atoms.propertyIsEnumerable(cur)) return "atom"; + return "variable"; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) {end = true; break;} + escaped = !escaped && next == "\\"; + } + if (end || !(escaped || quote == "`")) + state.tokenize = tokenBase; + return "string"; + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + 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 pushContext(state, col, type) { + return state.context = new Context(state.indented, 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; + } + + // Interface + + return { + startState: function(basecolumn) { + return { + tokenize: null, + context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), + indented: 0, + startOfLine: true + }; + }, + + token: function(stream, state) { + var ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + if (ctx.type == "case") ctx.type = "}"; + } + if (stream.eatSpace()) return null; + curPunc = null; + var style = (state.tokenize || tokenBase)(stream, state); + if (style == "comment") return style; + if (ctx.align == null) ctx.align = true; + + if (curPunc == "{") pushContext(state, stream.column(), "}"); + else if (curPunc == "[") pushContext(state, stream.column(), "]"); + else if (curPunc == "(") pushContext(state, stream.column(), ")"); + else if (curPunc == "case") ctx.type = "case"; + else if (curPunc == "}" && ctx.type == "}") ctx = popContext(state); + else if (curPunc == ctx.type) popContext(state); + state.startOfLine = false; + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase && state.tokenize != null) return 0; + var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); + if (ctx.type == "case" && /^(?:case|default)\b/.test(textAfter)) { + state.context.type = "}"; + return ctx.indented; + } + var closing = firstChar == ctx.type; + if (ctx.align) return ctx.column + (closing ? 0 : 1); + else return ctx.indented + (closing ? 0 : indentUnit); + }, + + electricChars: "{}:" + }; +}); + +CodeMirror.defineMIME("text/x-go", "go"); diff --git a/codemirror/mode/go/index.html b/codemirror/mode/go/index.html new file mode 100644 index 0000000..1a9ef53 --- /dev/null +++ b/codemirror/mode/go/index.html @@ -0,0 +1,74 @@ + + + + +MIME type: text/x-go
MIME types defined: text/x-groovy
MIME types defined: text/x-haskell.
MIME types defined: text/x-haxe.
Mode for html embedded scripts like JSP and ASP.NET. Depends on HtmlMixed which in turn depends on
+ JavaScript, CSS and XML.
Other dependancies include those of the scriping language chosen.
MIME types defined: application/x-aspx (ASP.NET),
+ application/x-ejs (Embedded Javascript), application/x-jsp (JavaServer Pages)
The HTML mixed mode depends on the XML, JavaScript, and CSS modes.
+ +MIME types defined: text/html
+ (redefined, only takes effect if you load this parser after the
+ XML parser).
MIME types defined: message/http.
+ JavaScript mode supports a two configuration + options: +
json which will set the mode to expect JSON data rather than a JavaScript program.typescript which will activate additional syntax highlighting and some other things for TypeScript code (demo).
+ MIME types defined: text/javascript, application/json, text/typescript, application/typescript.
This is a specialization of the JavaScript mode.
+ + diff --git a/codemirror/mode/jinja2/index.html b/codemirror/mode/jinja2/index.html new file mode 100644 index 0000000..7cd1da2 --- /dev/null +++ b/codemirror/mode/jinja2/index.html @@ -0,0 +1,38 @@ + + + + +MIME types defined: text/x-less, text/css (if not previously defined).
Loosely based on Franciszek
+ Wawrzak's CodeMirror
+ 1 mode. One configuration parameter is
+ supported, specials, to which you can provide an
+ array of strings to have those identifiers highlighted with
+ the lua-special style.
MIME types defined: text/x-lua.
Optionally depends on the XML mode for properly highlighted inline XML blocks.
+ +MIME types defined: text/x-markdown.
Parsing/Highlighting Tests: normal, verbose.
+ + + diff --git a/codemirror/mode/markdown/markdown.js b/codemirror/mode/markdown/markdown.js new file mode 100644 index 0000000..531c2b5 --- /dev/null +++ b/codemirror/mode/markdown/markdown.js @@ -0,0 +1,474 @@ +CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { + + var htmlFound = CodeMirror.mimeModes.hasOwnProperty("text/html"); + var htmlMode = CodeMirror.getMode(cmCfg, htmlFound ? "text/html" : "text/plain"); + var aliases = { + html: "htmlmixed", + js: "javascript", + json: "application/json", + c: "text/x-csrc", + "c++": "text/x-c++src", + java: "text/x-java", + csharp: "text/x-csharp", + "c#": "text/x-csharp" + }; + + var getMode = (function () { + var i, modes = {}, mimes = {}, mime; + + var list = []; + for (var m in CodeMirror.modes) + if (CodeMirror.modes.propertyIsEnumerable(m)) list.push(m); + for (i = 0; i < list.length; i++) { + modes[list[i]] = list[i]; + } + var mimesList = []; + for (var m in CodeMirror.mimeModes) + if (CodeMirror.mimeModes.propertyIsEnumerable(m)) + mimesList.push({mime: m, mode: CodeMirror.mimeModes[m]}); + for (i = 0; i < mimesList.length; i++) { + mime = mimesList[i].mime; + mimes[mime] = mimesList[i].mime; + } + + for (var a in aliases) { + if (aliases[a] in modes || aliases[a] in mimes) + modes[a] = aliases[a]; + } + + return function (lang) { + return modes[lang] ? CodeMirror.getMode(cmCfg, modes[lang]) : null; + }; + }()); + + // Should underscores in words open/close em/strong? + if (modeCfg.underscoresBreakWords === undefined) + modeCfg.underscoresBreakWords = true; + + // Turn on fenced code blocks? ("```" to start/end) + if (modeCfg.fencedCodeBlocks === undefined) modeCfg.fencedCodeBlocks = false; + + var codeDepth = 0; + var prevLineHasContent = false + , thisLineHasContent = false; + + var header = 'header' + , code = 'comment' + , quote = 'quote' + , list = 'string' + , hr = 'hr' + , image = 'tag' + , linkinline = 'link' + , linkemail = 'link' + , linktext = 'link' + , linkhref = 'string' + , em = 'em' + , strong = 'strong' + , emstrong = 'emstrong'; + + var hrRE = /^([*\-=_])(?:\s*\1){2,}\s*$/ + , ulRE = /^[*\-+]\s+/ + , olRE = /^[0-9]+\.\s+/ + , headerRE = /^(?:\={1,}|-{1,})$/ + , textRE = /^[^!\[\]*_\\<>` "'(]+/; + + function switchInline(stream, state, f) { + state.f = state.inline = f; + return f(stream, state); + } + + function switchBlock(stream, state, f) { + state.f = state.block = f; + return f(stream, state); + } + + + // Blocks + + function blankLine(state) { + // Reset linkTitle state + state.linkTitle = false; + // Reset EM state + state.em = false; + // Reset STRONG state + state.strong = false; + // Reset state.quote + state.quote = false; + if (!htmlFound && state.f == htmlBlock) { + state.f = inlineNormal; + state.block = blockNormal; + } + return null; + } + + function blockNormal(stream, state) { + + if (state.list !== false && state.indentationDiff >= 0) { // Continued list + if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block + state.indentation -= state.indentationDiff; + } + state.list = null; + } else { // No longer a list + state.list = false; + } + + if (state.indentationDiff >= 4) { + state.indentation -= 4; + stream.skipToEnd(); + return code; + } else if (stream.eatSpace()) { + return null; + } else if (stream.peek() === '#' || (prevLineHasContent && stream.match(headerRE)) ) { + state.header = true; + } else if (stream.eat('>')) { + state.indentation++; + state.quote = true; + } else if (stream.peek() === '[') { + return switchInline(stream, state, footnoteLink); + } else if (stream.match(hrRE, true)) { + return hr; + } else if (stream.match(ulRE, true) || stream.match(olRE, true)) { + state.indentation += 4; + state.list = true; + } else if (modeCfg.fencedCodeBlocks && stream.match(/^```([\w+#]*)/, true)) { + // try switching mode + state.localMode = getMode(RegExp.$1); + if (state.localMode) state.localState = state.localMode.startState(); + switchBlock(stream, state, local); + return code; + } + + return switchInline(stream, state, state.inline); + } + + function htmlBlock(stream, state) { + var style = htmlMode.token(stream, state.htmlState); + if (htmlFound && style === 'tag' && state.htmlState.type !== 'openTag' && !state.htmlState.context) { + state.f = inlineNormal; + state.block = blockNormal; + } + if (state.md_inside && stream.current().indexOf(">")!=-1) { + state.f = inlineNormal; + state.block = blockNormal; + state.htmlState.context = undefined; + } + return style; + } + + function local(stream, state) { + if (stream.sol() && stream.match(/^```/, true)) { + state.localMode = state.localState = null; + state.f = inlineNormal; + state.block = blockNormal; + return code; + } else if (state.localMode) { + return state.localMode.token(stream, state.localState); + } else { + stream.skipToEnd(); + return code; + } + } + + // Inline + function getType(state) { + var styles = []; + + if (state.strong) { styles.push(state.em ? emstrong : strong); } + else if (state.em) { styles.push(em); } + + if (state.linkText) { styles.push(linktext); } + + if (state.code) { styles.push(code); } + + if (state.header) { styles.push(header); } + if (state.quote) { styles.push(quote); } + if (state.list !== false) { styles.push(list); } + + return styles.length ? styles.join(' ') : null; + } + + function handleText(stream, state) { + if (stream.match(textRE, true)) { + return getType(state); + } + return undefined; + } + + function inlineNormal(stream, state) { + var style = state.text(stream, state); + if (typeof style !== 'undefined') + return style; + + if (state.list) { // List marker (*, +, -, 1., etc) + state.list = null; + return list; + } + + var ch = stream.next(); + + if (ch === '\\') { + stream.next(); + return getType(state); + } + + // Matches link titles present on next line + if (state.linkTitle) { + state.linkTitle = false; + var matchCh = ch; + if (ch === '(') { + matchCh = ')'; + } + matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); + var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh; + if (stream.match(new RegExp(regex), true)) { + return linkhref; + } + } + + // If this block is changed, it may need to be updated in GFM mode + if (ch === '`') { + var t = getType(state); + var before = stream.pos; + stream.eatWhile('`'); + var difference = 1 + stream.pos - before; + if (!state.code) { + codeDepth = difference; + state.code = true; + return getType(state); + } else { + if (difference === codeDepth) { // Must be exact + state.code = false; + return t; + } + return getType(state); + } + } else if (state.code) { + return getType(state); + } + + if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) { + stream.match(/\[[^\]]*\]/); + state.inline = state.f = linkHref; + return image; + } + + if (ch === '[' && stream.match(/.*\](\(| ?\[)/, false)) { + state.linkText = true; + return getType(state); + } + + if (ch === ']' && state.linkText) { + var type = getType(state); + state.linkText = false; + state.inline = state.f = linkHref; + return type; + } + + if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, true)) { + return switchInline(stream, state, inlineElement(linkinline, '>')); + } + + if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, true)) { + return switchInline(stream, state, inlineElement(linkemail, '>')); + } + + if (ch === '<' && stream.match(/^\w/, false)) { + if (stream.string.indexOf(">")!=-1) { + var atts = stream.string.substring(1,stream.string.indexOf(">")); + if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) { + state.md_inside = true; + } + } + stream.backUp(1); + return switchBlock(stream, state, htmlBlock); + } + + if (ch === '<' && stream.match(/^\/\w*?>/)) { + state.md_inside = false; + return "tag"; + } + + var ignoreUnderscore = false; + if (!modeCfg.underscoresBreakWords) { + if (ch === '_' && stream.peek() !== '_' && stream.match(/(\w)/, false)) { + var prevPos = stream.pos - 2; + if (prevPos >= 0) { + var prevCh = stream.string.charAt(prevPos); + if (prevCh !== '_' && prevCh.match(/(\w)/, false)) { + ignoreUnderscore = true; + } + } + } + } + var t = getType(state); + if (ch === '*' || (ch === '_' && !ignoreUnderscore)) { + if (state.strong === ch && stream.eat(ch)) { // Remove STRONG + state.strong = false; + return t; + } else if (!state.strong && stream.eat(ch)) { // Add STRONG + state.strong = ch; + return getType(state); + } else if (state.em === ch) { // Remove EM + state.em = false; + return t; + } else if (!state.em) { // Add EM + state.em = ch; + return getType(state); + } + } else if (ch === ' ') { + if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces + if (stream.peek() === ' ') { // Surrounded by spaces, ignore + return getType(state); + } else { // Not surrounded by spaces, back up pointer + stream.backUp(1); + } + } + } + + return getType(state); + } + + function linkHref(stream, state) { + // Check if space, and return NULL if so (to avoid marking the space) + if(stream.eatSpace()){ + return null; + } + var ch = stream.next(); + if (ch === '(' || ch === '[') { + return switchInline(stream, state, inlineElement(linkhref, ch === '(' ? ')' : ']')); + } + return 'error'; + } + + function footnoteLink(stream, state) { + if (stream.match(/^[^\]]*\]:/, true)) { + state.f = footnoteUrl; + return linktext; + } + return switchInline(stream, state, inlineNormal); + } + + function footnoteUrl(stream, state) { + // Check if space, and return NULL if so (to avoid marking the space) + if(stream.eatSpace()){ + return null; + } + // Match URL + stream.match(/^[^\s]+/, true); + // Check for link title + if (stream.peek() === undefined) { // End of line, set flag to check next line + state.linkTitle = true; + } else { // More content on line, check if link title + stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true); + } + state.f = state.inline = inlineNormal; + return linkhref; + } + + var savedInlineRE = []; + function inlineRE(endChar) { + if (!savedInlineRE[endChar]) { + // Escape endChar for RegExp (taken from http://stackoverflow.com/a/494122/526741) + endChar = (endChar+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); + // Match any non-endChar, escaped character, as well as the closing + // endChar. + savedInlineRE[endChar] = new RegExp('^(?:[^\\\\]|\\\\.)*?(' + endChar + ')'); + } + return savedInlineRE[endChar]; + } + + function inlineElement(type, endChar, next) { + next = next || inlineNormal; + return function(stream, state) { + stream.match(inlineRE(endChar)); + state.inline = state.f = next; + return type; + }; + } + + return { + startState: function() { + prevLineHasContent = false; + thisLineHasContent = false; + return { + f: blockNormal, + + block: blockNormal, + htmlState: CodeMirror.startState(htmlMode), + indentation: 0, + + inline: inlineNormal, + text: handleText, + + linkText: false, + linkTitle: false, + em: false, + strong: false, + header: false, + list: false, + quote: false + }; + }, + + copyState: function(s) { + return { + f: s.f, + + block: s.block, + htmlState: CodeMirror.copyState(htmlMode, s.htmlState), + indentation: s.indentation, + + localMode: s.localMode, + localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null, + + inline: s.inline, + text: s.text, + linkTitle: s.linkTitle, + em: s.em, + strong: s.strong, + header: s.header, + list: s.list, + quote: s.quote, + md_inside: s.md_inside + }; + }, + + token: function(stream, state) { + if (stream.sol()) { + if (stream.match(/^\s*$/, true)) { + prevLineHasContent = false; + return blankLine(state); + } else { + if(thisLineHasContent){ + prevLineHasContent = true; + thisLineHasContent = false; + } + thisLineHasContent = true; + } + + // Reset state.header + state.header = false; + + // Reset state.code + state.code = false; + + state.f = state.block; + var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, ' ').length; + var difference = Math.floor((indentation - state.indentation) / 4) * 4; + if (difference > 4) difference = 4; + var adjustedIndentation = state.indentation + difference; + state.indentationDiff = adjustedIndentation - state.indentation; + state.indentation = adjustedIndentation; + if (indentation > 0) return null; + } + return state.f(stream, state); + }, + + blankLine: blankLine, + + getType: getType + }; + +}, "xml"); + +CodeMirror.defineMIME("text/x-markdown", "markdown"); diff --git a/codemirror/mode/markdown/test.js b/codemirror/mode/markdown/test.js new file mode 100644 index 0000000..2e06707 --- /dev/null +++ b/codemirror/mode/markdown/test.js @@ -0,0 +1,1291 @@ +// Initiate ModeTest and set defaults +var MT = ModeTest; +MT.modeName = 'markdown'; +MT.modeOptions = {}; + +MT.testMode( + 'plainText', + 'foo', + [ + null, 'foo' + ] +); + +// Code blocks using 4 spaces (regardless of CodeMirror.tabSize value) +MT.testMode( + 'codeBlocksUsing4Spaces', + ' foo', + [ + null, ' ', + 'comment', 'foo' + ] +); +// Code blocks using 4 spaces with internal indentation +MT.testMode( + 'codeBlocksUsing4SpacesIndentation', + ' bar\n hello\n world\n foo\nbar', + [ + null, ' ', + 'comment', 'bar', + null, ' ', + 'comment', 'hello', + null, ' ', + 'comment', 'world', + null, ' ', + 'comment', 'foo', + null, 'bar' + ] +); +// Code blocks using 4 spaces with internal indentation +MT.testMode( + 'codeBlocksUsing4SpacesIndentation', + ' foo\n bar\n hello\n world', + [ + null, ' foo', + null, ' ', + 'comment', 'bar', + null, ' ', + 'comment', 'hello', + null, ' ', + 'comment', 'world' + ] +); + +// Code blocks using 1 tab (regardless of CodeMirror.indentWithTabs value) +MT.testMode( + 'codeBlocksUsing1Tab', + '\tfoo', + [ + null, '\t', + 'comment', 'foo' + ] +); + +// Inline code using backticks +MT.testMode( + 'inlineCodeUsingBackticks', + 'foo `bar`', + [ + null, 'foo ', + 'comment', '`bar`' + ] +); + +// Block code using single backtick (shouldn't work) +MT.testMode( + 'blockCodeSingleBacktick', + '`\nfoo\n`', + [ + 'comment', '`', + null, 'foo', + 'comment', '`' + ] +); + +// Unclosed backticks +// Instead of simply marking as CODE, it would be nice to have an +// incomplete flag for CODE, that is styled slightly different. +MT.testMode( + 'unclosedBackticks', + 'foo `bar', + [ + null, 'foo ', + 'comment', '`bar' + ] +); + +// Per documentation: "To include a literal backtick character within a +// code span, you can use multiple backticks as the opening and closing +// delimiters" +MT.testMode( + 'doubleBackticks', + '``foo ` bar``', + [ + 'comment', '``foo ` bar``' + ] +); + +// Tests based on Dingus +// http://daringfireball.net/projects/markdown/dingus +// +// Multiple backticks within an inline code block +MT.testMode( + 'consecutiveBackticks', + '`foo```bar`', + [ + 'comment', '`foo```bar`' + ] +); +// Multiple backticks within an inline code block with a second code block +MT.testMode( + 'consecutiveBackticks', + '`foo```bar` hello `world`', + [ + 'comment', '`foo```bar`', + null, ' hello ', + 'comment', '`world`' + ] +); +// Unclosed with several different groups of backticks +MT.testMode( + 'unclosedBackticks', + '``foo ``` bar` hello', + [ + 'comment', '``foo ``` bar` hello' + ] +); +// Closed with several different groups of backticks +MT.testMode( + 'closedBackticks', + '``foo ``` bar` hello`` world', + [ + 'comment', '``foo ``` bar` hello``', + null, ' world' + ] +); + +// atx headers +// http://daringfireball.net/projects/markdown/syntax#header +// +// H1 +MT.testMode( + 'atxH1', + '# foo', + [ + 'header', '# foo' + ] +); +// H2 +MT.testMode( + 'atxH2', + '## foo', + [ + 'header', '## foo' + ] +); +// H3 +MT.testMode( + 'atxH3', + '### foo', + [ + 'header', '### foo' + ] +); +// H4 +MT.testMode( + 'atxH4', + '#### foo', + [ + 'header', '#### foo' + ] +); +// H5 +MT.testMode( + 'atxH5', + '##### foo', + [ + 'header', '##### foo' + ] +); +// H6 +MT.testMode( + 'atxH6', + '###### foo', + [ + 'header', '###### foo' + ] +); +// H6 - 7x '#' should still be H6, per Dingus +// http://daringfireball.net/projects/markdown/dingus +MT.testMode( + 'atxH6NotH7', + '####### foo', + [ + 'header', '####### foo' + ] +); + +// Setext headers - H1, H2 +// Per documentation, "Any number of underlining =’s or -’s will work." +// http://daringfireball.net/projects/markdown/syntax#header +// Ideally, the text would be marked as `header` as well, but this is +// not really feasible at the moment. So, instead, we're testing against +// what works today, to avoid any regressions. +// +// Check if single underlining = works +MT.testMode( + 'setextH1', + 'foo\n=', + [ + null, 'foo', + 'header', '=' + ] +); +// Check if 3+ ='s work +MT.testMode( + 'setextH1', + 'foo\n===', + [ + null, 'foo', + 'header', '===' + ] +); +// Check if single underlining - works +MT.testMode( + 'setextH2', + 'foo\n-', + [ + null, 'foo', + 'header', '-' + ] +); +// Check if 3+ -'s work +MT.testMode( + 'setextH2', + 'foo\n---', + [ + null, 'foo', + 'header', '---' + ] +); + +// Single-line blockquote with trailing space +MT.testMode( + 'blockquoteSpace', + '> foo', + [ + 'quote', '> foo' + ] +); + +// Single-line blockquote +MT.testMode( + 'blockquoteNoSpace', + '>foo', + [ + 'quote', '>foo' + ] +); + +// Single-line blockquote followed by normal paragraph +MT.testMode( + 'blockquoteThenParagraph', + '>foo\n\nbar', + [ + 'quote', '>foo', + null, 'bar' + ] +); + +// Multi-line blockquote (lazy mode) +MT.testMode( + 'multiBlockquoteLazy', + '>foo\nbar', + [ + 'quote', '>foo', + 'quote', 'bar' + ] +); + +// Multi-line blockquote followed by normal paragraph (lazy mode) +MT.testMode( + 'multiBlockquoteLazyThenParagraph', + '>foo\nbar\n\nhello', + [ + 'quote', '>foo', + 'quote', 'bar', + null, 'hello' + ] +); + +// Multi-line blockquote (non-lazy mode) +MT.testMode( + 'multiBlockquote', + '>foo\n>bar', + [ + 'quote', '>foo', + 'quote', '>bar' + ] +); + +// Multi-line blockquote followed by normal paragraph (non-lazy mode) +MT.testMode( + 'multiBlockquoteThenParagraph', + '>foo\n>bar\n\nhello', + [ + 'quote', '>foo', + 'quote', '>bar', + null, 'hello' + ] +); + +// Check list types +MT.testMode( + 'listAsterisk', + '* foo\n* bar', + [ + 'string', '* foo', + 'string', '* bar' + ] +); +MT.testMode( + 'listPlus', + '+ foo\n+ bar', + [ + 'string', '+ foo', + 'string', '+ bar' + ] +); +MT.testMode( + 'listDash', + '- foo\n- bar', + [ + 'string', '- foo', + 'string', '- bar' + ] +); +MT.testMode( + 'listNumber', + '1. foo\n2. bar', + [ + 'string', '1. foo', + 'string', '2. bar' + ] +); + +// Formatting in lists (*) +MT.testMode( + 'listAsteriskFormatting', + '* *foo* bar\n\n* **foo** bar\n\n* ***foo*** bar\n\n* `foo` bar', + [ + 'string', '* ', + 'string em', '*foo*', + 'string', ' bar', + 'string', '* ', + 'string strong', '**foo**', + 'string', ' bar', + 'string', '* ', + 'string strong', '**', + 'string emstrong', '*foo**', + 'string em', '*', + 'string', ' bar', + 'string', '* ', + 'string comment', '`foo`', + 'string', ' bar' + ] +); +// Formatting in lists (+) +MT.testMode( + 'listPlusFormatting', + '+ *foo* bar\n\n+ **foo** bar\n\n+ ***foo*** bar\n\n+ `foo` bar', + [ + 'string', '+ ', + 'string em', '*foo*', + 'string', ' bar', + 'string', '+ ', + 'string strong', '**foo**', + 'string', ' bar', + 'string', '+ ', + 'string strong', '**', + 'string emstrong', '*foo**', + 'string em', '*', + 'string', ' bar', + 'string', '+ ', + 'string comment', '`foo`', + 'string', ' bar' + ] +); +// Formatting in lists (-) +MT.testMode( + 'listDashFormatting', + '- *foo* bar\n\n- **foo** bar\n\n- ***foo*** bar\n\n- `foo` bar', + [ + 'string', '- ', + 'string em', '*foo*', + 'string', ' bar', + 'string', '- ', + 'string strong', '**foo**', + 'string', ' bar', + 'string', '- ', + 'string strong', '**', + 'string emstrong', '*foo**', + 'string em', '*', + 'string', ' bar', + 'string', '- ', + 'string comment', '`foo`', + 'string', ' bar' + ] +); +// Formatting in lists (1.) +MT.testMode( + 'listNumberFormatting', + '1. *foo* bar\n\n2. **foo** bar\n\n3. ***foo*** bar\n\n4. `foo` bar', + [ + 'string', '1. ', + 'string em', '*foo*', + 'string', ' bar', + 'string', '2. ', + 'string strong', '**foo**', + 'string', ' bar', + 'string', '3. ', + 'string strong', '**', + 'string emstrong', '*foo**', + 'string em', '*', + 'string', ' bar', + 'string', '4. ', + 'string comment', '`foo`', + 'string', ' bar' + ] +); + +// Paragraph lists +MT.testMode( + 'listParagraph', + '* foo\n\n* bar', + [ + 'string', '* foo', + 'string', '* bar' + ] +); + +// Multi-paragraph lists +// +// 4 spaces +MT.testMode( + 'listMultiParagraph', + '* foo\n\n* bar\n\n hello', + [ + 'string', '* foo', + 'string', '* bar', + null, ' ', + 'string', 'hello' + ] +); +// 4 spaces, extra blank lines (should still be list, per Dingus) +MT.testMode( + 'listMultiParagraphExtra', + '* foo\n\n* bar\n\n\n hello', + [ + 'string', '* foo', + 'string', '* bar', + null, ' ', + 'string', 'hello' + ] +); +// 4 spaces, plus 1 space (should still be list, per Dingus) +MT.testMode( + 'listMultiParagraphExtraSpace', + '* foo\n\n* bar\n\n hello\n\n world', + [ + 'string', '* foo', + 'string', '* bar', + null, ' ', + 'string', 'hello', + null, ' ', + 'string', 'world' + ] +); +// 1 tab +MT.testMode( + 'listTab', + '* foo\n\n* bar\n\n\thello', + [ + 'string', '* foo', + 'string', '* bar', + null, '\t', + 'string', 'hello' + ] +); +// No indent +MT.testMode( + 'listNoIndent', + '* foo\n\n* bar\n\nhello', + [ + 'string', '* foo', + 'string', '* bar', + null, 'hello' + ] +); +// Blockquote +MT.testMode( + 'blockquote', + '* foo\n\n* bar\n\n > hello', + [ + 'string', '* foo', + 'string', '* bar', + null, ' ', + 'string quote', '> hello' + ] +); +// Code block +MT.testMode( + 'blockquoteCode', + '* foo\n\n* bar\n\n > hello\n\n world', + [ + 'string', '* foo', + 'string', '* bar', + null, ' ', + 'comment', '> hello', + null, ' ', + 'string', 'world' + ] +); +// Code block followed by text +MT.testMode( + 'blockquoteCodeText', + '* foo\n\n bar\n\n hello\n\n world', + [ + 'string', '* foo', + null, ' ', + 'string', 'bar', + null, ' ', + 'comment', 'hello', + null, ' ', + 'string', 'world' + ] +); + +// Nested list +// +// * +MT.testMode( + 'listAsteriskNested', + '* foo\n\n * bar', + [ + 'string', '* foo', + null, ' ', + 'string', '* bar' + ] +); +// + +MT.testMode( + 'listPlusNested', + '+ foo\n\n + bar', + [ + 'string', '+ foo', + null, ' ', + 'string', '+ bar' + ] +); +// - +MT.testMode( + 'listDashNested', + '- foo\n\n - bar', + [ + 'string', '- foo', + null, ' ', + 'string', '- bar' + ] +); +// 1. +MT.testMode( + 'listNumberNested', + '1. foo\n\n 2. bar', + [ + 'string', '1. foo', + null, ' ', + 'string', '2. bar' + ] +); +// Mixed +MT.testMode( + 'listMixed', + '* foo\n\n + bar\n\n - hello\n\n 1. world', + [ + 'string', '* foo', + null, ' ', + 'string', '+ bar', + null, ' ', + 'string', '- hello', + null, ' ', + 'string', '1. world' + ] +); +// Blockquote +MT.testMode( + 'listBlockquote', + '* foo\n\n + bar\n\n > hello', + [ + 'string', '* foo', + null, ' ', + 'string', '+ bar', + null, ' ', + 'quote string', '> hello' + ] +); +// Code +MT.testMode( + 'listCode', + '* foo\n\n + bar\n\n hello', + [ + 'string', '* foo', + null, ' ', + 'string', '+ bar', + null, ' ', + 'comment', 'hello' + ] +); +// Code with internal indentation +MT.testMode( + 'listCodeIndentation', + '* foo\n\n bar\n hello\n world\n foo\n bar', + [ + 'string', '* foo', + null, ' ', + 'comment', 'bar', + null, ' ', + 'comment', 'hello', + null, ' ', + 'comment', 'world', + null, ' ', + 'comment', 'foo', + null, ' ', + 'string', 'bar' + ] +); +// Code followed by text +MT.testMode( + 'listCodeText', + '* foo\n\n bar\n\nhello', + [ + 'string', '* foo', + null, ' ', + 'comment', 'bar', + null, 'hello' + ] +); + +// Following tests directly from official Markdown documentation +// http://daringfireball.net/projects/markdown/syntax#hr +MT.testMode( + 'hrSpace', + '* * *', + [ + 'hr', '* * *' + ] +); + +MT.testMode( + 'hr', + '***', + [ + 'hr', '***' + ] +); + +MT.testMode( + 'hrLong', + '*****', + [ + 'hr', '*****' + ] +); + +MT.testMode( + 'hrSpaceDash', + '- - -', + [ + 'hr', '- - -' + ] +); + +MT.testMode( + 'hrDashLong', + '---------------------------------------', + [ + 'hr', '---------------------------------------' + ] +); + +// Inline link with title +MT.testMode( + 'linkTitle', + '[foo](http://example.com/ "bar") hello', + [ + 'link', '[foo]', + 'string', '(http://example.com/ "bar")', + null, ' hello' + ] +); + +// Inline link without title +MT.testMode( + 'linkNoTitle', + '[foo](http://example.com/) bar', + [ + 'link', '[foo]', + 'string', '(http://example.com/)', + null, ' bar' + ] +); + +// Inline link with image +MT.testMode( + 'linkImage', + '[](http://example.com/) bar', + [ + 'link', '[', + 'tag', '![foo]', + 'string', '(http://example.com/)', + 'link', ']', + 'string', '(http://example.com/)', + null, ' bar' + ] +); + +// Inline link with Em +MT.testMode( + 'linkEm', + '[*foo*](http://example.com/) bar', + [ + 'link', '[', + 'link em', '*foo*', + 'link', ']', + 'string', '(http://example.com/)', + null, ' bar' + ] +); + +// Inline link with Strong +MT.testMode( + 'linkStrong', + '[**foo**](http://example.com/) bar', + [ + 'link', '[', + 'link strong', '**foo**', + 'link', ']', + 'string', '(http://example.com/)', + null, ' bar' + ] +); + +// Inline link with EmStrong +MT.testMode( + 'linkEmStrong', + '[***foo***](http://example.com/) bar', + [ + 'link', '[', + 'link strong', '**', + 'link emstrong', '*foo**', + 'link em', '*', + 'link', ']', + 'string', '(http://example.com/)', + null, ' bar' + ] +); + +// Image with title +MT.testMode( + 'imageTitle', + ' hello', + [ + 'tag', '![foo]', + 'string', '(http://example.com/ "bar")', + null, ' hello' + ] +); + +// Image without title +MT.testMode( + 'imageNoTitle', + ' bar', + [ + 'tag', '![foo]', + 'string', '(http://example.com/)', + null, ' bar' + ] +); + +// Image with asterisks +MT.testMode( + 'imageAsterisks', + ' bar', + [ + 'tag', '![*foo*]', + 'string', '(http://example.com/)', + null, ' bar' + ] +); + +// Not a link. Should be normal text due to square brackets being used +// regularly in text, especially in quoted material, and no space is allowed +// between square brackets and parentheses (per Dingus). +MT.testMode( + 'notALink', + '[foo] (bar)', + [ + null, '[foo] (bar)' + ] +); + +// Reference-style links +MT.testMode( + 'linkReference', + '[foo][bar] hello', + [ + 'link', '[foo]', + 'string', '[bar]', + null, ' hello' + ] +); +// Reference-style links with Em +MT.testMode( + 'linkReferenceEm', + '[*foo*][bar] hello', + [ + 'link', '[', + 'link em', '*foo*', + 'link', ']', + 'string', '[bar]', + null, ' hello' + ] +); +// Reference-style links with Strong +MT.testMode( + 'linkReferenceStrong', + '[**foo**][bar] hello', + [ + 'link', '[', + 'link strong', '**foo**', + 'link', ']', + 'string', '[bar]', + null, ' hello' + ] +); +// Reference-style links with EmStrong +MT.testMode( + 'linkReferenceEmStrong', + '[***foo***][bar] hello', + [ + 'link', '[', + 'link strong', '**', + 'link emstrong', '*foo**', + 'link em', '*', + 'link', ']', + 'string', '[bar]', + null, ' hello' + ] +); + +// Reference-style links with optional space separator (per docuentation) +// "You can optionally use a space to separate the sets of brackets" +MT.testMode( + 'linkReferenceSpace', + '[foo] [bar] hello', + [ + 'link', '[foo]', + null, ' ', + 'string', '[bar]', + null, ' hello' + ] +); +// Should only allow a single space ("...use *a* space...") +MT.testMode( + 'linkReferenceDoubleSpace', + '[foo] [bar] hello', + [ + null, '[foo] [bar] hello' + ] +); + +// Reference-style links with implicit link name +MT.testMode( + 'linkImplicit', + '[foo][] hello', + [ + 'link', '[foo]', + 'string', '[]', + null, ' hello' + ] +); + +// @todo It would be nice if, at some point, the document was actually +// checked to see if the referenced link exists + +// Link label, for reference-style links (taken from documentation) +// +// No title +MT.testMode( + 'labelNoTitle', + '[foo]: http://example.com/', + [ + 'link', '[foo]:', + null, ' ', + 'string', 'http://example.com/' + ] +); +// Indented +MT.testMode( + 'labelIndented', + ' [foo]: http://example.com/', + [ + null, ' ', + 'link', '[foo]:', + null, ' ', + 'string', 'http://example.com/' + ] +); +// Space in ID and title +MT.testMode( + 'labelSpaceTitle', + '[foo bar]: http://example.com/ "hello"', + [ + 'link', '[foo bar]:', + null, ' ', + 'string', 'http://example.com/ "hello"' + ] +); +// Double title +MT.testMode( + 'labelDoubleTitle', + '[foo bar]: http://example.com/ "hello" "world"', + [ + 'link', '[foo bar]:', + null, ' ', + 'string', 'http://example.com/ "hello"', + null, ' "world"' + ] +); +// Double quotes around title +MT.testMode( + 'labelTitleDoubleQuotes', + '[foo]: http://example.com/ "bar"', + [ + 'link', '[foo]:', + null, ' ', + 'string', 'http://example.com/ "bar"' + ] +); +// Single quotes around title +MT.testMode( + 'labelTitleSingleQuotes', + '[foo]: http://example.com/ \'bar\'', + [ + 'link', '[foo]:', + null, ' ', + 'string', 'http://example.com/ \'bar\'' + ] +); +// Parentheses around title +MT.testMode( + 'labelTitleParenthese', + '[foo]: http://example.com/ (bar)', + [ + 'link', '[foo]:', + null, ' ', + 'string', 'http://example.com/ (bar)' + ] +); +// Invalid title +MT.testMode( + 'labelTitleInvalid', + '[foo]: http://example.com/ bar', + [ + 'link', '[foo]:', + null, ' ', + 'string', 'http://example.com/', + null, ' bar' + ] +); +// Angle brackets around URL +MT.testMode( + 'labelLinkAngleBrackets', + '[foo]:MIME types defined: text/x-mysql.
MIME types defined: text/n-triples.
MIME types defined: text/x-ocaml.
MIME types defined: text/x-pascal.
MIME types defined: text/x-perl.
Simple HTML/PHP mode based on + the C-like mode. Depends on XML, + JavaScript, CSS, HTMLMixed, and C-like modes.
+ +MIME types defined: application/x-httpd-php (HTML with PHP code), text/x-php (plain, non-wrapped PHP code).
+ Simple mode that handles Pig Latin language. +
+ +MIME type defined: text/x-pig
+ (PIG code)
+
diff --git a/codemirror/mode/pig/pig.js b/codemirror/mode/pig/pig.js
new file mode 100644
index 0000000..f8818a9
--- /dev/null
+++ b/codemirror/mode/pig/pig.js
@@ -0,0 +1,171 @@
+/*
+ * Pig Latin Mode for CodeMirror 2
+ * @author Prasanth Jayachandran
+ * @link https://github.com/prasanthj/pig-codemirror-2
+ * This implementation is adapted from PL/SQL mode in CodeMirror 2.
+*/
+CodeMirror.defineMode("pig", function(_config, parserConfig) {
+ var keywords = parserConfig.keywords,
+ builtins = parserConfig.builtins,
+ types = parserConfig.types,
+ multiLineStrings = parserConfig.multiLineStrings;
+
+ var isOperatorChar = /[*+\-%<>=&?:\/!|]/;
+
+ function chain(stream, state, f) {
+ state.tokenize = f;
+ return f(stream, state);
+ }
+
+ var type;
+ function ret(tp, style) {
+ type = tp;
+ return style;
+ }
+
+ function tokenComment(stream, state) {
+ var isEnd = false;
+ var ch;
+ while(ch = stream.next()) {
+ if(ch == "/" && isEnd) {
+ state.tokenize = tokenBase;
+ break;
+ }
+ isEnd = (ch == "*");
+ }
+ return ret("comment", "comment");
+ }
+
+ function tokenString(quote) {
+ return function(stream, state) {
+ var escaped = false, next, end = false;
+ while((next = stream.next()) != null) {
+ if (next == quote && !escaped) {
+ end = true; break;
+ }
+ escaped = !escaped && next == "\\";
+ }
+ if (end || !(escaped || multiLineStrings))
+ state.tokenize = tokenBase;
+ return ret("string", "error");
+ };
+ }
+
+ function tokenBase(stream, state) {
+ var ch = stream.next();
+
+ // is a start of string?
+ if (ch == '"' || ch == "'")
+ return chain(stream, state, tokenString(ch));
+ // is it one of the special chars
+ else if(/[\[\]{}\(\),;\.]/.test(ch))
+ return ret(ch);
+ // is it a number?
+ else if(/\d/.test(ch)) {
+ stream.eatWhile(/[\w\.]/);
+ return ret("number", "number");
+ }
+ // multi line comment or operator
+ else if (ch == "/") {
+ if (stream.eat("*")) {
+ return chain(stream, state, tokenComment);
+ }
+ else {
+ stream.eatWhile(isOperatorChar);
+ return ret("operator", "operator");
+ }
+ }
+ // single line comment or operator
+ else if (ch=="-") {
+ if(stream.eat("-")){
+ stream.skipToEnd();
+ return ret("comment", "comment");
+ }
+ else {
+ stream.eatWhile(isOperatorChar);
+ return ret("operator", "operator");
+ }
+ }
+ // is it an operator
+ else if (isOperatorChar.test(ch)) {
+ stream.eatWhile(isOperatorChar);
+ return ret("operator", "operator");
+ }
+ else {
+ // get the while word
+ stream.eatWhile(/[\w\$_]/);
+ // is it one of the listed keywords?
+ if (keywords && keywords.propertyIsEnumerable(stream.current().toUpperCase())) {
+ if (stream.eat(")") || stream.eat(".")) {
+ //keywords can be used as variables like flatten(group), group.$0 etc..
+ }
+ else {
+ return ("keyword", "keyword");
+ }
+ }
+ // is it one of the builtin functions?
+ if (builtins && builtins.propertyIsEnumerable(stream.current().toUpperCase()))
+ {
+ return ("keyword", "variable-2");
+ }
+ // is it one of the listed types?
+ if (types && types.propertyIsEnumerable(stream.current().toUpperCase()))
+ return ("keyword", "variable-3");
+ // default is a 'variable'
+ return ret("variable", "pig-word");
+ }
+ }
+
+ // Interface
+ return {
+ startState: function() {
+ return {
+ tokenize: tokenBase,
+ startOfLine: true
+ };
+ },
+
+ token: function(stream, state) {
+ if(stream.eatSpace()) return null;
+ var style = state.tokenize(stream, state);
+ return style;
+ }
+ };
+});
+
+(function() {
+ function keywords(str) {
+ var obj = {}, words = str.split(" ");
+ for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
+ return obj;
+ }
+
+ // builtin funcs taken from trunk revision 1303237
+ var pBuiltins = "ABS ACOS ARITY ASIN ATAN AVG BAGSIZE BINSTORAGE BLOOM BUILDBLOOM CBRT CEIL "
+ + "CONCAT COR COS COSH COUNT COUNT_STAR COV CONSTANTSIZE CUBEDIMENSIONS DIFF DISTINCT DOUBLEABS "
+ + "DOUBLEAVG DOUBLEBASE DOUBLEMAX DOUBLEMIN DOUBLEROUND DOUBLESUM EXP FLOOR FLOATABS FLOATAVG "
+ + "FLOATMAX FLOATMIN FLOATROUND FLOATSUM GENERICINVOKER INDEXOF INTABS INTAVG INTMAX INTMIN "
+ + "INTSUM INVOKEFORDOUBLE INVOKEFORFLOAT INVOKEFORINT INVOKEFORLONG INVOKEFORSTRING INVOKER "
+ + "ISEMPTY JSONLOADER JSONMETADATA JSONSTORAGE LAST_INDEX_OF LCFIRST LOG LOG10 LOWER LONGABS "
+ + "LONGAVG LONGMAX LONGMIN LONGSUM MAX MIN MAPSIZE MONITOREDUDF NONDETERMINISTIC OUTPUTSCHEMA "
+ + "PIGSTORAGE PIGSTREAMING RANDOM REGEX_EXTRACT REGEX_EXTRACT_ALL REPLACE ROUND SIN SINH SIZE "
+ + "SQRT STRSPLIT SUBSTRING SUM STRINGCONCAT STRINGMAX STRINGMIN STRINGSIZE TAN TANH TOBAG "
+ + "TOKENIZE TOMAP TOP TOTUPLE TRIM TEXTLOADER TUPLESIZE UCFIRST UPPER UTF8STORAGECONVERTER ";
+
+ // taken from QueryLexer.g
+ var pKeywords = "VOID IMPORT RETURNS DEFINE LOAD FILTER FOREACH ORDER CUBE DISTINCT COGROUP "
+ + "JOIN CROSS UNION SPLIT INTO IF OTHERWISE ALL AS BY USING INNER OUTER ONSCHEMA PARALLEL "
+ + "PARTITION GROUP AND OR NOT GENERATE FLATTEN ASC DESC IS STREAM THROUGH STORE MAPREDUCE "
+ + "SHIP CACHE INPUT OUTPUT STDERROR STDIN STDOUT LIMIT SAMPLE LEFT RIGHT FULL EQ GT LT GTE LTE "
+ + "NEQ MATCHES TRUE FALSE ";
+
+ // data types
+ var pTypes = "BOOLEAN INT LONG FLOAT DOUBLE CHARARRAY BYTEARRAY BAG TUPLE MAP ";
+
+ CodeMirror.defineMIME("text/x-pig", {
+ name: "pig",
+ builtins: keywords(pBuiltins),
+ keywords: keywords(pKeywords),
+ types: keywords(pTypes)
+ });
+}());
diff --git a/codemirror/mode/plsql/index.html b/codemirror/mode/plsql/index.html
new file mode 100644
index 0000000..9206e42
--- /dev/null
+++ b/codemirror/mode/plsql/index.html
@@ -0,0 +1,62 @@
+
+
+
+ Simple mode that handles Oracle PL/SQL language (and Oracle SQL, of course). +
+ +MIME type defined: text/x-plsql
+ (PLSQL code)
+
diff --git a/codemirror/mode/plsql/plsql.js b/codemirror/mode/plsql/plsql.js
new file mode 100644
index 0000000..df119ba
--- /dev/null
+++ b/codemirror/mode/plsql/plsql.js
@@ -0,0 +1,216 @@
+CodeMirror.defineMode("plsql", function(_config, parserConfig) {
+ var keywords = parserConfig.keywords,
+ functions = parserConfig.functions,
+ types = parserConfig.types,
+ sqlplus = parserConfig.sqlplus,
+ multiLineStrings = parserConfig.multiLineStrings;
+ var isOperatorChar = /[+\-*&%=<>!?:\/|]/;
+ function chain(stream, state, f) {
+ state.tokenize = f;
+ return f(stream, state);
+ }
+
+ var type;
+ function ret(tp, style) {
+ type = tp;
+ return style;
+ }
+
+ function tokenBase(stream, state) {
+ var ch = stream.next();
+ // start of string?
+ if (ch == '"' || ch == "'")
+ return chain(stream, state, tokenString(ch));
+ // is it one of the special signs []{}().,;? Seperator?
+ else if (/[\[\]{}\(\),;\.]/.test(ch))
+ return ret(ch);
+ // start of a number value?
+ else if (/\d/.test(ch)) {
+ stream.eatWhile(/[\w\.]/);
+ return ret("number", "number");
+ }
+ // multi line comment or simple operator?
+ else if (ch == "/") {
+ if (stream.eat("*")) {
+ return chain(stream, state, tokenComment);
+ }
+ else {
+ stream.eatWhile(isOperatorChar);
+ return ret("operator", "operator");
+ }
+ }
+ // single line comment or simple operator?
+ else if (ch == "-") {
+ if (stream.eat("-")) {
+ stream.skipToEnd();
+ return ret("comment", "comment");
+ }
+ else {
+ stream.eatWhile(isOperatorChar);
+ return ret("operator", "operator");
+ }
+ }
+ // pl/sql variable?
+ else if (ch == "@" || ch == "$") {
+ stream.eatWhile(/[\w\d\$_]/);
+ return ret("word", "variable");
+ }
+ // is it a operator?
+ else if (isOperatorChar.test(ch)) {
+ stream.eatWhile(isOperatorChar);
+ return ret("operator", "operator");
+ }
+ else {
+ // get the whole word
+ stream.eatWhile(/[\w\$_]/);
+ // is it one of the listed keywords?
+ if (keywords && keywords.propertyIsEnumerable(stream.current().toLowerCase())) return ret("keyword", "keyword");
+ // is it one of the listed functions?
+ if (functions && functions.propertyIsEnumerable(stream.current().toLowerCase())) return ret("keyword", "builtin");
+ // is it one of the listed types?
+ if (types && types.propertyIsEnumerable(stream.current().toLowerCase())) return ret("keyword", "variable-2");
+ // is it one of the listed sqlplus keywords?
+ if (sqlplus && sqlplus.propertyIsEnumerable(stream.current().toLowerCase())) return ret("keyword", "variable-3");
+ // default: just a "variable"
+ return ret("word", "variable");
+ }
+ }
+
+ function tokenString(quote) {
+ return function(stream, state) {
+ var escaped = false, next, end = false;
+ while ((next = stream.next()) != null) {
+ if (next == quote && !escaped) {end = true; break;}
+ escaped = !escaped && next == "\\";
+ }
+ if (end || !(escaped || multiLineStrings))
+ state.tokenize = tokenBase;
+ return ret("string", "plsql-string");
+ };
+ }
+
+ function tokenComment(stream, state) {
+ var maybeEnd = false, ch;
+ while (ch = stream.next()) {
+ if (ch == "/" && maybeEnd) {
+ state.tokenize = tokenBase;
+ break;
+ }
+ maybeEnd = (ch == "*");
+ }
+ return ret("comment", "plsql-comment");
+ }
+
+ // Interface
+
+ return {
+ startState: function() {
+ return {
+ tokenize: tokenBase,
+ startOfLine: true
+ };
+ },
+
+ token: function(stream, state) {
+ if (stream.eatSpace()) return null;
+ var style = state.tokenize(stream, state);
+ return style;
+ }
+ };
+});
+
+(function() {
+ function keywords(str) {
+ var obj = {}, words = str.split(" ");
+ for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
+ return obj;
+ }
+ var cKeywords = "abort accept access add all alter and any array arraylen as asc assert assign at attributes audit " +
+ "authorization avg " +
+ "base_table begin between binary_integer body boolean by " +
+ "case cast char char_base check close cluster clusters colauth column comment commit compress connect " +
+ "connected constant constraint crash create current currval cursor " +
+ "data_base database date dba deallocate debugoff debugon decimal declare default definition delay delete " +
+ "desc digits dispose distinct do drop " +
+ "else elsif enable end entry escape exception exception_init exchange exclusive exists exit external " +
+ "fast fetch file for force form from function " +
+ "generic goto grant group " +
+ "having " +
+ "identified if immediate in increment index indexes indicator initial initrans insert interface intersect " +
+ "into is " +
+ "key " +
+ "level library like limited local lock log logging long loop " +
+ "master maxextents maxtrans member minextents minus mislabel mode modify multiset " +
+ "new next no noaudit nocompress nologging noparallel not nowait number_base " +
+ "object of off offline on online only open option or order out " +
+ "package parallel partition pctfree pctincrease pctused pls_integer positive positiven pragma primary prior " +
+ "private privileges procedure public " +
+ "raise range raw read rebuild record ref references refresh release rename replace resource restrict return " +
+ "returning reverse revoke rollback row rowid rowlabel rownum rows run " +
+ "savepoint schema segment select separate session set share snapshot some space split sql start statement " +
+ "storage subtype successful synonym " +
+ "tabauth table tables tablespace task terminate then to trigger truncate type " +
+ "union unique unlimited unrecoverable unusable update use using " +
+ "validate value values variable view views " +
+ "when whenever where while with work";
+
+ var cFunctions = "abs acos add_months ascii asin atan atan2 average " +
+ "bfilename " +
+ "ceil chartorowid chr concat convert cos cosh count " +
+ "decode deref dual dump dup_val_on_index " +
+ "empty error exp " +
+ "false floor found " +
+ "glb greatest " +
+ "hextoraw " +
+ "initcap instr instrb isopen " +
+ "last_day least lenght lenghtb ln lower lpad ltrim lub " +
+ "make_ref max min mod months_between " +
+ "new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower " +
+ "nls_sort nls_upper nlssort no_data_found notfound null nvl " +
+ "others " +
+ "power " +
+ "rawtohex reftohex round rowcount rowidtochar rpad rtrim " +
+ "sign sin sinh soundex sqlcode sqlerrm sqrt stddev substr substrb sum sysdate " +
+ "tan tanh to_char to_date to_label to_multi_byte to_number to_single_byte translate true trunc " +
+ "uid upper user userenv " +
+ "variance vsize";
+
+ var cTypes = "bfile blob " +
+ "character clob " +
+ "dec " +
+ "float " +
+ "int integer " +
+ "mlslabel " +
+ "natural naturaln nchar nclob number numeric nvarchar2 " +
+ "real rowtype " +
+ "signtype smallint string " +
+ "varchar varchar2";
+
+ var cSqlplus = "appinfo arraysize autocommit autoprint autorecovery autotrace " +
+ "blockterminator break btitle " +
+ "cmdsep colsep compatibility compute concat copycommit copytypecheck " +
+ "define describe " +
+ "echo editfile embedded escape exec execute " +
+ "feedback flagger flush " +
+ "heading headsep " +
+ "instance " +
+ "linesize lno loboffset logsource long longchunksize " +
+ "markup " +
+ "native newpage numformat numwidth " +
+ "pagesize pause pno " +
+ "recsep recsepchar release repfooter repheader " +
+ "serveroutput shiftinout show showmode size spool sqlblanklines sqlcase sqlcode sqlcontinue sqlnumber " +
+ "sqlpluscompatibility sqlprefix sqlprompt sqlterminator suffix " +
+ "tab term termout time timing trimout trimspool ttitle " +
+ "underline " +
+ "verify version " +
+ "wrap";
+
+ CodeMirror.defineMIME("text/x-plsql", {
+ name: "plsql",
+ keywords: keywords(cKeywords),
+ functions: keywords(cFunctions),
+ types: keywords(cTypes),
+ sqlplus: keywords(cSqlplus)
+ });
+}());
diff --git a/codemirror/mode/properties/index.html b/codemirror/mode/properties/index.html
new file mode 100644
index 0000000..e21e02a
--- /dev/null
+++ b/codemirror/mode/properties/index.html
@@ -0,0 +1,41 @@
+
+
+
MIME types defined: text/x-properties,
+ text/x-ini.
MIME types defined: text/x-python.
MIME types defined: text/x-rsrc.
Development of the CodeMirror R mode was kindly sponsored + by Ubalo, who hold + the license.
+ + + diff --git a/codemirror/mode/r/r.js b/codemirror/mode/r/r.js new file mode 100644 index 0000000..6410efb --- /dev/null +++ b/codemirror/mode/r/r.js @@ -0,0 +1,141 @@ +CodeMirror.defineMode("r", function(config) { + function wordObj(str) { + var words = str.split(" "), res = {}; + for (var i = 0; i < words.length; ++i) res[words[i]] = true; + return res; + } + var atoms = wordObj("NULL NA Inf NaN NA_integer_ NA_real_ NA_complex_ NA_character_"); + var builtins = wordObj("list quote bquote eval return call parse deparse"); + var keywords = wordObj("if else repeat while function for in next break"); + var blockkeywords = wordObj("if else repeat while function for"); + var opChars = /[+\-*\/^<>=!&|~$:]/; + var curPunc; + + function tokenBase(stream, state) { + curPunc = null; + var ch = stream.next(); + if (ch == "#") { + stream.skipToEnd(); + return "comment"; + } else if (ch == "0" && stream.eat("x")) { + stream.eatWhile(/[\da-f]/i); + return "number"; + } else if (ch == "." && stream.eat(/\d/)) { + stream.match(/\d*(?:e[+\-]?\d+)?/); + return "number"; + } else if (/\d/.test(ch)) { + stream.match(/\d*(?:\.\d+)?(?:e[+\-]\d+)?L?/); + return "number"; + } else if (ch == "'" || ch == '"') { + state.tokenize = tokenString(ch); + return "string"; + } else if (ch == "." && stream.match(/.[.\d]+/)) { + return "keyword"; + } else if (/[\w\.]/.test(ch) && ch != "_") { + stream.eatWhile(/[\w\.]/); + var word = stream.current(); + if (atoms.propertyIsEnumerable(word)) return "atom"; + if (keywords.propertyIsEnumerable(word)) { + if (blockkeywords.propertyIsEnumerable(word)) curPunc = "block"; + return "keyword"; + } + if (builtins.propertyIsEnumerable(word)) return "builtin"; + return "variable"; + } else if (ch == "%") { + if (stream.skipTo("%")) stream.next(); + return "variable-2"; + } else if (ch == "<" && stream.eat("-")) { + return "arrow"; + } else if (ch == "=" && state.ctx.argList) { + return "arg-is"; + } else if (opChars.test(ch)) { + if (ch == "$") return "dollar"; + stream.eatWhile(opChars); + return "operator"; + } else if (/[\(\){}\[\];]/.test(ch)) { + curPunc = ch; + if (ch == ";") return "semi"; + return null; + } else { + return null; + } + } + + function tokenString(quote) { + return function(stream, state) { + if (stream.eat("\\")) { + var ch = stream.next(); + if (ch == "x") stream.match(/^[a-f0-9]{2}/i); + else if ((ch == "u" || ch == "U") && stream.eat("{") && stream.skipTo("}")) stream.next(); + else if (ch == "u") stream.match(/^[a-f0-9]{4}/i); + else if (ch == "U") stream.match(/^[a-f0-9]{8}/i); + else if (/[0-7]/.test(ch)) stream.match(/^[0-7]{1,2}/); + return "string-2"; + } else { + var next; + while ((next = stream.next()) != null) { + if (next == quote) { state.tokenize = tokenBase; break; } + if (next == "\\") { stream.backUp(1); break; } + } + return "string"; + } + }; + } + + function push(state, type, stream) { + state.ctx = {type: type, + indent: state.indent, + align: null, + column: stream.column(), + prev: state.ctx}; + } + function pop(state) { + state.indent = state.ctx.indent; + state.ctx = state.ctx.prev; + } + + return { + startState: function() { + return {tokenize: tokenBase, + ctx: {type: "top", + indent: -config.indentUnit, + align: false}, + indent: 0, + afterIdent: false}; + }, + + token: function(stream, state) { + if (stream.sol()) { + if (state.ctx.align == null) state.ctx.align = false; + state.indent = stream.indentation(); + } + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + if (style != "comment" && state.ctx.align == null) state.ctx.align = true; + + var ctype = state.ctx.type; + if ((curPunc == ";" || curPunc == "{" || curPunc == "}") && ctype == "block") pop(state); + if (curPunc == "{") push(state, "}", stream); + else if (curPunc == "(") { + push(state, ")", stream); + if (state.afterIdent) state.ctx.argList = true; + } + else if (curPunc == "[") push(state, "]", stream); + else if (curPunc == "block") push(state, "block", stream); + else if (curPunc == ctype) pop(state); + state.afterIdent = style == "variable" || style == "keyword"; + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase) return 0; + var firstChar = textAfter && textAfter.charAt(0), ctx = state.ctx, + closing = firstChar == ctx.type; + if (ctx.type == "block") return ctx.indent + (firstChar == "{" ? 0 : config.indentUnit); + else if (ctx.align) return ctx.column + (closing ? 0 : 1); + else return ctx.indent + (closing ? 0 : config.indentUnit); + } + }; +}); + +CodeMirror.defineMIME("text/x-rsrc", "r"); diff --git a/codemirror/mode/rpm/changes/changes.js b/codemirror/mode/rpm/changes/changes.js new file mode 100644 index 0000000..14a08d9 --- /dev/null +++ b/codemirror/mode/rpm/changes/changes.js @@ -0,0 +1,19 @@ +CodeMirror.defineMode("changes", function() { + var headerSeperator = /^-+$/; + var headerLine = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ?\d{1,2} \d{2}:\d{2}(:\d{2})? [A-Z]{3,4} \d{4} - /; + var simpleEmail = /^[\w+.-]+@[\w.-]+/; + + return { + token: function(stream) { + if (stream.sol()) { + if (stream.match(headerSeperator)) { return 'tag'; } + if (stream.match(headerLine)) { return 'tag'; } + } + if (stream.match(simpleEmail)) { return 'string'; } + stream.next(); + return null; + } + }; +}); + +CodeMirror.defineMIME("text/x-rpm-changes", "changes"); diff --git a/codemirror/mode/rpm/changes/index.html b/codemirror/mode/rpm/changes/index.html new file mode 100644 index 0000000..e0e2d87 --- /dev/null +++ b/codemirror/mode/rpm/changes/index.html @@ -0,0 +1,53 @@ + + + + +MIME types defined: text/x-rpm-changes.
MIME types defined: text/x-rpm-spec.
The reStructuredText mode supports one configuration parameter:
+verbatim (string)If python mode is available,
+ it will be used for highlighting blocks containing Python/IPython terminal
+ sessions (blocks starting with >>> (for Python) or
+ In [num]: (for IPython).
+
+
MIME types defined: text/x-rst.
MIME types defined: text/x-ruby.
Development of the CodeMirror Ruby mode was kindly sponsored + by Ubalo, who hold + the license.
+ + + diff --git a/codemirror/mode/ruby/ruby.js b/codemirror/mode/ruby/ruby.js new file mode 100644 index 0000000..d106a54 --- /dev/null +++ b/codemirror/mode/ruby/ruby.js @@ -0,0 +1,195 @@ +CodeMirror.defineMode("ruby", function(config) { + function wordObj(words) { + var o = {}; + for (var i = 0, e = words.length; i < e; ++i) o[words[i]] = true; + return o; + } + var keywords = wordObj([ + "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else", + "elsif", "END", "end", "ensure", "false", "for", "if", "in", "module", "next", "not", "or", + "redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef", "unless", + "until", "when", "while", "yield", "nil", "raise", "throw", "catch", "fail", "loop", "callcc", + "caller", "lambda", "proc", "public", "protected", "private", "require", "load", + "require_relative", "extend", "autoload" + ]); + var indentWords = wordObj(["def", "class", "case", "for", "while", "do", "module", "then", + "catch", "loop", "proc", "begin"]); + var dedentWords = wordObj(["end", "until"]); + var matching = {"[": "]", "{": "}", "(": ")"}; + var curPunc; + + function chain(newtok, stream, state) { + state.tokenize.push(newtok); + return newtok(stream, state); + } + + function tokenBase(stream, state) { + curPunc = null; + if (stream.sol() && stream.match("=begin") && stream.eol()) { + state.tokenize.push(readBlockComment); + return "comment"; + } + if (stream.eatSpace()) return null; + var ch = stream.next(), m; + if (ch == "`" || ch == "'" || ch == '"' || + (ch == "/" && !stream.eol() && stream.peek() != " ")) { + return chain(readQuoted(ch, "string", ch == '"' || ch == "`"), stream, state); + } else if (ch == "%") { + var style, embed = false; + if (stream.eat("s")) style = "atom"; + else if (stream.eat(/[WQ]/)) { style = "string"; embed = true; } + else if (stream.eat(/[wxqr]/)) style = "string"; + var delim = stream.eat(/[^\w\s]/); + if (!delim) return "operator"; + if (matching.propertyIsEnumerable(delim)) delim = matching[delim]; + return chain(readQuoted(delim, style, embed, true), stream, state); + } else if (ch == "#") { + stream.skipToEnd(); + return "comment"; + } else if (ch == "<" && (m = stream.match(/^<-?[\`\"\']?([a-zA-Z_?]\w*)[\`\"\']?(?:;|$)/))) { + return chain(readHereDoc(m[1]), stream, state); + } else if (ch == "0") { + if (stream.eat("x")) stream.eatWhile(/[\da-fA-F]/); + else if (stream.eat("b")) stream.eatWhile(/[01]/); + else stream.eatWhile(/[0-7]/); + return "number"; + } else if (/\d/.test(ch)) { + stream.match(/^[\d_]*(?:\.[\d_]+)?(?:[eE][+\-]?[\d_]+)?/); + return "number"; + } else if (ch == "?") { + while (stream.match(/^\\[CM]-/)) {} + if (stream.eat("\\")) stream.eatWhile(/\w/); + else stream.next(); + return "string"; + } else if (ch == ":") { + if (stream.eat("'")) return chain(readQuoted("'", "atom", false), stream, state); + if (stream.eat('"')) return chain(readQuoted('"', "atom", true), stream, state); + stream.eatWhile(/[\w\?]/); + return "atom"; + } else if (ch == "@") { + stream.eat("@"); + stream.eatWhile(/[\w\?]/); + return "variable-2"; + } else if (ch == "$") { + stream.next(); + stream.eatWhile(/[\w\?]/); + return "variable-3"; + } else if (/\w/.test(ch)) { + stream.eatWhile(/[\w\?]/); + if (stream.eat(":")) return "atom"; + return "ident"; + } else if (ch == "|" && (state.varList || state.lastTok == "{" || state.lastTok == "do")) { + curPunc = "|"; + return null; + } else if (/[\(\)\[\]{}\\;]/.test(ch)) { + curPunc = ch; + return null; + } else if (ch == "-" && stream.eat(">")) { + return "arrow"; + } else if (/[=+\-\/*:\.^%<>~|]/.test(ch)) { + stream.eatWhile(/[=+\-\/*:\.^%<>~|]/); + return "operator"; + } else { + return null; + } + } + + function tokenBaseUntilBrace() { + var depth = 1; + return function(stream, state) { + if (stream.peek() == "}") { + depth--; + if (depth == 0) { + state.tokenize.pop(); + return state.tokenize[state.tokenize.length-1](stream, state); + } + } else if (stream.peek() == "{") { + depth++; + } + return tokenBase(stream, state); + }; + } + function readQuoted(quote, style, embed, unescaped) { + return function(stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && (unescaped || !escaped)) { + state.tokenize.pop(); + break; + } + if (embed && ch == "#" && !escaped && stream.eat("{")) { + state.tokenize.push(tokenBaseUntilBrace(arguments.callee)); + break; + } + escaped = !escaped && ch == "\\"; + } + return style; + }; + } + function readHereDoc(phrase) { + return function(stream, state) { + if (stream.match(phrase)) state.tokenize.pop(); + else stream.skipToEnd(); + return "string"; + }; + } + function readBlockComment(stream, state) { + if (stream.sol() && stream.match("=end") && stream.eol()) + state.tokenize.pop(); + stream.skipToEnd(); + return "comment"; + } + + return { + startState: function() { + return {tokenize: [tokenBase], + indented: 0, + context: {type: "top", indented: -config.indentUnit}, + continuedLine: false, + lastTok: null, + varList: false}; + }, + + token: function(stream, state) { + if (stream.sol()) state.indented = stream.indentation(); + var style = state.tokenize[state.tokenize.length-1](stream, state), kwtype; + if (style == "ident") { + var word = stream.current(); + style = keywords.propertyIsEnumerable(stream.current()) ? "keyword" + : /^[A-Z]/.test(word) ? "tag" + : (state.lastTok == "def" || state.lastTok == "class" || state.varList) ? "def" + : "variable"; + if (indentWords.propertyIsEnumerable(word)) kwtype = "indent"; + else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent"; + else if ((word == "if" || word == "unless") && stream.column() == stream.indentation()) + kwtype = "indent"; + } + if (curPunc || (style && style != "comment")) state.lastTok = word || curPunc || style; + if (curPunc == "|") state.varList = !state.varList; + + if (kwtype == "indent" || /[\(\[\{]/.test(curPunc)) + state.context = {prev: state.context, type: curPunc || style, indented: state.indented}; + else if ((kwtype == "dedent" || /[\)\]\}]/.test(curPunc)) && state.context.prev) + state.context = state.context.prev; + + if (stream.eol()) + state.continuedLine = (curPunc == "\\" || style == "operator"); + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize[state.tokenize.length-1] != tokenBase) return 0; + var firstChar = textAfter && textAfter.charAt(0); + var ct = state.context; + var closing = ct.type == matching[firstChar] || + ct.type == "keyword" && /^(?:end|until|else|elsif|when|rescue)\b/.test(textAfter); + return ct.indented + (closing ? 0 : config.indentUnit) + + (state.continuedLine ? config.indentUnit : 0); + }, + electricChars: "}de" // enD and rescuE + + }; +}); + +CodeMirror.defineMIME("text/x-ruby", "ruby"); + diff --git a/codemirror/mode/rust/index.html b/codemirror/mode/rust/index.html new file mode 100644 index 0000000..a6d47fe --- /dev/null +++ b/codemirror/mode/rust/index.html @@ -0,0 +1,48 @@ + + + + +MIME types defined: text/x-rustsrc.
MIME types defined: text/x-scheme.
MIME types defined: text/x-sh.
MIME types defined: application/sieve.
Simple Smalltalk mode.
+ +MIME types defined: text/x-stsrc.
A plain text/Smarty mode which allows for custom delimiter tags (defaults to { and }).
+ +MIME types defined: text/x-smarty
MIME types defined: application/x-sparql-query.
MIME types defined:
+ text/x-sql,
+ text/x-mysql,
+ text/x-mariadb,
+ text/x-plsql.
+
MIME types defined: text/x-stex.
Parsing/Highlighting Tests: normal, verbose.
+ + + diff --git a/codemirror/mode/stex/stex.js b/codemirror/mode/stex/stex.js new file mode 100644 index 0000000..42ed82c --- /dev/null +++ b/codemirror/mode/stex/stex.js @@ -0,0 +1,175 @@ +/* + * Author: Constantin Jucovschi (c.jucovschi@jacobs-university.de) + * Licence: MIT + */ + +CodeMirror.defineMode("stex", function() +{ + function pushCommand(state, command) { + state.cmdState.push(command); + } + + function peekCommand(state) { + if (state.cmdState.length>0) + return state.cmdState[state.cmdState.length-1]; + else + return null; + } + + function popCommand(state) { + if (state.cmdState.length>0) { + var plug = state.cmdState.pop(); + plug.closeBracket(); + } + } + + function applyMostPowerful(state) { + var context = state.cmdState; + for (var i = context.length - 1; i >= 0; i--) { + var plug = context[i]; + if (plug.name=="DEFAULT") + continue; + return plug.styleIdentifier(); + } + return null; + } + + function addPluginPattern(pluginName, cmdStyle, brackets, styles) { + return function () { + this.name=pluginName; + this.bracketNo = 0; + this.style=cmdStyle; + this.styles = styles; + this.brackets = brackets; + + this.styleIdentifier = function() { + if (this.bracketNo<=this.styles.length) + return this.styles[this.bracketNo-1]; + else + return null; + }; + this.openBracket = function() { + this.bracketNo++; + return "bracket"; + }; + this.closeBracket = function() {}; + }; + } + + var plugins = new Array(); + + plugins["importmodule"] = addPluginPattern("importmodule", "tag", "{[", ["string", "builtin"]); + plugins["documentclass"] = addPluginPattern("documentclass", "tag", "{[", ["", "atom"]); + plugins["usepackage"] = addPluginPattern("documentclass", "tag", "[", ["atom"]); + plugins["begin"] = addPluginPattern("documentclass", "tag", "[", ["atom"]); + plugins["end"] = addPluginPattern("documentclass", "tag", "[", ["atom"]); + + plugins["DEFAULT"] = function () { + this.name="DEFAULT"; + this.style="tag"; + + this.styleIdentifier = this.openBracket = this.closeBracket = function() {}; + }; + + function setState(state, f) { + state.f = f; + } + + function normal(source, state) { + if (source.match(/^\\[a-zA-Z@]+/)) { + var cmdName = source.current(); + cmdName = cmdName.substr(1, cmdName.length-1); + var plug; + if (plugins.hasOwnProperty(cmdName)) { + plug = plugins[cmdName]; + } else { + plug = plugins["DEFAULT"]; + } + plug = new plug(); + pushCommand(state, plug); + setState(state, beginParams); + return plug.style; + } + + // escape characters + if (source.match(/^\\[$&%#{}_]/)) { + return "tag"; + } + + // white space control characters + if (source.match(/^\\[,;!\/]/)) { + return "tag"; + } + + var ch = source.next(); + if (ch == "%") { + // special case: % at end of its own line; stay in same state + if (!source.eol()) { + setState(state, inCComment); + } + return "comment"; + } + else if (ch=='}' || ch==']') { + plug = peekCommand(state); + if (plug) { + plug.closeBracket(ch); + setState(state, beginParams); + } else + return "error"; + return "bracket"; + } else if (ch=='{' || ch=='[') { + plug = plugins["DEFAULT"]; + plug = new plug(); + pushCommand(state, plug); + return "bracket"; + } + else if (/\d/.test(ch)) { + source.eatWhile(/[\w.%]/); + return "atom"; + } + else { + source.eatWhile(/[\w-_]/); + return applyMostPowerful(state); + } + } + + function inCComment(source, state) { + source.skipToEnd(); + setState(state, normal); + return "comment"; + } + + function beginParams(source, state) { + var ch = source.peek(); + if (ch == '{' || ch == '[') { + var lastPlug = peekCommand(state); + lastPlug.openBracket(ch); + source.eat(ch); + setState(state, normal); + return "bracket"; + } + if (/[ \t\r]/.test(ch)) { + source.eat(ch); + return null; + } + setState(state, normal); + lastPlug = peekCommand(state); + if (lastPlug) { + popCommand(state); + } + return normal(source, state); + } + + return { + startState: function() { return { f:normal, cmdState:[] }; }, + copyState: function(s) { return { f: s.f, cmdState: s.cmdState.slice(0, s.cmdState.length) }; }, + + token: function(stream, state) { + var t = state.f(stream, state); + return t; + } + }; +}); + +CodeMirror.defineMIME("text/x-stex", "stex"); +CodeMirror.defineMIME("text/x-latex", "stex"); diff --git a/codemirror/mode/stex/test.js b/codemirror/mode/stex/test.js new file mode 100644 index 0000000..c5a34f3 --- /dev/null +++ b/codemirror/mode/stex/test.js @@ -0,0 +1,343 @@ +var MT = ModeTest; +MT.modeName = 'stex'; +MT.modeOptions = {}; + +MT.testMode( + 'word', + 'foo', + [ + null, 'foo' + ] +); + +MT.testMode( + 'twoWords', + 'foo bar', + [ + null, 'foo bar' + ] +); + +MT.testMode( + 'beginEndDocument', + '\\begin{document}\n\\end{document}', + [ + 'tag', '\\begin', + 'bracket', '{', + 'atom', 'document', + 'bracket', '}', + 'tag', '\\end', + 'bracket', '{', + 'atom', 'document', + 'bracket', '}' + ] +); + +MT.testMode( + 'beginEndEquation', + '\\begin{equation}\n E=mc^2\n\\end{equation}', + [ + 'tag', '\\begin', + 'bracket', '{', + 'atom', 'equation', + 'bracket', '}', + null, ' E=mc^2', + 'tag', '\\end', + 'bracket', '{', + 'atom', 'equation', + 'bracket', '}' + ] +); + +MT.testMode( + 'beginModule', + '\\begin{module}[]', + [ + 'tag', '\\begin', + 'bracket', '{', + 'atom', 'module', + 'bracket', '}[]' + ] +); + +MT.testMode( + 'beginModuleId', + '\\begin{module}[id=bbt-size]', + [ + 'tag', '\\begin', + 'bracket', '{', + 'atom', 'module', + 'bracket', '}[', + null, 'id=bbt-size', + 'bracket', ']' + ] +); + +MT.testMode( + 'importModule', + '\\importmodule[b-b-t]{b-b-t}', + [ + 'tag', '\\importmodule', + 'bracket', '[', + 'string', 'b-b-t', + 'bracket', ']{', + 'builtin', 'b-b-t', + 'bracket', '}' + ] +); + +MT.testMode( + 'importModulePath', + '\\importmodule[\\KWARCslides{dmath/en/cardinality}]{card}', + [ + 'tag', '\\importmodule', + 'bracket', '[', + 'tag', '\\KWARCslides', + 'bracket', '{', + 'string', 'dmath/en/cardinality', + 'bracket', '}]{', + 'builtin', 'card', + 'bracket', '}' + ] +); + +MT.testMode( + 'psForPDF', + '\\PSforPDF[1]{#1}', // could treat #1 specially + [ + 'tag', '\\PSforPDF', + 'bracket', '[', + 'atom', '1', + 'bracket', ']{', + null, '#1', + 'bracket', '}' + ] +); + +MT.testMode( + 'comment', + '% foo', + [ + 'comment', '% foo' + ] +); + +MT.testMode( + 'tagComment', + '\\item% bar', + [ + 'tag', '\\item', + 'comment', '% bar' + ] +); + +MT.testMode( + 'commentTag', + ' % \\item', + [ + null, ' ', + 'comment', '% \\item' + ] +); + +MT.testMode( + 'commentLineBreak', + '%\nfoo', + [ + 'comment', '%', + null, 'foo' + ] +); + +MT.testMode( + 'tagErrorCurly', + '\\begin}{', + [ + 'tag', '\\begin', + 'error', '}', + 'bracket', '{' + ] +); + +MT.testMode( + 'tagErrorSquare', + '\\item]{', + [ + 'tag', '\\item', + 'error', ']', + 'bracket', '{' + ] +); + +MT.testMode( + 'commentCurly', + '% }', + [ + 'comment', '% }' + ] +); + +MT.testMode( + 'tagHash', + 'the \\# key', + [ + null, 'the ', + 'tag', '\\#', + null, ' key' + ] +); + +MT.testMode( + 'tagNumber', + 'a \\$5 stetson', + [ + null, 'a ', + 'tag', '\\$', + 'atom', 5, + null, ' stetson' + ] +); + +MT.testMode( + 'tagPercent', + '100\\% beef', + [ + 'atom', '100', + 'tag', '\\%', + null, ' beef' + ] +); + +MT.testMode( + 'tagAmpersand', + 'L \\& N', + [ + null, 'L ', + 'tag', '\\&', + null, ' N' + ] +); + +MT.testMode( + 'tagUnderscore', + 'foo\\_bar', + [ + null, 'foo', + 'tag', '\\_', + null, 'bar' + ] +); + +MT.testMode( + 'tagBracketOpen', + '\\emph{\\{}', + [ + 'tag', '\\emph', + 'bracket', '{', + 'tag', '\\{', + 'bracket', '}' + ] +); + +MT.testMode( + 'tagBracketClose', + '\\emph{\\}}', + [ + 'tag', '\\emph', + 'bracket', '{', + 'tag', '\\}', + 'bracket', '}' + ] +); + +MT.testMode( + 'tagLetterNumber', + 'section \\S1', + [ + null, 'section ', + 'tag', '\\S', + 'atom', '1' + ] +); + +MT.testMode( + 'textTagNumber', + 'para \\P2', + [ + null, 'para ', + 'tag', '\\P', + 'atom', '2' + ] +); + +MT.testMode( + 'thinspace', + 'x\\,y', // thinspace + [ + null, 'x', + 'tag', '\\,', + null, 'y' + ] +); + +MT.testMode( + 'thickspace', + 'x\\;y', // thickspace + [ + null, 'x', + 'tag', '\\;', + null, 'y' + ] +); + +MT.testMode( + 'negativeThinspace', + 'x\\!y', // negative thinspace + [ + null, 'x', + 'tag', '\\!', + null, 'y' + ] +); + +MT.testMode( + 'periodNotSentence', + 'J.\\ L.\\ is', // period not ending a sentence + [ + null, 'J.\\ L.\\ is' + ] +); // maybe could be better + +MT.testMode( + 'periodSentence', + 'X\\@. The', // period ending a sentence + [ + null, 'X', + 'tag', '\\@', + null, '. The' + ] +); + +MT.testMode( + 'italicCorrection', + '{\\em If\\/} I', // italic correction + [ + 'bracket', '{', + 'tag', '\\em', + null, ' If', + 'tag', '\\/', + 'bracket', '}', + null, ' I' + ] +); + +MT.testMode( + 'tagBracket', + '\\newcommand{\\pop}', + [ + 'tag', '\\newcommand', + 'bracket', '{', + 'tag', '\\pop', + 'bracket', '}' + ] +); \ No newline at end of file diff --git a/codemirror/mode/tiddlywiki/index.html b/codemirror/mode/tiddlywiki/index.html new file mode 100644 index 0000000..89ae858 --- /dev/null +++ b/codemirror/mode/tiddlywiki/index.html @@ -0,0 +1,142 @@ + + + + +TiddlyWiki mode supports a single configuration.
+ +MIME types defined: text/x-tiddlywiki.
MIME type defined: text/x-vb.
MIME types defined: text/vbscript.
MIME types defined: text/velocity.
Simple mode that tries to handle Verilog-like languages as well as it
+ can. Takes one configuration parameters: keywords, an
+ object whose property names are the keywords in the language.
MIME types defined: text/x-verilog (Verilog code).
The XML mode supports two configuration parameters:
+htmlMode (boolean)br) do not require a closing tag.alignCDATA (boolean)MIME types defined: application/xml, text/html.
MIME types defined: application/xquery.
Development of the CodeMirror XQuery mode was sponsored by + MarkLogic and developed by + Mike Brevoort. +
+ + + diff --git a/codemirror/mode/xquery/test.js b/codemirror/mode/xquery/test.js new file mode 100644 index 0000000..23ab3d1 --- /dev/null +++ b/codemirror/mode/xquery/test.js @@ -0,0 +1,77 @@ +// Initiate ModeTest and set defaults +var MT = ModeTest; +MT.modeName = "xquery"; +MT.modeOptions = {}; + +MT.testMode("eviltest", + 'xquery version "1.0-ml";\ + (: this is\ + : a \ + "comment" :)\ + let $let := <x attr="value">"test"<func>function() $var {function()} {$var}</func></x>\ + let $joe:=1\ + return element element {\ + attribute attribute { 1 },\ + element test { 'a' }, \ + attribute foo { "bar" },\ + fn:doc()[ foo/@bar eq $let ],\ + //x } \ + \ + (: a more \'evil\' test :)\ + (: Modified Blakeley example (: with nested comment :) ... :)\ + declare private function local:declare() {()};\ + declare private function local:private() {()};\ + declare private function local:function() {()};\ + declare private function local:local() {()};\ + let $let := <let>let $let := "let"</let>\ + return element element {\ + attribute attribute { try { xdmp:version() } catch($e) { xdmp:log($e) } },\ + attribute fn:doc { "bar" castable as xs:string },\ + element text { text { "text" } },\ + fn:doc()[ child::eq/(@bar | attribute::attribute) eq $let ],\ + //fn:doc\ + }', ["keyword","xquery",null," ","keyword","version",null," ","variable",""1","keyword",".","atom","0","keyword","-","variable","ml"","def variable",";",null," ","comment","(: this is : a \"comment\" :)",null," ","keyword","let",null," ","variable","$let",null," ","keyword",":=",null," ","variable","<x",null," ","variable","attr","keyword","=","variable",""value">"test"<func>","def variable",";function","","()",null," ","variable","$var",null," ","","{","keyword","function","","()}",null," ","","{","variable","$var","","}","variable","<","keyword","/","variable","func><","keyword","/","variable","x>",null," ","keyword","let",null," ","variable","$joe","keyword",":=","atom","1",null," ","keyword","return",null," ","keyword","element",null," ","variable","element",null," ","","{",null," ","keyword","attribute",null," ","variable","attribute",null," ","","{",null," ","atom","1",null," ","","},",null," ","keyword","element",null," ","variable","test",null," ","","{",null," ","variable","'a'",null," ","","},",null," ","keyword","attribute",null," ","variable","foo",null," ","","{",null," ","variable",""bar"",null," ","","},",null," ","def variable","fn:doc","","()[",null," ","variable","foo","keyword","/","variable","@bar",null," ","keyword","eq",null," ","variable","$let",null," ","","],",null," ","keyword","//","variable","x",null," ","","}",null," ","comment","(: a more 'evil' test :)",null," ","comment","(: Modified Blakeley example (: with nested comment :) ... :)",null," ","keyword","declare",null," ","keyword","private",null," ","keyword","function",null," ","def variable","local:declare","","()",null," ","","{()}","variable",";",null," ","keyword","declare",null," ","keyword","private",null," ","keyword","function",null," ","def variable","local:private","","()",null," ","","{()}","variable",";",null," ","keyword","declare",null," ","keyword","private",null," ","keyword","function",null," ","def variable","local:function","","()",null," ","","{()}","variable",";",null," ","keyword","declare",null," ","keyword","private",null," ","keyword","function",null," ","def variable","local:local","","()",null," ","","{()}","variable",";",null," ","keyword","let",null," ","variable","$let",null," ","keyword",":=",null," ","variable","<let>let",null," ","variable","$let",null," ","keyword",":=",null," ","variable",""let"<","keyword","/let","variable",">",null," ","keyword","return",null," ","keyword","element",null," ","variable","element",null," ","","{",null," ","keyword","attribute",null," ","variable","attribute",null," ","","{",null," ","keyword","try",null," ","","{",null," ","def variable","xdmp:version","","()",null," ","","}",null," ","keyword","catch","","(","variable","$e","",")",null," ","","{",null," ","def variable","xdmp:log","","(","variable","$e","",")",null," ","","}",null," ","","},",null," ","keyword","attribute",null," ","variable","fn:doc",null," ","","{",null," ","variable",""bar"",null," ","variable","castable",null," ","keyword","as",null," ","atom","xs:string",null," ","","},",null," ","keyword","element",null," ","variable","text",null," ","","{",null," ","keyword","text",null," ","","{",null," ","variable",""text"",null," ","","}",null," ","","},",null," ","def variable","fn:doc","","()[",null," ","qualifier","child::","variable","eq","keyword","/","","(","variable","@bar",null," ","keyword","|",null," ","qualifier","attribute::","variable","attribute","",")",null," ","keyword","eq",null," ","variable","$let",null," ","","],",null," ","keyword","//","variable","fn:doc",null," ","","}"]); + +MT.testMode("testEmptySequenceKeyword", + '"foo" instance of empty-sequence()', + ["string","\"foo\"",null," ","keyword","instance",null," ","keyword","of",null," ","keyword","empty-sequence","","()"]); + + +MT.testMode("testMultiAttr", + 'hello world
', + ["tag","","variable","hello",null," ","variable","world","tag","
"]); + +MT.testMode("test namespaced variable", + 'declare namespace e = "http://example.com/ANamespace";\ +declare variable $e:exampleComThisVarIsNotRecognized as element(*) external;', + ["keyword","declare",null," ","keyword","namespace",null," ","variable","e",null," ","keyword","=",null," ","string","\"http://example.com/ANamespace\"","variable",";declare",null," ","keyword","variable",null," ","variable","$e:exampleComThisVarIsNotRecognized",null," ","keyword","as",null," ","keyword","element","","(","keyword","*","",")",null," ","variable","external;"]); + +MT.testMode("test EQName variable", + 'declare variable $"http://www.example.com/ns/my":var := 12;\ +MIME types defined: text/x-yaml.
MIME type defined: text/x-z80.
A limited set of programmatic sanity tests for CodeMirror.
+ +Please enable JavaScript...
+ + + + + + + + + + + + + + + + + + + + + diff --git a/codemirror/test/lint/acorn.js b/codemirror/test/lint/acorn.js new file mode 100644 index 0000000..6323b1f --- /dev/null +++ b/codemirror/test/lint/acorn.js @@ -0,0 +1,1593 @@ +// Acorn is a tiny, fast JavaScript parser written in JavaScript. +// +// Acorn was written by Marijn Haverbeke and released under an MIT +// license. The Unicode regexps (for identifiers and whitespace) were +// taken from [Esprima](http://esprima.org) by Ariya Hidayat. +// +// Git repositories for Acorn are available at +// +// http://marijnhaverbeke.nl/git/acorn +// https://github.com/marijnh/acorn.git +// +// Please use the [github bug tracker][ghbt] to report issues. +// +// [ghbt]: https://github.com/marijnh/acorn/issues + +(function(exports) { + "use strict"; + + exports.version = "0.0.1"; + + // The main exported interface (under `window.acorn` when in the + // browser) is a `parse` function that takes a code string and + // returns an abstract syntax tree as specified by [Mozilla parser + // API][api], with the caveat that the SpiderMonkey-specific syntax + // (`let`, `yield`, inline XML, etc) is not recognized. + // + // [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API + + var options, input, inputLen, sourceFile; + + exports.parse = function(inpt, opts) { + input = String(inpt); inputLen = input.length; + options = opts || {}; + for (var opt in defaultOptions) if (!options.hasOwnProperty(opt)) + options[opt] = defaultOptions[opt]; + sourceFile = options.sourceFile || null; + return parseTopLevel(options.program); + }; + + // A second optional argument can be given to further configure + // the parser process. These options are recognized: + + var defaultOptions = exports.defaultOptions = { + // `ecmaVersion` indicates the ECMAScript version to parse. Must + // be either 3 or 5. This + // influences support for strict mode, the set of reserved words, and + // support for getters and setter. + ecmaVersion: 5, + // Turn on `strictSemicolons` to prevent the parser from doing + // automatic semicolon insertion. + strictSemicolons: false, + // When `allowTrailingCommas` is false, the parser will not allow + // trailing commas in array and object literals. + allowTrailingCommas: true, + // By default, reserved words are not enforced. Enable + // `forbidReserved` to enforce them. + forbidReserved: false, + // When `trackComments` is turned on, the parser will attach + // `commentsBefore` and `commentsAfter` properties to AST nodes + // holding arrays of strings. A single comment may appear in both + // a `commentsBefore` and `commentsAfter` array (of the nodes + // after and before it), but never twice in the before (or after) + // array of different nodes. + trackComments: false, + // When `locations` is on, `loc` properties holding objects with + // `start` and `end` properties in `{line, column}` form (with + // line being 1-based and column 0-based) will be attached to the + // nodes. + locations: false, + // Nodes have their start and end characters offsets recorded in + // `start` and `end` properties (directly on the node, rather than + // the `loc` object, which holds line/column data. To also add a + // [semi-standardized][range] `range` property holding a `[start, + // end]` array with the same numbers, set the `ranges` option to + // `true`. + // + // [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678 + ranges: false, + // It is possible to parse multiple files into a single AST by + // passing the tree produced by parsing the first file as + // `program` option in subsequent parses. This will add the + // toplevel forms of the parsed file to the `Program` (top) node + // of an existing parse tree. + program: null, + // When `location` is on, you can pass this to record the source + // file in every node's `loc` object. + sourceFile: null + }; + + // The `getLineInfo` function is mostly useful when the + // `locations` option is off (for performance reasons) and you + // want to find the line/column position for a given character + // offset. `input` should be the code string that the offset refers + // into. + + var getLineInfo = exports.getLineInfo = function(input, offset) { + for (var line = 1, cur = 0;;) { + lineBreak.lastIndex = cur; + var match = lineBreak.exec(input); + if (match && match.index < offset) { + ++line; + cur = match.index + match[0].length; + } else break; + } + return {line: line, column: offset - cur}; + }; + + // Acorn is organized as a tokenizer and a recursive-descent parser. + // Both use (closure-)global variables to keep their state and + // communicate. We already saw the `options`, `input`, and + // `inputLen` variables above (set in `parse`). + + // The current position of the tokenizer in the input. + + var tokPos; + + // The start and end offsets of the current token. + + var tokStart, tokEnd; + + // When `options.locations` is true, these hold objects + // containing the tokens start and end line/column pairs. + + var tokStartLoc, tokEndLoc; + + // The type and value of the current token. Token types are objects, + // named by variables against which they can be compared, and + // holding properties that describe them (indicating, for example, + // the precedence of an infix operator, and the original name of a + // keyword token). The kind of value that's held in `tokVal` depends + // on the type of the token. For literals, it is the literal value, + // for operators, the operator name, and so on. + + var tokType, tokVal; + + // These are used to hold arrays of comments when + // `options.trackComments` is true. + + var tokCommentsBefore, tokCommentsAfter; + + // Interal state for the tokenizer. To distinguish between division + // operators and regular expressions, it remembers whether the last + // token was one that is allowed to be followed by an expression. + // (If it is, a slash is probably a regexp, if it isn't it's a + // division operator. See the `parseStatement` function for a + // caveat.) + + var tokRegexpAllowed, tokComments; + + // When `options.locations` is true, these are used to keep + // track of the current line, and know when a new line has been + // entered. See the `curLineLoc` function. + + var tokCurLine, tokLineStart, tokLineStartNext; + + // These store the position of the previous token, which is useful + // when finishing a node and assigning its `end` position. + + var lastStart, lastEnd, lastEndLoc; + + // This is the parser's state. `inFunction` is used to reject + // `return` statements outside of functions, `labels` to verify that + // `break` and `continue` have somewhere to jump to, and `strict` + // indicates whether strict mode is on. + + var inFunction, labels, strict; + + // This function is used to raise exceptions on parse errors. It + // takes either a `{line, column}` object or an offset integer (into + // the current `input`) as `pos` argument. It attaches the position + // to the end of the error message, and then raises a `SyntaxError` + // with that message. + + function raise(pos, message) { + if (typeof pos == "number") pos = getLineInfo(input, pos); + message += " (" + pos.line + ":" + pos.column + ")"; + throw new SyntaxError(message); + } + + // ## Token types + + // The assignment of fine-grained, information-carrying type objects + // allows the tokenizer to store the information it has about a + // token in a way that is very cheap for the parser to look up. + + // All token type variables start with an underscore, to make them + // easy to recognize. + + // These are the general types. The `type` property is only used to + // make them recognizeable when debugging. + + var _num = {type: "num"}, _regexp = {type: "regexp"}, _string = {type: "string"}; + var _name = {type: "name"}, _eof = {type: "eof"}; + + // Keyword tokens. The `keyword` property (also used in keyword-like + // operators) indicates that the token originated from an + // identifier-like word, which is used when parsing property names. + // + // The `beforeExpr` property is used to disambiguate between regular + // expressions and divisions. It is set on all token types that can + // be followed by an expression (thus, a slash after them would be a + // regular expression). + // + // `isLoop` marks a keyword as starting a loop, which is important + // to know when parsing a label, in order to allow or disallow + // continue jumps to that label. + + var _break = {keyword: "break"}, _case = {keyword: "case", beforeExpr: true}, _catch = {keyword: "catch"}; + var _continue = {keyword: "continue"}, _debugger = {keyword: "debugger"}, _default = {keyword: "default"}; + var _do = {keyword: "do", isLoop: true}, _else = {keyword: "else", beforeExpr: true}; + var _finally = {keyword: "finally"}, _for = {keyword: "for", isLoop: true}, _function = {keyword: "function"}; + var _if = {keyword: "if"}, _return = {keyword: "return", beforeExpr: true}, _switch = {keyword: "switch"}; + var _throw = {keyword: "throw", beforeExpr: true}, _try = {keyword: "try"}, _var = {keyword: "var"}; + var _while = {keyword: "while", isLoop: true}, _with = {keyword: "with"}, _new = {keyword: "new", beforeExpr: true}; + var _this = {keyword: "this"}; + + // The keywords that denote values. + + var _null = {keyword: "null", atomValue: null}, _true = {keyword: "true", atomValue: true}; + var _false = {keyword: "false", atomValue: false}; + + // Some keywords are treated as regular operators. `in` sometimes + // (when parsing `for`) needs to be tested against specifically, so + // we assign a variable name to it for quick comparing. + + var _in = {keyword: "in", binop: 7, beforeExpr: true}; + + // Map keyword names to token types. + + var keywordTypes = {"break": _break, "case": _case, "catch": _catch, + "continue": _continue, "debugger": _debugger, "default": _default, + "do": _do, "else": _else, "finally": _finally, "for": _for, + "function": _function, "if": _if, "return": _return, "switch": _switch, + "throw": _throw, "try": _try, "var": _var, "while": _while, "with": _with, + "null": _null, "true": _true, "false": _false, "new": _new, "in": _in, + "instanceof": {keyword: "instanceof", binop: 7}, "this": _this, + "typeof": {keyword: "typeof", prefix: true}, + "void": {keyword: "void", prefix: true}, + "delete": {keyword: "delete", prefix: true}}; + + // Punctuation token types. Again, the `type` property is purely for debugging. + + var _bracketL = {type: "[", beforeExpr: true}, _bracketR = {type: "]"}, _braceL = {type: "{", beforeExpr: true}; + var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"}; + var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true}; + var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true}; + + // Operators. These carry several kinds of properties to help the + // parser use them properly (the presence of these properties is + // what categorizes them as operators). + // + // `binop`, when present, specifies that this operator is a binary + // operator, and will refer to its precedence. + // + // `prefix` and `postfix` mark the operator as a prefix or postfix + // unary operator. `isUpdate` specifies that the node produced by + // the operator should be of type UpdateExpression rather than + // simply UnaryExpression (`++` and `--`). + // + // `isAssign` marks all of `=`, `+=`, `-=` etcetera, which act as + // binary operators with a very low precedence, that should result + // in AssignmentExpression nodes. + + var _slash = {binop: 10, beforeExpr: true}, _eq = {isAssign: true, beforeExpr: true}; + var _assign = {isAssign: true, beforeExpr: true}, _plusmin = {binop: 9, prefix: true, beforeExpr: true}; + var _incdec = {postfix: true, prefix: true, isUpdate: true}, _prefix = {prefix: true, beforeExpr: true}; + var _bin1 = {binop: 1, beforeExpr: true}, _bin2 = {binop: 2, beforeExpr: true}; + var _bin3 = {binop: 3, beforeExpr: true}, _bin4 = {binop: 4, beforeExpr: true}; + var _bin5 = {binop: 5, beforeExpr: true}, _bin6 = {binop: 6, beforeExpr: true}; + var _bin7 = {binop: 7, beforeExpr: true}, _bin8 = {binop: 8, beforeExpr: true}; + var _bin10 = {binop: 10, beforeExpr: true}; + + // This is a trick taken from Esprima. It turns out that, on + // non-Chrome browsers, to check whether a string is in a set, a + // predicate containing a big ugly `switch` statement is faster than + // a regular expression, and on Chrome the two are about on par. + // This function uses `eval` (non-lexical) to produce such a + // predicate from a space-separated string of words. + // + // It starts by sorting the words by length. + + function makePredicate(words) { + words = words.split(" "); + var f = "", cats = []; + out: for (var i = 0; i < words.length; ++i) { + for (var j = 0; j < cats.length; ++j) + if (cats[j][0].length == words[i].length) { + cats[j].push(words[i]); + continue out; + } + cats.push([words[i]]); + } + function compareTo(arr) { + if (arr.length == 1) return f += "return str === " + JSON.stringify(arr[0]) + ";"; + f += "switch(str){"; + for (var i = 0; i < arr.length; ++i) f += "case " + JSON.stringify(arr[i]) + ":"; + f += "return true}return false;"; + } + + // When there are more than three length categories, an outer + // switch first dispatches on the lengths, to save on comparisons. + + if (cats.length > 3) { + cats.sort(function(a, b) {return b.length - a.length;}); + f += "switch(str.length){"; + for (var i = 0; i < cats.length; ++i) { + var cat = cats[i]; + f += "case " + cat[0].length + ":"; + compareTo(cat); + } + f += "}"; + + // Otherwise, simply generate a flat `switch` statement. + + } else { + compareTo(words); + } + return new Function("str", f); + } + + // The ECMAScript 3 reserved word list. + + var isReservedWord3 = makePredicate("abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile"); + + // ECMAScript 5 reserved words. + + var isReservedWord5 = makePredicate("class enum extends super const export import"); + + // The additional reserved words in strict mode. + + var isStrictReservedWord = makePredicate("implements interface let package private protected public static yield"); + + // The forbidden variable names in strict mode. + + var isStrictBadIdWord = makePredicate("eval arguments"); + + // And the keywords. + + var isKeyword = makePredicate("break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this"); + + // ## Character categories + + // Big ugly regular expressions that match characters in the + // whitespace, identifier, and identifier-start categories. These + // are only applied when a character is found to actually have a + // code point above 128. + + var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/; + var nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc"; + var nonASCIIidentifierChars = "\u0371-\u0374\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u0620-\u0649\u0672-\u06d3\u06e7-\u06e8\u06fb-\u06fc\u0730-\u074a\u0800-\u0814\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0840-\u0857\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962-\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09d7\u09df-\u09e0\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5f-\u0b60\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2-\u0ce3\u0ce6-\u0cef\u0d02\u0d03\u0d46-\u0d48\u0d57\u0d62-\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e34-\u0e3a\u0e40-\u0e45\u0e50-\u0e59\u0eb4-\u0eb9\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f41-\u0f47\u0f71-\u0f84\u0f86-\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1029\u1040-\u1049\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u170e-\u1710\u1720-\u1730\u1740-\u1750\u1772\u1773\u1780-\u17b2\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1920-\u192b\u1930-\u193b\u1951-\u196d\u19b0-\u19c0\u19c8-\u19c9\u19d0-\u19d9\u1a00-\u1a15\u1a20-\u1a53\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1b46-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1bb0-\u1bb9\u1be6-\u1bf3\u1c00-\u1c22\u1c40-\u1c49\u1c5b-\u1c7d\u1cd0-\u1cd2\u1d00-\u1dbe\u1e01-\u1f15\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2d81-\u2d96\u2de0-\u2dff\u3021-\u3028\u3099\u309a\ua640-\ua66d\ua674-\ua67d\ua69f\ua6f0-\ua6f1\ua7f8-\ua800\ua806\ua80b\ua823-\ua827\ua880-\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8f3-\ua8f7\ua900-\ua909\ua926-\ua92d\ua930-\ua945\ua980-\ua983\ua9b3-\ua9c0\uaa00-\uaa27\uaa40-\uaa41\uaa4c-\uaa4d\uaa50-\uaa59\uaa7b\uaae0-\uaae9\uaaf2-\uaaf3\uabc0-\uabe1\uabec\uabed\uabf0-\uabf9\ufb20-\ufb28\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f"; + var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]"); + var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]"); + + // Whether a single character denotes a newline. + + var newline = /[\n\r\u2028\u2029]/; + + // Matches a whole line break (where CRLF is considered a single + // line break). Used to count lines. + + var lineBreak = /\r\n|[\n\r\u2028\u2029]/g; + + // Test whether a given character code starts an identifier. + + function isIdentifierStart(code) { + if (code < 65) return code === 36; + if (code < 91) return true; + if (code < 97) return code === 95; + if (code < 123)return true; + return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code)); + } + + // Test whether a given character is part of an identifier. + + function isIdentifierChar(code) { + if (code < 48) return code === 36; + if (code < 58) return true; + if (code < 65) return false; + if (code < 91) return true; + if (code < 97) return code === 95; + if (code < 123)return true; + return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code)); + } + + // ## Tokenizer + + // These are used when `options.locations` is on, in order to track + // the current line number and start of line offset, in order to set + // `tokStartLoc` and `tokEndLoc`. + + function nextLineStart() { + lineBreak.lastIndex = tokLineStart; + var match = lineBreak.exec(input); + return match ? match.index + match[0].length : input.length + 1; + } + + function curLineLoc() { + while (tokLineStartNext <= tokPos) { + ++tokCurLine; + tokLineStart = tokLineStartNext; + tokLineStartNext = nextLineStart(); + } + return {line: tokCurLine, column: tokPos - tokLineStart}; + } + + // Reset the token state. Used at the start of a parse. + + function initTokenState() { + tokCurLine = 1; + tokPos = tokLineStart = 0; + tokLineStartNext = nextLineStart(); + tokRegexpAllowed = true; + tokComments = null; + skipSpace(); + } + + // Called at the end of every token. Sets `tokEnd`, `tokVal`, + // `tokCommentsAfter`, and `tokRegexpAllowed`, and skips the space + // after the token, so that the next one's `tokStart` will point at + // the right position. + + function finishToken(type, val) { + tokEnd = tokPos; + if (options.locations) tokEndLoc = curLineLoc(); + tokType = type; + skipSpace(); + tokVal = val; + tokCommentsAfter = tokComments; + tokRegexpAllowed = type.beforeExpr; + } + + function skipBlockComment() { + var end = input.indexOf("*/", tokPos += 2); + if (end === -1) raise(tokPos - 2, "Unterminated comment"); + if (options.trackComments) + (tokComments || (tokComments = [])).push(input.slice(tokPos, end)); + tokPos = end + 2; + } + + function skipLineComment() { + var start = tokPos; + var ch = input.charCodeAt(tokPos+=2); + while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8329) { + ++tokPos; + ch = input.charCodeAt(tokPos); + } + (tokComments || (tokComments = [])).push(input.slice(start, tokPos)); + } + + // Called at the start of the parse and after every token. Skips + // whitespace and comments, and, if `options.trackComments` is on, + // will store all skipped comments in `tokComments`. + + function skipSpace() { + tokComments = null; + while (tokPos < inputLen) { + var ch = input.charCodeAt(tokPos); + if (ch === 47) { // '/' + var next = input.charCodeAt(tokPos+1); + if (next === 42) { // '*' + skipBlockComment(); + } else if (next === 47) { // '/' + skipLineComment(); + } else break; + } else if (ch < 14 && ch > 8) { + ++tokPos; + } else if (ch === 32 || ch === 160) { // ' ', '\xa0' + ++tokPos; + } else if (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) { + ++tokPos; + } else { + break; + } + } + } + + // ### Token reading + + // This is the function that is called to fetch the next token. It + // is somewhat obscure, because it works in character codes rather + // than characters, and because operator parsing has been inlined + // into it. + // + // All in the name of speed. + // + // The `forceRegexp` parameter is used in the one case where the + // `tokRegexpAllowed` trick does not work. See `parseStatement`. + + function readToken(forceRegexp) { + tokStart = tokPos; + if (options.locations) tokStartLoc = curLineLoc(); + tokCommentsBefore = tokComments; + if (forceRegexp) return readRegexp(); + if (tokPos >= inputLen) return finishToken(_eof); + + var code = input.charCodeAt(tokPos); + // Identifier or keyword. '\uXXXX' sequences are allowed in + // identifiers, so '\' also dispatches to that. + if (isIdentifierStart(code) || code === 92 /* '\' */) return readWord(); + var next = input.charCodeAt(tokPos+1); + + switch(code) { + // The interpretation of a dot depends on whether it is followed + // by a digit. + case 46: // '.' + if (next >= 48 && next <= 57) return readNumber(String.fromCharCode(code)); + ++tokPos; + return finishToken(_dot); + + // Punctuation tokens. + case 40: ++tokPos; return finishToken(_parenL); + case 41: ++tokPos; return finishToken(_parenR); + case 59: ++tokPos; return finishToken(_semi); + case 44: ++tokPos; return finishToken(_comma); + case 91: ++tokPos; return finishToken(_bracketL); + case 93: ++tokPos; return finishToken(_bracketR); + case 123: ++tokPos; return finishToken(_braceL); + case 125: ++tokPos; return finishToken(_braceR); + case 58: ++tokPos; return finishToken(_colon); + case 63: ++tokPos; return finishToken(_question); + + // '0x' is a hexadecimal number. + case 48: // '0' + if (next === 120 || next === 88) return readHexNumber(); + // Anything else beginning with a digit is an integer, octal + // number, or float. + case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: // 1-9 + return readNumber(String.fromCharCode(code)); + + // Quotes produce strings. + case 34: case 39: // '"', "'" + return readString(code); + + // Operators are parsed inline in tiny state machines. '=' (61) is + // often referred to. `finishOp` simply skips the amount of + // characters it is given as second argument, and returns a token + // of the type given by its first argument. + + case 47: // '/' + if (tokRegexpAllowed) {++tokPos; return readRegexp();} + if (next === 61) return finishOp(_assign, 2); + return finishOp(_slash, 1); + + case 37: case 42: // '%*' + if (next === 61) return finishOp(_assign, 2); + return finishOp(_bin10, 1); + + case 124: case 38: // '|&' + if (next === code) return finishOp(code === 124 ? _bin1 : _bin2, 2); + if (next === 61) return finishOp(_assign, 2); + return finishOp(code === 124 ? _bin3 : _bin5, 1); + + case 94: // '^' + if (next === 61) return finishOp(_assign, 2); + return finishOp(_bin4, 1); + + case 43: case 45: // '+-' + if (next === code) return finishOp(_incdec, 2); + if (next === 61) return finishOp(_assign, 2); + return finishOp(_plusmin, 1); + + case 60: case 62: // '<>' + var size = 1; + if (next === code) { + size = code === 62 && input.charCodeAt(tokPos+2) === 62 ? 3 : 2; + if (input.charCodeAt(tokPos + size) === 61) return finishOp(_assign, size + 1); + return finishOp(_bin8, size); + } + if (next === 61) + size = input.charCodeAt(tokPos+2) === 61 ? 3 : 2; + return finishOp(_bin7, size); + + case 61: case 33: // '=!' + if (next === 61) return finishOp(_bin6, input.charCodeAt(tokPos+2) === 61 ? 3 : 2); + return finishOp(code === 61 ? _eq : _prefix, 1); + + case 126: // '~' + return finishOp(_prefix, 1); + } + + // If we are here, we either found a non-ASCII identifier + // character, or something that's entirely disallowed. + var ch = String.fromCharCode(code); + if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return readWord(); + raise(tokPos, "Unexpected character '" + ch + "'"); + } + + function finishOp(type, size) { + var str = input.slice(tokPos, tokPos + size); + tokPos += size; + finishToken(type, str); + } + + // Parse a regular expression. Some context-awareness is necessary, + // since a '/' inside a '[]' set does not end the expression. + + function readRegexp() { + var content = "", escaped, inClass, start = tokPos; + for (;;) { + if (tokPos >= inputLen) raise(start, "Unterminated regular expression"); + var ch = input.charAt(tokPos); + if (newline.test(ch)) raise(start, "Unterminated regular expression"); + if (!escaped) { + if (ch === "[") inClass = true; + else if (ch === "]" && inClass) inClass = false; + else if (ch === "/" && !inClass) break; + escaped = ch === "\\"; + } else escaped = false; + ++tokPos; + } + var content = input.slice(start, tokPos); + ++tokPos; + // Need to use `readWord1` because '\uXXXX' sequences are allowed + // here (don't ask). + var mods = readWord1(); + if (mods && !/^[gmsiy]*$/.test(mods)) raise(start, "Invalid regexp flag"); + return finishToken(_regexp, new RegExp(content, mods)); + } + + // Read an integer in the given radix. Return null if zero digits + // were read, the integer value otherwise. When `len` is given, this + // will return `null` unless the integer has exactly `len` digits. + + function readInt(radix, len) { + var start = tokPos, total = 0; + for (;;) { + var code = input.charCodeAt(tokPos), val; + if (code >= 97) val = code - 97 + 10; // a + else if (code >= 65) val = code - 65 + 10; // A + else if (code >= 48 && code <= 57) val = code - 48; // 0-9 + else val = Infinity; + if (val >= radix) break; + ++tokPos; + total = total * radix + val; + } + if (tokPos === start || len != null && tokPos - start !== len) return null; + + return total; + } + + function readHexNumber() { + tokPos += 2; // 0x + var val = readInt(16); + if (val == null) raise(tokStart + 2, "Expected hexadecimal number"); + if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number"); + return finishToken(_num, val); + } + + // Read an integer, octal integer, or floating-point number. + + function readNumber(ch) { + var start = tokPos, isFloat = ch === "."; + if (!isFloat && readInt(10) == null) raise(start, "Invalid number"); + if (isFloat || input.charAt(tokPos) === ".") { + var next = input.charAt(++tokPos); + if (next === "-" || next === "+") ++tokPos; + if (readInt(10) === null && ch === ".") raise(start, "Invalid number"); + isFloat = true; + } + if (/e/i.test(input.charAt(tokPos))) { + var next = input.charAt(++tokPos); + if (next === "-" || next === "+") ++tokPos; + if (readInt(10) === null) raise(start, "Invalid number") + isFloat = true; + } + if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number"); + + var str = input.slice(start, tokPos), val; + if (isFloat) val = parseFloat(str); + else if (ch !== "0" || str.length === 1) val = parseInt(str, 10); + else if (/[89]/.test(str) || strict) raise(start, "Invalid number"); + else val = parseInt(str, 8); + return finishToken(_num, val); + } + + // Read a string value, interpreting backslash-escapes. + + function readString(quote) { + tokPos++; + var str = []; + for (;;) { + if (tokPos >= inputLen) raise(tokStart, "Unterminated string constant"); + var ch = input.charCodeAt(tokPos); + if (ch === quote) { + ++tokPos; + return finishToken(_string, String.fromCharCode.apply(null, str)); + } + if (ch === 92) { // '\' + ch = input.charCodeAt(++tokPos); + var octal = /^[0-7]+/.exec(input.slice(tokPos, tokPos + 3)); + if (octal) octal = octal[0]; + while (octal && parseInt(octal, 8) > 255) octal = octal.slice(0, octal.length - 1); + if (octal === "0") octal = null; + ++tokPos; + if (octal) { + if (strict) raise(tokPos - 2, "Octal literal in strict mode"); + str.push(parseInt(octal, 8)); + tokPos += octal.length - 1; + } else { + switch (ch) { + case 110: str.push(10); break; // 'n' -> '\n' + case 114: str.push(13); break; // 'r' -> '\r' + case 120: str.push(readHexChar(2)); break; // 'x' + case 117: str.push(readHexChar(4)); break; // 'u' + case 85: str.push(readHexChar(8)); break; // 'U' + case 116: str.push(9); break; // 't' -> '\t' + case 98: str.push(8); break; // 'b' -> '\b' + case 118: str.push(11); break; // 'v' -> '\u000b' + case 102: str.push(12); break; // 'f' -> '\f' + case 48: str.push(0); break; // 0 -> '\0' + case 13: if (input.charCodeAt(tokPos) === 10) ++tokPos; // '\r\n' + case 10: break; // ' \n' + default: str.push(ch); break; + } + } + } else { + if (ch === 13 || ch === 10 || ch === 8232 || ch === 8329) raise(tokStart, "Unterminated string constant"); + if (ch !== 92) str.push(ch); // '\' + ++tokPos; + } + } + } + + // Used to read character escape sequences ('\x', '\u', '\U'). + + function readHexChar(len) { + var n = readInt(16, len); + if (n === null) raise(tokStart, "Bad character escape sequence"); + return n; + } + + // Used to signal to callers of `readWord1` whether the word + // contained any escape sequences. This is needed because words with + // escape sequences must not be interpreted as keywords. + + var containsEsc; + + // Read an identifier, and return it as a string. Sets `containsEsc` + // to whether the word contained a '\u' escape. + // + // Only builds up the word character-by-character when it actually + // containeds an escape, as a micro-optimization. + + function readWord1() { + containsEsc = false; + var word, first = true, start = tokPos; + for (;;) { + var ch = input.charCodeAt(tokPos); + if (isIdentifierChar(ch)) { + if (containsEsc) word += input.charAt(tokPos); + ++tokPos; + } else if (ch === 92) { // "\" + if (!containsEsc) word = input.slice(start, tokPos); + containsEsc = true; + if (input.charCodeAt(++tokPos) != 117) // "u" + raise(tokPos, "Expecting Unicode escape sequence \\uXXXX"); + ++tokPos; + var esc = readHexChar(4); + var escStr = String.fromCharCode(esc); + if (!escStr) raise(tokPos - 1, "Invalid Unicode escape"); + if (!(first ? isIdentifierStart(esc) : isIdentifierChar(esc))) + raise(tokPos - 4, "Invalid Unicode escape"); + word += escStr; + } else { + break; + } + first = false; + } + return containsEsc ? word : input.slice(start, tokPos); + } + + // Read an identifier or keyword token. Will check for reserved + // words when necessary. + + function readWord() { + var word = readWord1(); + var type = _name; + if (!containsEsc) { + if (isKeyword(word)) type = keywordTypes[word]; + else if (options.forbidReserved && + (options.ecmaVersion === 3 ? isReservedWord3 : isReservedWord5)(word) || + strict && isStrictReservedWord(word)) + raise(tokStart, "The keyword '" + word + "' is reserved"); + } + return finishToken(type, word); + } + + // ## Parser + + // A recursive descent parser operates by defining functions for all + // syntactic elements, and recursively calling those, each function + // advancing the input stream and returning an AST node. Precedence + // of constructs (for example, the fact that `!x[1]` means `!(x[1])` + // instead of `(!x)[1]` is handled by the fact that the parser + // function that parses unary prefix operators is called first, and + // in turn calls the function that parses `[]` subscripts — that + // way, it'll receive the node for `x[1]` already parsed, and wraps + // *that* in the unary operator node. + // + // Acorn uses an [operator precedence parser][opp] to handle binary + // operator precedence, because it is much more compact than using + // the technique outlined above, which uses different, nesting + // functions to specify precedence, for all of the ten binary + // precedence levels that JavaScript defines. + // + // [opp]: http://en.wikipedia.org/wiki/Operator-precedence_parser + + // ### Parser utilities + + // Continue to the next token. + + function next() { + lastStart = tokStart; + lastEnd = tokEnd; + lastEndLoc = tokEndLoc; + readToken(); + } + + // Enter strict mode. Re-reads the next token to please pedantic + // tests ("use strict"; 010; -- should fail). + + function setStrict(strct) { + strict = strct; + tokPos = lastEnd; + skipSpace(); + readToken(); + } + + // Start an AST node, attaching a start offset and optionally a + // `commentsBefore` property to it. + + function startNode() { + var node = {type: null, start: tokStart, end: null}; + if (options.trackComments && tokCommentsBefore) { + node.commentsBefore = tokCommentsBefore; + tokCommentsBefore = null; + } + if (options.locations) + node.loc = {start: tokStartLoc, end: null, source: sourceFile}; + if (options.ranges) + node.range = [tokStart, 0]; + return node; + } + + // Start a node whose start offset/comments information should be + // based on the start of another node. For example, a binary + // operator node is only started after its left-hand side has + // already been parsed. + + function startNodeFrom(other) { + var node = {type: null, start: other.start}; + if (other.commentsBefore) { + node.commentsBefore = other.commentsBefore; + other.commentsBefore = null; + } + if (options.locations) + node.loc = {start: other.loc.start, end: null, source: other.loc.source}; + if (options.ranges) + node.range = [other.range[0], 0]; + + return node; + } + + // Finish an AST node, adding `type`, `end`, and `commentsAfter` + // properties. + // + // We keep track of the last node that we finished, in order + // 'bubble' `commentsAfter` properties up to the biggest node. I.e. + // in '`1 + 1 // foo', the comment should be attached to the binary + // operator node, not the second literal node. + + var lastFinishedNode; + + function finishNode(node, type) { + node.type = type; + node.end = lastEnd; + if (options.trackComments) { + if (tokCommentsAfter) { + node.commentsAfter = tokCommentsAfter; + tokCommentsAfter = null; + } else if (lastFinishedNode && lastFinishedNode.end === lastEnd) { + node.commentsAfter = lastFinishedNode.commentsAfter; + lastFinishedNode.commentsAfter = null; + } + lastFinishedNode = node; + } + if (options.locations) + node.loc.end = lastEndLoc; + if (options.ranges) + node.range[1] = lastEnd; + return node; + } + + // Test whether a statement node is the string literal `"use strict"`. + + function isUseStrict(stmt) { + return options.ecmaVersion >= 5 && stmt.type === "ExpressionStatement" && + stmt.expression.type === "Literal" && stmt.expression.value === "use strict"; + } + + // Predicate that tests whether the next token is of the given + // type, and if yes, consumes it as a side effect. + + function eat(type) { + if (tokType === type) { + next(); + return true; + } + } + + // Test whether a semicolon can be inserted at the current position. + + function canInsertSemicolon() { + return !options.strictSemicolons && + (tokType === _eof || tokType === _braceR || newline.test(input.slice(lastEnd, tokStart))); + } + + // Consume a semicolon, or, failing that, see if we are allowed to + // pretend that there is a semicolon at this position. + + function semicolon() { + if (!eat(_semi) && !canInsertSemicolon()) unexpected(); + } + + // Expect a token of a given type. If found, consume it, otherwise, + // raise an unexpected token error. + + function expect(type) { + if (tokType === type) next(); + else unexpected(); + } + + // Raise an unexpected token error. + + function unexpected() { + raise(tokStart, "Unexpected token"); + } + + // Verify that a node is an lval — something that can be assigned + // to. + + function checkLVal(expr) { + if (expr.type !== "Identifier" && expr.type !== "MemberExpression") + raise(expr.start, "Assigning to rvalue"); + if (strict && expr.type === "Identifier" && isStrictBadIdWord(expr.name)) + raise(expr.start, "Assigning to " + expr.name + " in strict mode"); + } + + // ### Statement parsing + + // Parse a program. Initializes the parser, reads any number of + // statements, and wraps them in a Program node. Optionally takes a + // `program` argument. If present, the statements will be appended + // to its body instead of creating a new node. + + function parseTopLevel(program) { + initTokenState(); + lastStart = lastEnd = tokPos; + if (options.locations) lastEndLoc = curLineLoc(); + inFunction = strict = null; + labels = []; + readToken(); + + var node = program || startNode(), first = true; + if (!program) node.body = []; + while (tokType !== _eof) { + var stmt = parseStatement(); + node.body.push(stmt); + if (first && isUseStrict(stmt)) setStrict(true); + first = false; + } + return finishNode(node, "Program"); + }; + + var loopLabel = {kind: "loop"}, switchLabel = {kind: "switch"}; + + // Parse a single statement. + // + // If expecting a statement and finding a slash operator, parse a + // regular expression literal. This is to handle cases like + // `if (foo) /blah/.exec(foo);`, where looking at the previous token + // does not help. + + function parseStatement() { + if (tokType === _slash) + readToken(true); + + var starttype = tokType, node = startNode(); + + // Most types of statements are recognized by the keyword they + // start with. Many are trivial to parse, some require a bit of + // complexity. + + switch (starttype) { + case _break: case _continue: + next(); + var isBreak = starttype === _break; + if (eat(_semi) || canInsertSemicolon()) node.label = null; + else if (tokType !== _name) unexpected(); + else { + node.label = parseIdent(); + semicolon(); + } + + // Verify that there is an actual destination to break or + // continue to. + for (var i = 0; i < labels.length; ++i) { + var lab = labels[i]; + if (node.label == null || lab.name === node.label.name) { + if (lab.kind != null && (isBreak || lab.kind === "loop")) break; + if (node.label && isBreak) break; + } + } + if (i === labels.length) raise(node.start, "Unsyntactic " + starttype.keyword); + return finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement"); + + case _debugger: + next(); + return finishNode(node, "DebuggerStatement"); + + case _do: + next(); + labels.push(loopLabel); + node.body = parseStatement(); + labels.pop(); + expect(_while); + node.test = parseParenExpression(); + semicolon(); + return finishNode(node, "DoWhileStatement"); + + // Disambiguating between a `for` and a `for`/`in` loop is + // non-trivial. Basically, we have to parse the init `var` + // statement or expression, disallowing the `in` operator (see + // the second parameter to `parseExpression`), and then check + // whether the next token is `in`. When there is no init part + // (semicolon immediately after the opening parenthesis), it is + // a regular `for` loop. + + case _for: + next(); + labels.push(loopLabel); + expect(_parenL); + if (tokType === _semi) return parseFor(node, null); + if (tokType === _var) { + var init = startNode(); + next(); + parseVar(init, true); + if (init.declarations.length === 1 && eat(_in)) + return parseForIn(node, init); + return parseFor(node, init); + } + var init = parseExpression(false, true); + if (eat(_in)) {checkLVal(init); return parseForIn(node, init);} + return parseFor(node, init); + + case _function: + next(); + return parseFunction(node, true); + + case _if: + next(); + node.test = parseParenExpression(); + node.consequent = parseStatement(); + node.alternate = eat(_else) ? parseStatement() : null; + return finishNode(node, "IfStatement"); + + case _return: + if (!inFunction) raise(tokStart, "'return' outside of function"); + next(); + + // In `return` (and `break`/`continue`), the keywords with + // optional arguments, we eagerly look for a semicolon or the + // possibility to insert one. + + if (eat(_semi) || canInsertSemicolon()) node.argument = null; + else { node.argument = parseExpression(); semicolon(); } + return finishNode(node, "ReturnStatement"); + + case _switch: + next(); + node.discriminant = parseParenExpression(); + node.cases = []; + expect(_braceL); + labels.push(switchLabel); + + // Statements under must be grouped (by label) in SwitchCase + // nodes. `cur` is used to keep the node that we are currently + // adding statements to. + + for (var cur, sawDefault; tokType != _braceR;) { + if (tokType === _case || tokType === _default) { + var isCase = tokType === _case; + if (cur) finishNode(cur, "SwitchCase"); + node.cases.push(cur = startNode()); + cur.consequent = []; + next(); + if (isCase) cur.test = parseExpression(); + else { + if (sawDefault) raise(lastStart, "Multiple default clauses"); sawDefault = true; + cur.test = null; + } + expect(_colon); + } else { + if (!cur) unexpected(); + cur.consequent.push(parseStatement()); + } + } + if (cur) finishNode(cur, "SwitchCase"); + next(); // Closing brace + labels.pop(); + return finishNode(node, "SwitchStatement"); + + case _throw: + next(); + if (newline.test(input.slice(lastEnd, tokStart))) + raise(lastEnd, "Illegal newline after throw"); + node.argument = parseExpression(); + return finishNode(node, "ThrowStatement"); + + case _try: + next(); + node.block = parseBlock(); + node.handlers = []; + while (tokType === _catch) { + var clause = startNode(); + next(); + expect(_parenL); + clause.param = parseIdent(); + if (strict && isStrictBadIdWord(clause.param.name)) + raise(clause.param.start, "Binding " + clause.param.name + " in strict mode"); + expect(_parenR); + clause.guard = null; + clause.body = parseBlock(); + node.handlers.push(finishNode(clause, "CatchClause")); + } + node.finalizer = eat(_finally) ? parseBlock() : null; + if (!node.handlers.length && !node.finalizer) + raise(node.start, "Missing catch or finally clause"); + return finishNode(node, "TryStatement"); + + case _var: + next(); + node = parseVar(node); + semicolon(); + return node; + + case _while: + next(); + node.test = parseParenExpression(); + labels.push(loopLabel); + node.body = parseStatement(); + labels.pop(); + return finishNode(node, "WhileStatement"); + + case _with: + if (strict) raise(tokStart, "'with' in strict mode"); + next(); + node.object = parseParenExpression(); + node.body = parseStatement(); + return finishNode(node, "WithStatement"); + + case _braceL: + return parseBlock(); + + case _semi: + next(); + return finishNode(node, "EmptyStatement"); + + // If the statement does not start with a statement keyword or a + // brace, it's an ExpressionStatement or LabeledStatement. We + // simply start parsing an expression, and afterwards, if the + // next token is a colon and the expression was a simple + // Identifier node, we switch to interpreting it as a label. + + default: + var maybeName = tokVal, expr = parseExpression(); + if (starttype === _name && expr.type === "Identifier" && eat(_colon)) { + for (var i = 0; i < labels.length; ++i) + if (labels[i].name === maybeName) raise(expr.start, "Label '" + maybeName + "' is already declared"); + var kind = tokType.isLoop ? "loop" : tokType === _switch ? "switch" : null; + labels.push({name: maybeName, kind: kind}); + node.body = parseStatement(); + node.label = expr; + return finishNode(node, "LabeledStatement"); + } else { + node.expression = expr; + semicolon(); + return finishNode(node, "ExpressionStatement"); + } + } + } + + // Used for constructs like `switch` and `if` that insist on + // parentheses around their expression. + + function parseParenExpression() { + expect(_parenL); + var val = parseExpression(); + expect(_parenR); + return val; + } + + // Parse a semicolon-enclosed block of statements, handling `"use + // strict"` declarations when `allowStrict` is true (used for + // function bodies). + + function parseBlock(allowStrict) { + var node = startNode(), first = true, strict = false, oldStrict; + node.body = []; + expect(_braceL); + while (!eat(_braceR)) { + var stmt = parseStatement(); + node.body.push(stmt); + if (first && isUseStrict(stmt)) { + oldStrict = strict; + setStrict(strict = true); + } + first = false + } + if (strict && !oldStrict) setStrict(false); + return finishNode(node, "BlockStatement"); + } + + // Parse a regular `for` loop. The disambiguation code in + // `parseStatement` will already have parsed the init statement or + // expression. + + function parseFor(node, init) { + node.init = init; + expect(_semi); + node.test = tokType === _semi ? null : parseExpression(); + expect(_semi); + node.update = tokType === _parenR ? null : parseExpression(); + expect(_parenR); + node.body = parseStatement(); + labels.pop(); + return finishNode(node, "ForStatement"); + } + + // Parse a `for`/`in` loop. + + function parseForIn(node, init) { + node.left = init; + node.right = parseExpression(); + expect(_parenR); + node.body = parseStatement(); + labels.pop(); + return finishNode(node, "ForInStatement"); + } + + // Parse a list of variable declarations. + + function parseVar(node, noIn) { + node.declarations = []; + node.kind = "var"; + for (;;) { + var decl = startNode(); + decl.id = parseIdent(); + if (strict && isStrictBadIdWord(decl.id.name)) + raise(decl.id.start, "Binding " + decl.id.name + " in strict mode"); + decl.init = eat(_eq) ? parseExpression(true, noIn) : null; + node.declarations.push(finishNode(decl, "VariableDeclarator")); + if (!eat(_comma)) break; + } + return finishNode(node, "VariableDeclaration"); + } + + // ### Expression parsing + + // These nest, from the most general expression type at the top to + // 'atomic', nondivisible expression types at the bottom. Most of + // the functions will simply let the function(s) below them parse, + // and, *if* the syntactic construct they handle is present, wrap + // the AST node that the inner parser gave them in another node. + + // Parse a full expression. The arguments are used to forbid comma + // sequences (in argument lists, array literals, or object literals) + // or the `in` operator (in for loops initalization expressions). + + function parseExpression(noComma, noIn) { + var expr = parseMaybeAssign(noIn); + if (!noComma && tokType === _comma) { + var node = startNodeFrom(expr); + node.expressions = [expr]; + while (eat(_comma)) node.expressions.push(parseMaybeAssign(noIn)); + return finishNode(node, "SequenceExpression"); + } + return expr; + } + + // Parse an assignment expression. This includes applications of + // operators like `+=`. + + function parseMaybeAssign(noIn) { + var left = parseMaybeConditional(noIn); + if (tokType.isAssign) { + var node = startNodeFrom(left); + node.operator = tokVal; + node.left = left; + next(); + node.right = parseMaybeAssign(noIn); + checkLVal(left); + return finishNode(node, "AssignmentExpression"); + } + return left; + } + + // Parse a ternary conditional (`?:`) operator. + + function parseMaybeConditional(noIn) { + var expr = parseExprOps(noIn); + if (eat(_question)) { + var node = startNodeFrom(expr); + node.test = expr; + node.consequent = parseExpression(true); + expect(_colon); + node.alternate = parseExpression(true, noIn); + return finishNode(node, "ConditionalExpression"); + } + return expr; + } + + // Start the precedence parser. + + function parseExprOps(noIn) { + return parseExprOp(parseMaybeUnary(noIn), -1, noIn); + } + + // Parse binary operators with the operator precedence parsing + // algorithm. `left` is the left-hand side of the operator. + // `minPrec` provides context that allows the function to stop and + // defer further parser to one of its callers when it encounters an + // operator that has a lower precedence than the set it is parsing. + + function parseExprOp(left, minPrec, noIn) { + var prec = tokType.binop; + if (prec != null && (!noIn || tokType !== _in)) { + if (prec > minPrec) { + var node = startNodeFrom(left); + node.left = left; + node.operator = tokVal; + next(); + node.right = parseExprOp(parseMaybeUnary(noIn), prec, noIn); + var node = finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression"); + return parseExprOp(node, minPrec, noIn); + } + } + return left; + } + + // Parse unary operators, both prefix and postfix. + + function parseMaybeUnary(noIn) { + if (tokType.prefix) { + var node = startNode(), update = tokType.isUpdate; + node.operator = tokVal; + node.prefix = true; + next(); + node.argument = parseMaybeUnary(noIn); + if (update) checkLVal(node.argument); + else if (strict && node.operator === "delete" && + node.argument.type === "Identifier") + raise(node.start, "Deleting local variable in strict mode"); + return finishNode(node, update ? "UpdateExpression" : "UnaryExpression"); + } + var expr = parseExprSubscripts(); + while (tokType.postfix && !canInsertSemicolon()) { + var node = startNodeFrom(expr); + node.operator = tokVal; + node.prefix = false; + node.argument = expr; + checkLVal(expr); + next(); + expr = finishNode(node, "UpdateExpression"); + } + return expr; + } + + // Parse call, dot, and `[]`-subscript expressions. + + function parseExprSubscripts() { + return parseSubscripts(parseExprAtom()); + } + + function parseSubscripts(base, noCalls) { + if (eat(_dot)) { + var node = startNodeFrom(base); + node.object = base; + node.property = parseIdent(true); + node.computed = false; + return parseSubscripts(finishNode(node, "MemberExpression"), noCalls); + } else if (eat(_bracketL)) { + var node = startNodeFrom(base); + node.object = base; + node.property = parseExpression(); + node.computed = true; + expect(_bracketR); + return parseSubscripts(finishNode(node, "MemberExpression"), noCalls); + } else if (!noCalls && eat(_parenL)) { + var node = startNodeFrom(base); + node.callee = base; + node.arguments = parseExprList(_parenR, false); + return parseSubscripts(finishNode(node, "CallExpression"), noCalls); + } else return base; + } + + // Parse an atomic expression — either a single token that is an + // expression, an expression started by a keyword like `function` or + // `new`, or an expression wrapped in punctuation like `()`, `[]`, + // or `{}`. + + function parseExprAtom() { + switch (tokType) { + case _this: + var node = startNode(); + next(); + return finishNode(node, "ThisExpression"); + case _name: + return parseIdent(); + case _num: case _string: case _regexp: + var node = startNode(); + node.value = tokVal; + node.raw = input.slice(tokStart, tokEnd); + next(); + return finishNode(node, "Literal"); + + case _null: case _true: case _false: + var node = startNode(); + node.value = tokType.atomValue; + next(); + return finishNode(node, "Literal"); + + case _parenL: + next(); + var val = parseExpression(); + expect(_parenR); + return val; + + case _bracketL: + var node = startNode(); + next(); + node.elements = parseExprList(_bracketR, true, true); + return finishNode(node, "ArrayExpression"); + + case _braceL: + return parseObj(); + + case _function: + var node = startNode(); + next(); + return parseFunction(node, false); + + case _new: + return parseNew(); + + default: + unexpected(); + } + } + + // New's precedence is slightly tricky. It must allow its argument + // to be a `[]` or dot subscript expression, but not a call — at + // least, not without wrapping it in parentheses. Thus, it uses the + + function parseNew() { + var node = startNode(); + next(); + node.callee = parseSubscripts(parseExprAtom(false), true); + if (eat(_parenL)) node.arguments = parseExprList(_parenR, false); + else node.arguments = []; + return finishNode(node, "NewExpression"); + } + + // Parse an object literal. + + function parseObj() { + var node = startNode(), first = true, sawGetSet = false; + node.properties = []; + next(); + while (!eat(_braceR)) { + if (!first) { + expect(_comma); + if (options.allowTrailingCommas && eat(_braceR)) break; + } else first = false; + + var prop = {key: parsePropertyName()}, isGetSet = false, kind; + if (eat(_colon)) { + prop.value = parseExpression(true); + kind = prop.kind = "init"; + } else if (options.ecmaVersion >= 5 && prop.key.type === "Identifier" && + (prop.key.name === "get" || prop.key.name === "set")) { + isGetSet = sawGetSet = true; + kind = prop.kind = prop.key.name; + prop.key = parsePropertyName(); + if (!tokType === _parenL) unexpected(); + prop.value = parseFunction(startNode(), false); + } else unexpected(); + + // getters and setters are not allowed to clash — either with + // each other or with an init property — and in strict mode, + // init properties are also not allowed to be repeated. + + if (prop.key.type === "Identifier" && (strict || sawGetSet)) { + for (var i = 0; i < node.properties.length; ++i) { + var other = node.properties[i]; + if (other.key.name === prop.key.name) { + var conflict = kind == other.kind || isGetSet && other.kind === "init" || + kind === "init" && (other.kind === "get" || other.kind === "set"); + if (conflict && !strict && kind === "init" && other.kind === "init") conflict = false; + if (conflict) raise(prop.key.start, "Redefinition of property"); + } + } + } + node.properties.push(prop); + } + return finishNode(node, "ObjectExpression"); + } + + function parsePropertyName() { + if (tokType === _num || tokType === _string) return parseExprAtom(); + return parseIdent(true); + } + + // Parse a function declaration or literal (depending on the + // `isStatement` parameter). + + function parseFunction(node, isStatement) { + if (tokType === _name) node.id = parseIdent(); + else if (isStatement) unexpected(); + else node.id = null; + node.params = []; + var first = true; + expect(_parenL); + while (!eat(_parenR)) { + if (!first) expect(_comma); else first = false; + node.params.push(parseIdent()); + } + + // Start a new scope with regard to labels and the `inFunction` + // flag (restore them to their old value afterwards). + var oldInFunc = inFunction, oldLabels = labels; + inFunction = true; labels = []; + node.body = parseBlock(true); + inFunction = oldInFunc; labels = oldLabels; + + // If this is a strict mode function, verify that argument names + // are not repeated, and it does not try to bind the words `eval` + // or `arguments`. + if (strict || node.body.body.length && isUseStrict(node.body.body[0])) { + for (var i = node.id ? -1 : 0; i < node.params.length; ++i) { + var id = i < 0 ? node.id : node.params[i]; + if (isStrictReservedWord(id.name) || isStrictBadIdWord(id.name)) + raise(id.start, "Defining '" + id.name + "' in strict mode"); + if (i >= 0) for (var j = 0; j < i; ++j) if (id.name === node.params[j].name) + raise(id.start, "Argument name clash in strict mode"); + } + } + + return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression"); + } + + // Parses a comma-separated list of expressions, and returns them as + // an array. `close` is the token type that ends the list, and + // `allowEmpty` can be turned on to allow subsequent commas with + // nothing in between them to be parsed as `null` (which is needed + // for array literals). + + function parseExprList(close, allowTrailingComma, allowEmpty) { + var elts = [], first = true; + while (!eat(close)) { + if (!first) { + expect(_comma); + if (allowTrailingComma && options.allowTrailingCommas && eat(close)) break; + } else first = false; + + if (allowEmpty && tokType === _comma) elts.push(null); + else elts.push(parseExpression(true)); + } + return elts; + } + + // Parse the next token as an identifier. If `liberal` is true (used + // when parsing properties), it will also convert keywords into + // identifiers. + + function parseIdent(liberal) { + var node = startNode(); + node.name = tokType === _name ? tokVal : (liberal && !options.forbidReserved && tokType.keyword) || unexpected(); + next(); + return finishNode(node, "Identifier"); + } + +})(typeof exports === "undefined" ? (window.acorn = {}) : exports); diff --git a/codemirror/test/lint/lint.js b/codemirror/test/lint/lint.js new file mode 100644 index 0000000..01a9d01 --- /dev/null +++ b/codemirror/test/lint/lint.js @@ -0,0 +1,104 @@ +/* + Simple linter, based on the Acorn [1] parser module + + All of the existing linters either cramp my style or have huge + dependencies (Closure). So here's a very simple, non-invasive one + that only spots + + - missing semicolons and trailing commas + - variables or properties that are reserved words + - assigning to a variable you didn't declare + + [1]: https://github.com/marijnh/acorn/ +*/ + +var fs = require("fs"), acorn = require("./acorn.js"), walk = require("./walk.js"); + +var scopePasser = walk.make({ + ScopeBody: function(node, prev, c) { c(node, node.scope); } +}); + +function checkFile(fileName) { + var file = fs.readFileSync(fileName, "utf8"); + var badChar = file.match(/[\x00-\x08\x0b\x0c\x0e-\x19\uFEFF]/); + if (badChar) + fail("Undesirable character " + badChar[0].charCodeAt(0) + " at position " + badChar.index, + {source: fileName}); + + try { + var parsed = acorn.parse(file, { + locations: true, + ecmaVersion: 3, + strictSemicolons: true, + forbidReserved: true, + sourceFile: fileName + }); + } catch (e) { + fail(e.message, {source: fileName}); + return; + } + + var scopes = []; + + walk.simple(parsed, { + ScopeBody: function(node, scope) { + node.scope = scope; + scopes.push(scope); + } + }, walk.scopeVisitor, {vars: Object.create(null)}); + + var ignoredGlobals = Object.create(null); + + function inScope(name, scope) { + for (var cur = scope; cur; cur = cur.prev) + if (name in cur.vars) return true; + } + function checkLHS(node, scope) { + if (node.type == "Identifier" && !(node.name in ignoredGlobals) && + !inScope(node.name, scope)) { + ignoredGlobals[node.name] = true; + fail("Assignment to global variable", node.loc); + } + } + + walk.simple(parsed, { + UpdateExpression: function(node, scope) {checkLHS(node.argument, scope);}, + AssignmentExpression: function(node, scope) {checkLHS(node.left, scope);}, + Identifier: function(node, scope) { + // Mark used identifiers + for (var cur = scope; cur; cur = cur.prev) + if (node.name in cur.vars) { + cur.vars[node.name].used = true; + return; + } + } + }, scopePasser); + + for (var i = 0; i < scopes.length; ++i) { + var scope = scopes[i]; + for (var name in scope.vars) { + var info = scope.vars[name]; + if (!info.used && info.type != "catch clause" && info.type != "function name" && name.charAt(0) != "_") + fail("Unused " + info.type + " " + name, info.node.loc); + } + } +} + +var failed = false; +function fail(msg, pos) { + if (pos.start) msg += " (" + pos.start.line + ":" + pos.start.column + ")"; + console.log(pos.source.match(/[^\/]+$/)[0] + ": " + msg); + failed = true; +} + +function checkDir(dir) { + fs.readdirSync(dir).forEach(function(file) { + var fname = dir + "/" + file; + if (/\.js$/.test(file)) checkFile(fname); + else if (fs.lstatSync(fname).isDirectory()) checkDir(fname); + }); +} + +exports.checkDir = checkDir; +exports.checkFile = checkFile; +exports.success = function() { return !failed; }; diff --git a/codemirror/test/lint/parse-js.js b/codemirror/test/lint/parse-js.js new file mode 100644 index 0000000..c165a27 --- /dev/null +++ b/codemirror/test/lint/parse-js.js @@ -0,0 +1,1372 @@ +/*********************************************************************** + + A JavaScript tokenizer / parser / beautifier / compressor. + + This version is suitable for Node.js. With minimal changes (the + exports stuff) it should work on any JS platform. + + This file contains the tokenizer/parser. It is a port to JavaScript + of parse-js [1], a JavaScript parser library written in Common Lisp + by Marijn Haverbeke. Thank you Marijn! + + [1] http://marijn.haverbeke.nl/parse-js/ + + Exported functions: + + - tokenizer(code) -- returns a function. Call the returned + function to fetch the next token. + + - parse(code) -- returns an AST of the given JavaScript code. + + -------------------------------- (C) --------------------------------- + + Author: Mihai Bazon +' + ModeTest.htmlEscape(text) + ''; + s += '
' + ModeTest.htmlEscape(text) + ''; + s += '
| ' + + '' + + ModeTest.htmlEscape(val).replace(/ /g,'·') + + '' + + ' | '; + } + s += '
| ' + output[i] + ' | '; + } + s += '
' . get_string('deleted') . '
'; + echo ''; + echo '';
+ echo '' . get_string('deletesubmitconfirm', 'programming') . ''; + echo '
|
| + + | + +
+
+
+
+ |
+
'; + echo $renderer->render($diff); + echo ''; + +/// Finish the page + $OUTPUT->footer($course); + +?> diff --git a/history_fetch_code.php b/history_fetch_code.php new file mode 100644 index 0000000..1997f15 --- /dev/null +++ b/history_fetch_code.php @@ -0,0 +1,31 @@ +get_record('programming_submits', array('id' => $submitid)); + if (! $programming = $DB->get_record('programming', array('id' => $submit->programmingid))) { + error('Course module is incorrect'); + } + if ($submit->userid != $USER->id) { + if (! $course = $DB->get_record('course', array('id' => $programming->course))) { + error('Course is misconfigured'); + } + if (! $cm = get_coursemodule_from_instance('programming', $programming->id, $course->id)) { + error('Course Module ID was incorrect'); + } + $context = context_module::instance($cm->id); + require_login($course->id); + if (!has_capability('mod/programming:viewotherprogram', $context)) { + $submit = null; + } + } + if ($programming->presetcode) { + $submit->code = programming_format_code($programming, $submit); + } + + if ($submit) { + echo str_replace("\r\n", "\r", $submit->code); + } +?> diff --git a/index.php b/index.php new file mode 100644 index 0000000..10037ac --- /dev/null +++ b/index.php @@ -0,0 +1,167 @@ +libdir.'/gradelib.php'); + + $id = required_param('id', PARAM_INT); // course + if (!$course = $DB->get_record('course', array('id'=>$id))) { + print_error('Course ID is incorrect'); + } + require_login($course); +/// Get all required strings + + $strprogrammings = get_string('modulenameplural', 'programming'); + $strprogramming = get_string('modulename', 'programming'); + + $PAGE->set_url('/mod/programming/index.php', array('id' => $id)); + $PAGE->set_title('programming'); + $PAGE->set_heading('programming heading'); + $PAGE->set_pagelayout('incourse'); + $PAGE->set_context(context_course::instance($id)); + $PAGE->navbar->add($strprogrammings); + + echo $OUTPUT->header(); + + + + +/* +/// Print the header + $title = ''; + include_once('pageheader.php'); + +//*/ + $currenttab = 'result'; + include_once('index_tabs.php'); + $table = new html_table(); +/// Get all the appropriate data + + if (! $programmings = get_all_instances_in_course('programming', $course)) { + notice('There are no programmings', '../../course/view.php?id='.$course->id); + die; + } + +/// Print the list of instances (your module will probably extend this) + + $timenow = time(); + $strname = get_string('name'); + $strweek = get_string('week'); + $strtopic = get_string('topic'); + $strlinecount = get_string('linecount', 'programming'); + $strtotal = get_string('total', 'programming'); + $strjudgeresult = get_string('judgeresult', 'programming'); + $strna = get_string('n/a', 'programming'); + $strglobalid = get_string('globalid', 'programming'); + $strsubmitcount = get_string('submitcount', 'programming'); + $strlanguage = get_string('language', 'programming'); + + $params = array($id, $USER->id); + $sql = "SELECT p.id, submitcount, + ps.id AS submitid, codelines, codesize, ps.timemodified, + ps.status AS status, pl.name AS lang + FROM {programming} AS p, + {programming_result} AS pr, + {programming_submits} AS ps, + {programming_languages} AS pl + WHERE p.course = ? + AND p.id = pr.programmingid + AND pr.userid= ? + AND pr.latestsubmitid=ps.id + AND ps.language=pl.id"; + $submits = $DB->get_records_sql($sql, $params); + + if (is_array($submits)) { + foreach($submits as $submit) { + if ($submit->status == PROGRAMMING_STATUS_COMPILEFAIL) { + $submit->judgeresult = get_string('CE', 'programming'); + } + else if ($submit->status == PROGRAMMING_STATUS_FINISH) { + $tr = $DB->get_records('programming_test_results', array('submitid'=>$submit->submitid)); + $submit->judgeresult = programming_contest_get_judgeresult($tr); + } + } + } else { + $submits = array(); + } + + if ($course->format == 'weeks') { + $table->head = array ($strweek); + $table->align = array ('CENTER'); + } else if ($course->format == 'topics') { + $table->head = array ($strtopic); + $table->align = array ('CENTER'); + } else if ($course->format == 'proglist') { + $table->head = array($strglobalid); + $table->align = array('CENTER'); + } else { + $table->head = array (); + $table->align = array (); + } + $table->head = array_merge($table->head, array($strname, $strjudgeresult, $strlanguage, $strlinecount, $strsubmitcount)); + $table->align = array_merge($table->align, array('LEFT', 'CENTER', 'CENTER', 'CENTER', 'CENTER')); + + $totallines = $totalsubmit = 0; + foreach ($programmings as $programming) { + $submit = null; + if (array_key_exists($programming->id, $submits)) { + $submit = $submits[$programming->id]; + } + + if ($submit) { + $totallines += $submit->codelines; + $totalsubmit += $submit->submitcount; + } + + $link = $resultlink = $countlink = $langlink = $codelink = ''; + if (!$programming->visible) { + //Show dimmed if the mod is hidden + $link = "coursemodule\">$programming->name"; + if ($submit) { + $resultlink = ''.$submit->judgeresult.''; + $countlink = ''.$submit->submitcount.''; + $langlink = ''.$submit->lang.''; + $codelink= ''.$submit->codelines.''; + } + } else { + //Show normal if the mod is visible + $link = "coursemodule\">$programming->name"; + if ($submit) { + $resultlink = ''.$submit->judgeresult.''; + $countlink = ''.$submit->submitcount.''; + $langlink = ''.$submit->lang.''; + $codelink= ''.$submit->codelines.''; + } + } + + if ($course->format == 'weeks' or $course->format == 'topics') { + $section = array($programming->section); + } else if ($course->format == 'proglist') { + $section = array($programming->globalid); + } else { + $section = array(); + } + if ($submit) { + $table->data[] = array_merge($section, array($link, $resultlink, $langlink, $codelink, $countlink)); + } else { + $table->data[] = array_merge($section, array($link, '', '', '', '')); + } + } + + if (in_array($course->format, array('weeks', 'topics', 'proglist'))) { + $table->data[] = array($strtotal, '', '', '', $totallines, $totalsubmit); + } else { + $table->data[] = array($strtotal, '', '', $totallines, $totalsubmit); + } + + echo '
dp.SyntaxHighlighter Version: {V}©2004-2007 Alex Gorbatchev. |
";
+foreach($inAr as $key => $val) {
+ print $key . ", " . $val . "\n";
+}
+print "";
+
+// create parameters from the input array: an xmlrpc array of xmlrpc structs
+$p = array();
+foreach ($inAr as $key => $val) {
+ $p[] = new PhpXmlRpc\Value(
+ array(
+ "name" => new PhpXmlRpc\Value($key),
+ "age" => new PhpXmlRpc\Value($val, "int")
+ ),
+ "struct"
+ );
+}
+$v = new PhpXmlRpc\Value($p, "array");
+print "Encoded into xmlrpc format it looks like this: \n" . htmlentities($v->serialize()) . "\n"; + +// create client and message objects +$req = new PhpXmlRpc\Request('examples.sortByAge', array($v)); +$client = new PhpXmlRpc\Client("http://phpxmlrpc.sourceforge.net/server.php"); + +// set maximum debug level, to have the complete communication printed to screen +$client->setDebug(2); + +// send request +print "Now sending request (detailed debug info follows)"; +$resp = $client->send($req); + +// check response for errors, and take appropriate action +if (!$resp->faultCode()) { + print "The server gave me these results:
";
+ $value = $resp->value();
+ foreach ($value as $struct) {
+ $name = $struct["name"];
+ $age = $struct["age"];
+ print htmlspecialchars($name->scalarval()) . ", " . htmlspecialchars($age->scalarval()) . "\n";
+ }
+
+ print "
For nerds: I got this value back
" .
+ htmlentities($resp->serialize()) . "
\n";
+} else {
+ print "An error occurred:";
+ print "Code: " . htmlspecialchars($resp->faultCode()) .
+ "\nReason: '" . htmlspecialchars($resp->faultString()) . '\'
';
+}
+
+?>
+
+
diff --git a/lib/phpxmlrpc/demo/client/getstatename.php b/lib/phpxmlrpc/demo/client/getstatename.php
new file mode 100644
index 0000000..69ce3e0
--- /dev/null
+++ b/lib/phpxmlrpc/demo/client/getstatename.php
@@ -0,0 +1,43 @@
+
+xmlrpc - Getstatename demo
+
+Getstatename demo
+
+Send a U.S. state number to the server and get back the state name
+
+The code demonstrates usage of automatic encoding/decoding of php variables into xmlrpc values
+encode($stateNo))
+ );
+ print "Sending the following request:\n\n" . htmlentities($req->serialize()) . "\n\n
Debug info of server data follows...\n\n";
+ $client = new PhpXmlRpc\Client("http://phpxmlrpc.sourceforge.net/server.php");
+ $client->setDebug(1);
+ $r = $client->send($req);
+ if (!$r->faultCode()) {
+ $v = $r->value();
+ print "
State number " . $stateNo . " is "
+ . htmlspecialchars($encoder->decode($v)) . "
";
+ } else {
+ print "An error occurred: ";
+ print "Code: " . htmlspecialchars($r->faultCode())
+ . " Reason: '" . htmlspecialchars($r->faultString()) . "'Enter a state number to query its name
"; + +?> + + diff --git a/lib/phpxmlrpc/demo/client/introspect.php b/lib/phpxmlrpc/demo/client/introspect.php new file mode 100644 index 0000000..7870a94 --- /dev/null +++ b/lib/phpxmlrpc/demo/client/introspect.php @@ -0,0 +1,86 @@ + +${txt}
\n"; + } else { + print "No documentation available.
\n"; + } + } + if ($rs[1]->faultCode()) { + display_error($rs[1]); + } else { + print "\n";
+ // note: using PhpXmlRpc\Encoder::decode() here would lead to cleaner code
+ $val = $rs[1]->value();
+ if ($val->kindOf() == "array") {
+ foreach ($val as $x) {
+ $ret = $x[0];
+ print "" . $ret->scalarval() . " "
+ . $methodName->scalarval() . "(";
+ if ($x->count() > 1) {
+ for ($k = 1; $k < $x->count(); $k++) {
+ $y = $x[$k];
+ print $y->scalarval();
+ if ($k < $x->count() - 1) {
+ print ", ";
+ }
+ }
+ }
+ print ")
\n";
+ }
+ } else {
+ print "Signature unknown\n";
+ }
+ print "
This form enables you to send mail via an XML-RPC server. + When you press Send this page will reload, showing you the XML-RPC request sent to the host server, the + XML-RPC response received and the internal evaluation done by the PHP implementation.
+ +You can find the source to this page here: mail.php
+ And the source to a functionally identical mail-by-XML-RPC server in the file server.php included with the library (look for the 'mail_send'
+ method)
"; + print "name: " . htmlspecialchars($value["toolkitName"]) . "\n"; + print "version: " . htmlspecialchars($value["toolkitVersion"]) . "\n"; + print "docs: " . htmlspecialchars($value["toolkitDocsUrl"]) . "\n"; + print "os: " . htmlspecialchars($value["toolkitOperatingSystem"]) . "\n"; + print ""; +} else { + print "An error occurred: "; + print "Code: " . htmlspecialchars($resp->faultCode()) . " Reason: '" . htmlspecialchars($resp->faultString()) . "'\n"; +} +?> + + diff --git a/lib/phpxmlrpc/demo/client/wrap.php b/lib/phpxmlrpc/demo/client/wrap.php new file mode 100644 index 0000000..c13c55d --- /dev/null +++ b/lib/phpxmlrpc/demo/client/wrap.php @@ -0,0 +1,53 @@ + +
Server methods list could not be retrieved: error {$resp->faultCode()} '" . htmlspecialchars($resp->faultString()) . "'
\n"; +} else { + echo "Server methods list retrieved, now wrapping it up...
\n+ Dave 35 + Edd 45 + Fred 23 + Barney 37 ++And the array will be returned with the entries sorted by their numbers. +'; +function ageSorter($req) +{ + global $agesorter_arr, $s; + + PhpXmlRpc\Server::xmlrpc_debugmsg("Entering 'agesorter'"); + // get the parameter + $sno = $req->getParam(0); + // error string for [if|when] things go wrong + $err = ""; + $agar = array(); + + $max = $sno->count(); + PhpXmlRpc\Server::xmlrpc_debugmsg("Found $max array elements"); + foreach ($sno as $i => $rec) { + if ($rec->kindOf() != "struct") { + $err = "Found non-struct in array at element $i"; + break; + } + // extract name and age from struct + $n = $rec["name"]; + $a = $rec["age"]; + // $n and $a are xmlrpcvals, + // so get the scalarval from them + $agar[$n->scalarval()] = $a->scalarval(); + } + + // create the output value + $v = new Value(array(), Value::$xmlrpcArray); + + $agesorter_arr = $agar; + // hack, must make global as uksort() won't + // allow us to pass any other auxiliary information + uksort($agesorter_arr, 'agesorter_compare'); + while (list($key, $val) = each($agesorter_arr)) { + // recreate each struct element + $v[] = new Value( + array( + "name" => new Value($key), + "age" => new Value($val, "int") + ), + Value::$xmlrpcStruct + ); + } + + if ($err) { + return new PhpXmlRpc\Response(0, PhpXmlRpc\PhpXmlRpc::$xmlrpcerruser, $err); + } else { + return new PhpXmlRpc\Response($v); + } +} + +// signature and instructions, place these in the dispatch map +$mailsend_sig = array(array( + Value::$xmlrpcBoolean, Value::$xmlrpcString, Value::$xmlrpcString, + Value::$xmlrpcString, Value::$xmlrpcString, Value::$xmlrpcString, + Value::$xmlrpcString, Value::$xmlrpcString, +)); +$mailsend_doc = 'mail.send(recipient, subject, text, sender, cc, bcc, mimetype)
" . htmlentities($v->serialize()) . ""; +$v = new PhpXmlRpc\Value("What are you saying? >> << &&"); +print "
" . htmlentities($v->serialize()) . ""; + +$v = new PhpXmlRpc\Value( + array( + new PhpXmlRpc\Value("ABCDEFHIJ"), + new PhpXmlRpc\Value(1234, 'int'), + new PhpXmlRpc\Value(1, 'boolean'), + ), + "array" +); + +print "
" . htmlentities($v->serialize()) . ""; + +$v = new PhpXmlRpc\Value( + array( + "thearray" => new PhpXmlRpc\Value( + array( + new PhpXmlRpc\Value("ABCDEFHIJ"), + new PhpXmlRpc\Value(1234, 'int'), + new PhpXmlRpc\Value(1, 'boolean'), + new PhpXmlRpc\Value(0, 'boolean'), + new PhpXmlRpc\Value(true, 'boolean'), + new PhpXmlRpc\Value(false, 'boolean'), + ), + "array" + ), + "theint" => new PhpXmlRpc\Value(23, 'int'), + "thestring" => new PhpXmlRpc\Value("foobarwhizz"), + "thestruct" => new PhpXmlRpc\Value( + array( + "one" => new PhpXmlRpc\Value(1, 'int'), + "two" => new PhpXmlRpc\Value(2, 'int'), + ), + "struct" + ), + ), + "struct" +); + +print "
" . htmlentities($v->serialize()) . ""; + +$w = new PhpXmlRpc\Value(array($v, new PhpXmlRpc\Value("That was the struct!")), "array"); + +print "
" . htmlentities($w->serialize()) . ""; + +$w = new PhpXmlRpc\Value("Mary had a little lamb, +Whose fleece was white as snow, +And everywhere that Mary went +the lamb was sure to go. + +Mary had a little lamb +She tied it to a pylon +Ten thousand volts went down its back +And turned it into nylon", "base64" +); +print "
" . htmlentities($w->serialize()) . ""; +print "
Value of base64 string is: '" . $w->scalarval() . "'"; + +$req->method(''); +$req->addParam(new PhpXmlRpc\Value("41", "int")); + +print "
" . htmlentities($op) . ""; + +print "
\n"; + +$t = time(); +$date = PhpXmlRpc\Helper\Date::iso8601Encode($t); +print "Now is $t --> $date\n"; +print "Or in UTC, that is " . PhpXmlRpc\Helper\Date::iso8601Encode($t, 1) . "\n"; +$tb = PhpXmlRpc\Helper\Date::iso8601Decode($date); +print "That is to say $date --> $tb\n"; +print "Which comes out at " . PhpXmlRpc\Helper\Date::iso8601Encode($tb) . "\n"; +print "Which was the time in UTC at " . PhpXmlRpc\Helper\Date::iso8601Encode($date, 1) . "\n"; + +print "\n"; + +?> + + diff --git a/lib/phpxmlrpc/doc/api_changes_v4.md b/lib/phpxmlrpc/doc/api_changes_v4.md new file mode 100644 index 0000000..57b4eb3 --- /dev/null +++ b/lib/phpxmlrpc/doc/api_changes_v4.md @@ -0,0 +1,237 @@ +API Changes between library versions 3 and 4 +============================================ + +Class loading +------------- + +It is not necessary any more to include the files xmlrpc.inc, xmlrpcs.inc and xmlrpc_wrappers.inc to have the +library classes available. + +Instead, it is recommended to rely on class autoloading. + +* If you are using Composer, just install the library by declaring it as dependency for your project in composer.json + + "require": { + ..., + "phpxmlrpc/phpxmlrpc": "~4.0" + }, + +* If you do not use Composer, an autoloader for the library can be found in src/Atuloader.php. + The php example files in the demo/client folder do make use of it. + Example code to set up the autoloader: + + include_once
+
+
+ (
+
+void )
+ )
+ )
+
+
+
+
+
+
+ " . + htmlentities($r->serialize()) . "
'; + $endTag = ''; + + //$content = file_get_contents($inFile); + $last = 0; + $out = ''; + while (($start = strpos($content, $startTag, $last)) !== false) { + $end = strpos($content, $endTag, $start); + $code = substr($content, $start + strlen($startTag), $end - $start - strlen($startTag)); + if ($code[strlen($code) - 1] == "\n") { + $code = substr($code, 0, -1); + } + + $code = str_replace(array('>', '<'), array('>', '<'), $code); + $code = highlight_string('<?php
\n".htmlentities($message, $flags)."\n"; + } + } else { + print "\n$message\n"; + } + + // let the user see this now in case there's a time out later... + flush(); + } +} diff --git a/lib/phpxmlrpc/src/Helper/XMLParser.php b/lib/phpxmlrpc/src/Helper/XMLParser.php new file mode 100644 index 0000000..b7d137f --- /dev/null +++ b/lib/phpxmlrpc/src/Helper/XMLParser.php @@ -0,0 +1,561 @@ + '', + 'stack' => array(), + 'valuestack' => array(), + 'isf' => 0, + 'isf_reason' => '', + 'method' => false, // so we can check later if we got a methodname or not + 'params' => array(), + 'pt' => array(), + 'rt' => '', + ); + + public $xmlrpc_valid_parents = array( + 'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'), + 'BOOLEAN' => array('VALUE'), + 'I4' => array('VALUE'), + 'I8' => array('VALUE'), + 'EX:I8' => array('VALUE'), + 'INT' => array('VALUE'), + 'STRING' => array('VALUE'), + 'DOUBLE' => array('VALUE'), + 'DATETIME.ISO8601' => array('VALUE'), + 'BASE64' => array('VALUE'), + 'MEMBER' => array('STRUCT'), + 'NAME' => array('MEMBER'), + 'DATA' => array('ARRAY'), + 'ARRAY' => array('VALUE'), + 'STRUCT' => array('VALUE'), + 'PARAM' => array('PARAMS'), + 'METHODNAME' => array('METHODCALL'), + 'PARAMS' => array('METHODCALL', 'METHODRESPONSE'), + 'FAULT' => array('METHODRESPONSE'), + 'NIL' => array('VALUE'), // only used when extension activated + 'EX:NIL' => array('VALUE'), // only used when extension activated + ); + + /** + * xml parser handler function for opening element tags. + */ + public function xmlrpc_se($parser, $name, $attrs, $acceptSingleVals = false) + { + // if invalid xmlrpc already detected, skip all processing + if ($this->_xh['isf'] < 2) { + // check for correct element nesting + // top level element can only be of 2 types + /// @todo optimization creep: save this check into a bool variable, instead of using count() every time: + /// there is only a single top level element in xml anyway + if (count($this->_xh['stack']) == 0) { + if ($name != 'METHODRESPONSE' && $name != 'METHODCALL' && ( + $name != 'VALUE' && !$acceptSingleVals) + ) { + $this->_xh['isf'] = 2; + $this->_xh['isf_reason'] = 'missing top level xmlrpc element'; + + return; + } else { + $this->_xh['rt'] = strtolower($name); + } + } else { + // not top level element: see if parent is OK + $parent = end($this->_xh['stack']); + if (!array_key_exists($name, $this->xmlrpc_valid_parents) || !in_array($parent, $this->xmlrpc_valid_parents[$name])) { + $this->_xh['isf'] = 2; + $this->_xh['isf_reason'] = "xmlrpc element $name cannot be child of $parent"; + + return; + } + } + + switch ($name) { + // optimize for speed switch cases: most common cases first + case 'VALUE': + /// @todo we could check for 2 VALUE elements inside a MEMBER or PARAM element + $this->_xh['vt'] = 'value'; // indicator: no value found yet + $this->_xh['ac'] = ''; + $this->_xh['lv'] = 1; + $this->_xh['php_class'] = null; + break; + case 'I8': + case 'EX:I8': + if (PHP_INT_SIZE === 4) { + /// INVALID ELEMENT: RAISE ISF so that it is later recognized!!! + $this->_xh['isf'] = 2; + $this->_xh['isf_reason'] = "Received i8 element but php is compiled in 32 bit mode"; + + return; + } + // fall through voluntarily + case 'I4': + case 'INT': + case 'STRING': + case 'BOOLEAN': + case 'DOUBLE': + case 'DATETIME.ISO8601': + case 'BASE64': + if ($this->_xh['vt'] != 'value') { + // two data elements inside a value: an error occurred! + $this->_xh['isf'] = 2; + $this->_xh['isf_reason'] = "$name element following a {$this->_xh['vt']} element inside a single value"; + + return; + } + $this->_xh['ac'] = ''; // reset the accumulator + break; + case 'STRUCT': + case 'ARRAY': + if ($this->_xh['vt'] != 'value') { + //two data elements inside a value: an error occurred! + $this->_xh['isf'] = 2; + $this->_xh['isf_reason'] = "$name element following a {$this->_xh['vt']} element inside a single value"; + + return; + } + // create an empty array to hold child values, and push it onto appropriate stack + $curVal = array(); + $curVal['values'] = array(); + $curVal['type'] = $name; + // check for out-of-band information to rebuild php objs + // and in case it is found, save it + if (@isset($attrs['PHP_CLASS'])) { + $curVal['php_class'] = $attrs['PHP_CLASS']; + } + $this->_xh['valuestack'][] = $curVal; + $this->_xh['vt'] = 'data'; // be prepared for a data element next + break; + case 'DATA': + if ($this->_xh['vt'] != 'data') { + //two data elements inside a value: an error occurred! + $this->_xh['isf'] = 2; + $this->_xh['isf_reason'] = "found two data elements inside an array element"; + + return; + } + case 'METHODCALL': + case 'METHODRESPONSE': + case 'PARAMS': + // valid elements that add little to processing + break; + case 'METHODNAME': + case 'NAME': + /// @todo we could check for 2 NAME elements inside a MEMBER element + $this->_xh['ac'] = ''; + break; + case 'FAULT': + $this->_xh['isf'] = 1; + break; + case 'MEMBER': + $this->_xh['valuestack'][count($this->_xh['valuestack']) - 1]['name'] = ''; // set member name to null, in case we do not find in the xml later on + //$this->_xh['ac']=''; + // Drop trough intentionally + case 'PARAM': + // clear value type, so we can check later if no value has been passed for this param/member + $this->_xh['vt'] = null; + break; + case 'NIL': + case 'EX:NIL': + if (PhpXmlRpc::$xmlrpc_null_extension) { + if ($this->_xh['vt'] != 'value') { + //two data elements inside a value: an error occurred! + $this->_xh['isf'] = 2; + $this->_xh['isf_reason'] = "$name element following a {$this->_xh['vt']} element inside a single value"; + + return; + } + $this->_xh['ac'] = ''; // reset the accumulator + break; + } + // we do not support the
\n";
+} else {
+ echo "$title\n\n";
+}
+
+if ($is_web) {
+ echo "Using lib version: " . PhpXmlRpc::$xmlrpcVersion . " on PHP version: " . phpversion() . "
\n";
+ if ($xd) {
+ echo "XDEBUG profiling enabled: skipping remote tests. Trace file is: " . htmlspecialchars(xdebug_get_profiler_filename()) . "
\n";
+ }
+ flush();
+ ob_flush();
+} else {
+ echo "Using lib version: " . PhpXmlRpc::$xmlrpcVersion . " on PHP version: " . phpversion() . "\n";
+ if ($xd) {
+ echo "XDEBUG profiling enabled: skipping remote tests\nTrace file is: " . xdebug_get_profiler_filename() . "\n";
+ }
+}
+
+// test 'manual style' data encoding vs. 'automatic style' encoding
+begin_test('Data encoding (large array)', 'manual encoding');
+for ($i = 0; $i < $num_tests; $i++) {
+ $vals = array();
+ for ($j = 0; $j < 10; $j++) {
+ $valarray = array();
+ foreach ($data[$j] as $key => $val) {
+ $values = array();
+ $values[] = new Value($val[0], 'int');
+ $values[] = new Value($val[1], 'double');
+ $values[] = new Value($val[2], 'string');
+ $values[] = new Value($val[3], 'boolean');
+ $values[] = new Value($val[4], 'dateTime.iso8601');
+ $values[] = new Value($val[5], 'int');
+ $values[] = new Value($val[6], 'double');
+ $values[] = new Value($val[7], 'string');
+ $values[] = new Value($val[8], 'boolean');
+ $values[] = new Value($val[9], 'dateTime.iso8601');
+ $valarray[$key] = new Value($values, 'array');
+ }
+ $vals[] = new Value($valarray, 'struct');
+ }
+ $value = new Value($vals, 'array');
+ $out = $value->serialize();
+}
+end_test('Data encoding (large array)', 'manual encoding', $out);
+
+begin_test('Data encoding (large array)', 'automatic encoding');
+$encoder = new Encoder();
+for ($i = 0; $i < $num_tests; $i++) {
+ $value = $encoder->encode($data, array('auto_dates'));
+ $out = $value->serialize();
+}
+end_test('Data encoding (large array)', 'automatic encoding', $out);
+
+if (function_exists('xmlrpc_set_type')) {
+ begin_test('Data encoding (large array)', 'xmlrpc-epi encoding');
+ for ($i = 0; $i < $num_tests; $i++) {
+ for ($j = 0; $j < 10; $j++) {
+ foreach ($keys as $k) {
+ xmlrpc_set_type($data[$j][$k][4], 'datetime');
+ xmlrpc_set_type($data[$j][$k][8], 'datetime');
+ }
+ }
+ $out = xmlrpc_encode($data);
+ }
+ end_test('Data encoding (large array)', 'xmlrpc-epi encoding', $out);
+}
+
+// test 'old style' data decoding vs. 'automatic style' decoding
+$dummy = new Request('');
+$out = new Response($value);
+$in = '' . "\n" . $out->serialize();
+
+begin_test('Data decoding (large array)', 'manual decoding');
+for ($i = 0; $i < $num_tests; $i++) {
+ $response = $dummy->ParseResponse($in, true);
+ $value = $response->value();
+ $result = array();
+ foreach($value as $val1) {
+ $out = array();
+ foreach($val1 as $name => $val) {
+ $out[$name] = array();
+ foreach($val as $data) {
+ $out[$name][] = $data->scalarval();
+ }
+ }
+ $result[] = $out;
+ }
+}
+end_test('Data decoding (large array)', 'manual decoding', $result);
+
+begin_test('Data decoding (large array)', 'manual decoding deprecated');
+for ($i = 0; $i < $num_tests; $i++) {
+ $response = $dummy->ParseResponse($in, true);
+ $value = $response->value();
+ $result = array();
+ $l = $value->arraysize();
+ for ($k = 0; $k < $l; $k++) {
+ $val1 = $value->arraymem($k);
+ $out = array();
+ while (list($name, $val) = $val1->structeach()) {
+ $out[$name] = array();
+ $m = $val->arraysize();
+ for ($j = 0; $j < $m; $j++) {
+ $data = $val->arraymem($j);
+ $out[$name][] = $data->scalarval();
+ }
+ } // while
+ $result[] = $out;
+ }
+}
+end_test('Data decoding (large array)', 'manual decoding deprecated', $result);
+
+begin_test('Data decoding (large array)', 'automatic decoding');
+for ($i = 0; $i < $num_tests; $i++) {
+ $response = $dummy->ParseResponse($in, true, 'phpvals');
+ $value = $response->value();
+}
+end_test('Data decoding (large array)', 'automatic decoding', $value);
+
+if (function_exists('xmlrpc_decode')) {
+ begin_test('Data decoding (large array)', 'xmlrpc-epi decoding');
+ for ($i = 0; $i < $num_tests; $i++) {
+ $response = $dummy->ParseResponse($in, true, 'xml');
+ $value = xmlrpc_decode($response->value());
+ }
+ end_test('Data decoding (large array)', 'xmlrpc-epi decoding', $value);
+}
+
+if (!$xd) {
+
+ /// test multicall vs. many calls vs. keep-alives
+ $encoder = new Encoder();
+ $value = $encoder->encode($data1, array('auto_dates'));
+ $req = new Request('interopEchoTests.echoValue', array($value));
+ $reqs = array();
+ for ($i = 0; $i < 25; $i++) {
+ $reqs[] = $req;
+ }
+ $server = explode(':', $args['LOCALSERVER']);
+ if (count($server) > 1) {
+ $srv = $server[1] . '://' . $server[0] . $args['URI'];
+ $c = new Client($args['URI'], $server[0], $server[1]);
+ } else {
+ $srv = $args['LOCALSERVER'] . $args['URI'];
+ $c = new Client($args['URI'], $args['LOCALSERVER']);
+ }
+ // do not interfere with http compression
+ $c->accepted_compression = array();
+ //$c->debug=true;
+
+ $testName = "Repeated send (small array) to $srv";
+
+ if (function_exists('gzinflate')) {
+ $c->accepted_compression = null;
+ }
+ begin_test($testName, 'http 10');
+ $response = array();
+ for ($i = 0; $i < 25; $i++) {
+ $resp = $c->send($req);
+ $response[] = $resp->value();
+ }
+ end_test($testName, 'http 10', $response);
+
+ if (function_exists('curl_init')) {
+ begin_test($testName, 'http 11 w. keep-alive');
+ $response = array();
+ for ($i = 0; $i < 25; $i++) {
+ $resp = $c->send($req, 10, 'http11');
+ $response[] = $resp->value();
+ }
+ end_test($testName, 'http 11 w. keep-alive', $response);
+
+ $c->keepalive = false;
+ begin_test($testName, 'http 11');
+ $response = array();
+ for ($i = 0; $i < 25; $i++) {
+ $resp = $c->send($req, 10, 'http11');
+ $response[] = $resp->value();
+ }
+ end_test($testName, 'http 11', $response);
+ }
+
+ begin_test($testName, 'multicall');
+ $response = $c->send($reqs);
+ foreach ($response as $key => & $val) {
+ $val = $val->value();
+ }
+ end_test($testName, 'multicall', $response);
+
+ if (function_exists('gzinflate')) {
+ $c->accepted_compression = array('gzip');
+ $c->request_compression = 'gzip';
+
+ begin_test($testName, 'http 10 w. compression');
+ $response = array();
+ for ($i = 0; $i < 25; $i++) {
+ $resp = $c->send($req);
+ $response[] = $resp->value();
+ }
+ end_test($testName, 'http 10 w. compression', $response);
+
+ if (function_exists('curl_init')) {
+ begin_test($testName, 'http 11 w. keep-alive and compression');
+ $response = array();
+ for ($i = 0; $i < 25; $i++) {
+ $resp = $c->send($req, 10, 'http11');
+ $response[] = $resp->value();
+ }
+ end_test($testName, 'http 11 w. keep-alive and compression', $response);
+
+ $c->keepalive = false;
+ begin_test($testName, 'http 11 w. compression');
+ $response = array();
+ for ($i = 0; $i < 25; $i++) {
+ $resp = $c->send($req, 10, 'http11');
+ $response[] = $resp->value();
+ }
+ end_test($testName, 'http 11 w. compression', $response);
+ }
+
+ begin_test($testName, 'multicall w. compression');
+ $response = $c->send($reqs);
+ foreach ($response as $key => & $val) {
+ $val = $val->value();
+ }
+ end_test($testName, 'multicall w. compression', $response);
+ }
+} // end of 'if no xdebug profiling'
+
+
+echo "\n";
+foreach ($test_results as $test => $results) {
+ echo "\nTEST: $test\n";
+ foreach ($results as $case => $data) {
+ echo " $case: {$data['time']} secs - Output data CRC: " . crc32(serialize($data['result'])) . "\n";
+ }
+}
+
+if ($is_web) {
+ echo "\n\n\n\n";
+}
diff --git a/lib/phpxmlrpc/tests/ci/travis/apache_vhost b/lib/phpxmlrpc/tests/ci/travis/apache_vhost
new file mode 100644
index 0000000..87841d6
--- /dev/null
+++ b/lib/phpxmlrpc/tests/ci/travis/apache_vhost
@@ -0,0 +1,68 @@
+# Configuration file for Apache running on Travis.
+# PHP setup in FCGI mode
+
+| Test | +Result | +
| ' . htmlspecialchars($test) . ' | ' . htmlspecialchars($result['description']) . " |
| Test | +Result | +
| ' . htmlspecialchars($test) . ' | ' . htmlspecialchars($result['description']) . " |
\n---SENDING---\n" . htmlentities($op) . "\n---END---\n"; + // let the client see this now in case http times out... + flush(); + } + + if($timeout>0) + { + $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr, $timeout); + } + else + { + $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr); + } + if($fp) + { + if($timeout>0 && function_exists('stream_set_timeout')) + { + stream_set_timeout($fp, $timeout); + } + } + else + { + $this->errstr='Connect error: '.$this->errstr; + $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr . ' (' . $this->errno . ')'); + return $r; + } + + if(!fputs($fp, $op, strlen($op))) + { + $this->errstr='Write error'; + $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr); + return $r; + } + else + { + // reset errno and errstr on succesful socket connection + $this->errstr = ''; + } + // G. Giunta 2005/10/24: close socket before parsing. + // should yeld slightly better execution times, and make easier recursive calls (e.g. to follow http redirects) + $ipd=''; + while($data=fread($fp, 32768)) + { + // shall we check for $data === FALSE? + // as per the manual, it signals an error + $ipd.=$data; + } + fclose($fp); + $r =& $msg->parseResponse($ipd, false, $this->return_type); + return $r; + + } + + /** + * @access private + */ + function &sendPayloadHTTPS($msg, $server, $port, $timeout=0, $username='', + $password='', $authtype=1, $cert='',$certpass='', $cacert='', $cacertdir='', + $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1, + $keepalive=false, $key='', $keypass='') + { + $r =& $this->sendPayloadCURL($msg, $server, $port, $timeout, $username, + $password, $authtype, $cert, $certpass, $cacert, $cacertdir, $proxyhost, $proxyport, + $proxyusername, $proxypassword, $proxyauthtype, 'https', $keepalive, $key, $keypass); + return $r; + } + + /** + * Contributed by Justin Miller
\n---SENDING---\n" . htmlentities($payload) . "\n---END---\n"; + // let the client see this now in case http times out... + flush(); + } + + if(!$keepalive || !$this->xmlrpc_curl_handle) + { + $curl = curl_init($method . '://' . $server . ':' . $port . $this->path); + if($keepalive) + { + $this->xmlrpc_curl_handle = $curl; + } + } + else + { + $curl = $this->xmlrpc_curl_handle; + } + + // results into variable + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + + if($this->debug) + { + curl_setopt($curl, CURLOPT_VERBOSE, 1); + } + curl_setopt($curl, CURLOPT_USERAGENT, $GLOBALS['xmlrpcName'].' '.$GLOBALS['xmlrpcVersion']); + // required for XMLRPC: post the data + curl_setopt($curl, CURLOPT_POST, 1); + // the data + curl_setopt($curl, CURLOPT_POSTFIELDS, $payload); + + // return the header too + curl_setopt($curl, CURLOPT_HEADER, 1); + + // will only work with PHP >= 5.0 + // NB: if we set an empty string, CURL will add http header indicating + // ALL methods it is supporting. This is possibly a better option than + // letting the user tell what curl can / cannot do... + if(is_array($this->accepted_compression) && count($this->accepted_compression)) + { + //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression)); + // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?) + if (count($this->accepted_compression) == 1) + { + curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]); + } + else + curl_setopt($curl, CURLOPT_ENCODING, ''); + } + // extra headers + $headers = array('Content-Type: ' . $msg->content_type , 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings)); + // if no keepalive is wanted, let the server know it in advance + if(!$keepalive) + { + $headers[] = 'Connection: close'; + } + // request compression header + if($encoding_hdr) + { + $headers[] = $encoding_hdr; + } + + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + // timeout is borked + if($timeout) + { + curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1); + } + + if($username && $password) + { + curl_setopt($curl, CURLOPT_USERPWD, $username.':'.$password); + if (defined('CURLOPT_HTTPAUTH')) + { + curl_setopt($curl, CURLOPT_HTTPAUTH, $authtype); + } + else if ($authtype != 1) + { + error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth is supported by the current PHP/curl install'); + } + } + + if($method == 'https') + { + // set cert file + if($cert) + { + curl_setopt($curl, CURLOPT_SSLCERT, $cert); + } + // set cert password + if($certpass) + { + curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certpass); + } + // whether to verify remote host's cert + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer); + // set ca certificates file/dir + if($cacert) + { + curl_setopt($curl, CURLOPT_CAINFO, $cacert); + } + if($cacertdir) + { + curl_setopt($curl, CURLOPT_CAPATH, $cacertdir); + } + // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?) + if($key) + { + curl_setopt($curl, CURLOPT_SSLKEY, $key); + } + // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?) + if($keypass) + { + curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keypass); + } + // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that it matches the hostname used + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost); + } + + // proxy info + if($proxyhost) + { + if($proxyport == 0) + { + $proxyport = 8080; // NB: even for HTTPS, local connection is on port 8080 + } + curl_setopt($curl, CURLOPT_PROXY,$proxyhost.':'.$proxyport); + //curl_setopt($curl, CURLOPT_PROXYPORT,$proxyport); + if($proxyusername) + { + curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyusername.':'.$proxypassword); + if (defined('CURLOPT_PROXYAUTH')) + { + curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyauthtype); + } + else if ($proxyauthtype != 1) + { + error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth to proxy is supported by the current PHP/curl install'); + } + } + } + + // NB: should we build cookie http headers by hand rather than let CURL do it? + // the following code does not honour 'expires', 'path' and 'domain' cookie attributes + // set to clint obj the the user... + if (count($this->cookies)) + { + $cookieheader = ''; + foreach ($this->cookies as $name => $cookie) + { + $cookieheader .= $name . '=' . $cookie['value'] . ', '; + } + curl_setopt($curl, CURLOPT_COOKIE, substr($cookieheader, 0, -2)); + } + + $result = curl_exec($curl); + + if(!$result) + { + $this->errstr='no response'; + $resp=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['curl_fail'], $GLOBALS['xmlrpcstr']['curl_fail']. ': '. curl_error($curl)); + if(!$keepalive) + { + curl_close($curl); + } + } + else + { + if(!$keepalive) + { + curl_close($curl); + } + $resp =& $msg->parseResponse($result, true, $this->return_type); + } + return $resp; + } + + /** + * Send an array of request messages and return an array of responses. + * Unless $this->no_multicall has been set to true, it will try first + * to use one single xmlrpc call to server method system.multicall, and + * revert to sending many successive calls in case of failure. + * This failure is also stored in $this->no_multicall for subsequent calls. + * Unfortunately, there is no server error code universally used to denote + * the fact that multicall is unsupported, so there is no way to reliably + * distinguish between that and a temporary failure. + * If you are sure that server supports multicall and do not want to + * fallback to using many single calls, set the fourth parameter to FALSE. + * + * NB: trying to shoehorn extra functionality into existing syntax has resulted + * in pretty much convoluted code... + * + * @param array $msgs an array of xmlrpcmsg objects + * @param integer $timeout connection timeout (in seconds) + * @param string $method the http protocol variant to be used + * @param boolean fallback When true, upon receiveing an error during multicall, multiple single calls will be attempted + * @return array + * @access public + */ + function multicall($msgs, $timeout=0, $method='', $fallback=true) + { + if ($method == '') + { + $method = $this->method; + } + if(!$this->no_multicall) + { + $results = $this->_try_multicall($msgs, $timeout, $method); + if(is_array($results)) + { + // System.multicall succeeded + return $results; + } + else + { + // either system.multicall is unsupported by server, + // or call failed for some other reason. + if ($fallback) + { + // Don't try it next time... + $this->no_multicall = true; + } + else + { + if (is_a($results, 'xmlrpcresp')) + { + $result = $results; + } + else + { + $result =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['multicall_error'], $GLOBALS['xmlrpcstr']['multicall_error']); + } + } + } + } + else + { + // override fallback, in case careless user tries to do two + // opposite things at the same time + $fallback = true; + } + + $results = array(); + if ($fallback) + { + // system.multicall is (probably) unsupported by server: + // emulate multicall via multiple requests + foreach($msgs as $msg) + { + $results[] =& $this->send($msg, $timeout, $method); + } + } + else + { + // user does NOT want to fallback on many single calls: + // since we should always return an array of responses, + // return an array with the same error repeated n times + foreach($msgs as $msg) + { + $results[] = $result; + } + } + return $results; + } + + /** + * Attempt to boxcar $msgs via system.multicall. + * Returns either an array of xmlrpcreponses, an xmlrpc error response + * or false (when received response does not respect valid multicall syntax) + * @access private + */ + function _try_multicall($msgs, $timeout, $method) + { + // Construct multicall message + $calls = array(); + foreach($msgs as $msg) + { + $call['methodName'] =& new xmlrpcval($msg->method(),'string'); + $numParams = $msg->getNumParams(); + $params = array(); + for($i = 0; $i < $numParams; $i++) + { + $params[$i] = $msg->getParam($i); + } + $call['params'] =& new xmlrpcval($params, 'array'); + $calls[] =& new xmlrpcval($call, 'struct'); + } + $multicall =& new xmlrpcmsg('system.multicall'); + $multicall->addParam(new xmlrpcval($calls, 'array')); + + // Attempt RPC call + $result =& $this->send($multicall, $timeout, $method); + + if($result->faultCode() != 0) + { + // call to system.multicall failed + return $result; + } + + // Unpack responses. + $rets = $result->value(); + + if ($this->return_type == 'xml') + { + return $rets; + } + else if ($this->return_type == 'phpvals') + { + ///@todo test this code branch... + $rets = $result->value(); + if(!is_array($rets)) + { + return false; // bad return type from system.multicall + } + $numRets = count($rets); + if($numRets != count($msgs)) + { + return false; // wrong number of return values. + } + + $response = array(); + for($i = 0; $i < $numRets; $i++) + { + $val = $rets[$i]; + if (!is_array($val)) { + return false; + } + switch(count($val)) + { + case 1: + if(!isset($val[0])) + { + return false; // Bad value + } + // Normal return value + $response[$i] =& new xmlrpcresp($val[0], 0, '', 'phpvals'); + break; + case 2: + /// @todo remove usage of @: it is apparently quite slow + $code = @$val['faultCode']; + if(!is_int($code)) + { + return false; + } + $str = @$val['faultString']; + if(!is_string($str)) + { + return false; + } + $response[$i] =& new xmlrpcresp(0, $code, $str); + break; + default: + return false; + } + } + return $response; + } + else // return type == 'xmlrpcvals' + { + $rets = $result->value(); + if($rets->kindOf() != 'array') + { + return false; // bad return type from system.multicall + } + $numRets = $rets->arraysize(); + if($numRets != count($msgs)) + { + return false; // wrong number of return values. + } + + $response = array(); + for($i = 0; $i < $numRets; $i++) + { + $val = $rets->arraymem($i); + switch($val->kindOf()) + { + case 'array': + if($val->arraysize() != 1) + { + return false; // Bad value + } + // Normal return value + $response[$i] =& new xmlrpcresp($val->arraymem(0)); + break; + case 'struct': + $code = $val->structmem('faultCode'); + if($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') + { + return false; + } + $str = $val->structmem('faultString'); + if($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') + { + return false; + } + $response[$i] =& new xmlrpcresp(0, $code->scalarval(), $str->scalarval()); + break; + default: + return false; + } + } + return $response; + } + } + } // end class xmlrpc_client + + class xmlrpcresp + { + var $val = 0; + var $valtyp; + var $errno = 0; + var $errstr = ''; + var $payload; + var $hdrs = array(); + var $_cookies = array(); + var $content_type = 'text/xml'; + var $raw_data = ''; + + /** + * @param mixed $val either an xmlrpcval obj, a php value or the xml serialization of an xmlrpcval (a string) + * @param integer $fcode set it to anything but 0 to create an error response + * @param string $fstr the error string, in case of an error response + * @param string $valtyp either 'xmlrpcvals', 'phpvals' or 'xml' + * + * @todo add check that $val / $fcode / $fstr is of correct type??? + * NB: as of now we do not do it, since it might be either an xmlrpcval or a plain + * php val, or a complete xml chunk, depending on usage of xmlrpc_client::send() inside which creator is called... + */ + function xmlrpcresp($val, $fcode = 0, $fstr = '', $valtyp='') + { + if($fcode != 0) + { + // error response + $this->errno = $fcode; + $this->errstr = $fstr; + //$this->errstr = htmlspecialchars($fstr); // XXX: encoding probably shouldn't be done here; fix later. + } + else + { + // successful response + $this->val = $val; + if ($valtyp == '') + { + // user did not declare type of response value: try to guess it + if (is_object($this->val) && is_a($this->val, 'xmlrpcval')) + { + $this->valtyp = 'xmlrpcvals'; + } + else if (is_string($this->val)) + { + $this->valtyp = 'xml'; + + } + else + { + $this->valtyp = 'phpvals'; + } + } + else + { + // user declares type of resp value: believe him + $this->valtyp = $valtyp; + } + } + } + + /** + * Returns the error code of the response. + * @return integer the error code of this response (0 for not-error responses) + * @access public + */ + function faultCode() + { + return $this->errno; + } + + /** + * Returns the error code of the response. + * @return string the error string of this response ('' for not-error responses) + * @access public + */ + function faultString() + { + return $this->errstr; + } + + /** + * Returns the value received by the server. + * @return mixed the xmlrpcval object returned by the server. Might be an xml string or php value if the response has been created by specially configured xmlrpc_client objects + * @access public + */ + function value() + { + return $this->val; + } + + /** + * Returns an array with the cookies received from the server. + * Array has the form: $cookiename => array ('value' => $val, $attr1 => $val1, $attr2 = $val2, ...) + * with attributes being e.g. 'expires', 'path', domain'. + * NB: cookies sent as 'expired' by the server (i.e. with an expiry date in the past) + * are still present in the array. It is up to the user-defined code to decide + * how to use the received cookies, and wheter they have to be sent back with the next + * request to the server (using xmlrpc_client::setCookie) or not + * @return array array of cookies received from the server + * @access public + */ + function cookies() + { + return $this->_cookies; + } + + /** + * Returns xml representation of the response. XML prologue not included + * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed + * @return string the xml representation of the response + * @access public + */ + function serialize($charset_encoding='') + { + if ($charset_encoding != '') + $this->content_type = 'text/xml; charset=' . $charset_encoding; + else + $this->content_type = 'text/xml'; + $result = "
';
+ foreach($GLOBALS['_xh']['headers'] as $header => $value)
+ {
+ print htmlentities("HEADER: $header: $value\n");
+ }
+ foreach($GLOBALS['_xh']['cookies'] as $header => $value)
+ {
+ print htmlentities("COOKIE: $header={$value['value']}\n");
+ }
+ print "\n";
+ }
+
+ // if CURL was used for the call, http headers have been processed,
+ // and dechunking + reinflating have been carried out
+ if(!$headers_processed)
+ {
+ // Decode chunked encoding sent by http 1.1 servers
+ if(isset($GLOBALS['_xh']['headers']['transfer-encoding']) && $GLOBALS['_xh']['headers']['transfer-encoding'] == 'chunked')
+ {
+ if(!$data = decode_chunked($data))
+ {
+ error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to rebuild the chunked data received from server');
+ $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['dechunk_fail'], $GLOBALS['xmlrpcstr']['dechunk_fail']);
+ return $r;
+ }
+ }
+
+ // Decode gzip-compressed stuff
+ // code shamelessly inspired from nusoap library by Dietrich Ayala
+ if(isset($GLOBALS['_xh']['headers']['content-encoding']))
+ {
+ $GLOBALS['_xh']['headers']['content-encoding'] = str_replace('x-', '', $GLOBALS['_xh']['headers']['content-encoding']);
+ if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' || $GLOBALS['_xh']['headers']['content-encoding'] == 'gzip')
+ {
+ // if decoding works, use it. else assume data wasn't gzencoded
+ if(function_exists('gzinflate'))
+ {
+ if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data))
+ {
+ $data = $degzdata;
+ if($this->debug)
+ print "---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---"; + } + elseif($GLOBALS['_xh']['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) + { + $data = $degzdata; + if($this->debug) + print "
---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---"; + } + else + { + error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to decode the deflated data received from server'); + $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['decompress_fail'], $GLOBALS['xmlrpcstr']['decompress_fail']); + return $r; + } + } + else + { + error_log('XML-RPC: xmlrpcmsg::parseResponse: the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.'); + $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['cannot_decompress'], $GLOBALS['xmlrpcstr']['cannot_decompress']); + return $r; + } + } + } + } // end of 'if needed, de-chunk, re-inflate response' + + // real stupid hack to avoid PHP 4 complaining about returning NULL by ref + $r = null; + $r =& $r; + return $r; + } + + /** + * Parse the xmlrpc response contained in the string $data and return an xmlrpcresp object. + * @param string $data the xmlrpc response, eventually including http headers + * @param bool $headers_processed when true prevents parsing HTTP headers for interpretation of content-encoding and consequent decoding + * @param string $return_type decides return type, i.e. content of response->value(). Either 'xmlrpcvals', 'xml' or 'phpvals' + * @return xmlrpcresp + * @access public + */ + function &parseResponse($data='', $headers_processed=false, $return_type='xmlrpcvals') + { + if($this->debug) + { + //by maHo, replaced htmlspecialchars with htmlentities + print "
---GOT---\n" . htmlentities($data) . "\n---END---\n"; + } + + if($data == '') + { + error_log('XML-RPC: xmlrpcmsg::parseResponse: no response received from server.'); + $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_data'], $GLOBALS['xmlrpcstr']['no_data']); + return $r; + } + + $GLOBALS['_xh']=array(); + + $raw_data = $data; + // parse the HTTP headers of the response, if present, and separate them from data + if(substr($data, 0, 4) == 'HTTP') + { + $r =& $this->parseResponseHeaders($data, $headers_processed); + if ($r) + { + // failed processing of HTTP response headers + // save into response obj the full payload received, for debugging + $r->raw_data = $data; + return $r; + } + } + else + { + $GLOBALS['_xh']['headers'] = array(); + $GLOBALS['_xh']['cookies'] = array(); + } + + if($this->debug) + { + $start = strpos($data, '', $start); + $comments = substr($data, $start, $end-$start); + print "
---SERVER DEBUG INFO (DECODED) ---\n\t".htmlentities(str_replace("\n", "\n\t", base64_decode($comments)))."\n---END---\n";
+ }
+ }
+
+ // be tolerant of extra whitespace in response body
+ $data = trim($data);
+
+ /// @todo return an error msg if $data=='' ?
+
+ // be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts)
+ // idea from Luca Mariano ---PARSED---\n"; + // somehow htmlentities chokes on var_export, and some full html string... + //print htmlentitites(var_export($GLOBALS['_xh']['value'], true)); + print htmlspecialchars(var_export($GLOBALS['_xh']['value'], true)); + print "\n---END---"; + } + + // note that using =& will raise an error if $GLOBALS['_xh']['st'] does not generate an object. + $v =& $GLOBALS['_xh']['value']; + + if($GLOBALS['_xh']['isf']) + { + /// @todo we should test here if server sent an int and a string, + /// and/or coerce them into such... + if ($return_type == 'xmlrpcvals') + { + $errno_v = $v->structmem('faultCode'); + $errstr_v = $v->structmem('faultString'); + $errno = $errno_v->scalarval(); + $errstr = $errstr_v->scalarval(); + } + else + { + $errno = $v['faultCode']; + $errstr = $v['faultString']; + } + + if($errno == 0) + { + // FAULT returned, errno needs to reflect that + $errno = -1; + } + + $r =& new xmlrpcresp(0, $errno, $errstr); + } + else + { + $r=&new xmlrpcresp($v, 0, '', $return_type); + } + } + + $r->hdrs = $GLOBALS['_xh']['headers']; + $r->_cookies = $GLOBALS['_xh']['cookies']; + $r->raw_data = $raw_data; + return $r; + } + } + + class xmlrpcval + { + var $me=array(); + var $mytype=0; + var $_php_class=null; + + /** + * @param mixed $val + * @param string $type any valid xmlrpc type name (lowercase). If null, 'string' is assumed + */ + function xmlrpcval($val=-1, $type='') + { + /// @todo: optimization creep - do not call addXX, do it all inline. + /// downside: booleans will not be coerced anymore + if($val!==-1 || $type!='') + { + // optimization creep: inlined all work done by constructor + switch($type) + { + case '': + $this->mytype=1; + $this->me['string']=$val; + break; + case 'i4': + case 'int': + case 'double': + case 'string': + case 'boolean': + case 'dateTime.iso8601': + case 'base64': + case 'null': + $this->mytype=1; + $this->me[$type]=$val; + break; + case 'array': + $this->mytype=2; + $this->me['array']=$val; + break; + case 'struct': + $this->mytype=3; + $this->me['struct']=$val; + break; + default: + error_log("XML-RPC: xmlrpcval::xmlrpcval: not a known type ($type)"); + } + /*if($type=='') + { + $type='string'; + } + if($GLOBALS['xmlrpcTypes'][$type]==1) + { + $this->addScalar($val,$type); + } + elseif($GLOBALS['xmlrpcTypes'][$type]==2) + { + $this->addArray($val); + } + elseif($GLOBALS['xmlrpcTypes'][$type]==3) + { + $this->addStruct($val); + }*/ + } + } + + /** + * Add a single php value to an (unitialized) xmlrpcval + * @param mixed $val + * @param string $type + * @return int 1 or 0 on failure + */ + function addScalar($val, $type='string') + { + $typeof=@$GLOBALS['xmlrpcTypes'][$type]; + if($typeof!=1) + { + error_log("XML-RPC: xmlrpcval::addScalar: not a scalar type ($type)"); + return 0; + } + + // coerce booleans into correct values + // NB: we should iether do it for datetimes, integers and doubles, too, + // or just plain remove this check, implemnted on booleans only... + if($type==$GLOBALS['xmlrpcBoolean']) + { + if(strcasecmp($val,'true')==0 || $val==1 || ($val==true && strcasecmp($val,'false'))) + { + $val=true; + } + else + { + $val=false; + } + } + + switch($this->mytype) + { + case 1: + error_log('XML-RPC: xmlrpcval::addScalar: scalar xmlrpcval can have only one value'); + return 0; + case 3: + error_log('XML-RPC: xmlrpcval::addScalar: cannot add anonymous scalar to struct xmlrpcval'); + return 0; + case 2: + // we're adding a scalar value to an array here + //$ar=$this->me['array']; + //$ar[]=&new xmlrpcval($val, $type); + //$this->me['array']=$ar; + // Faster (?) avoid all the costly array-copy-by-val done here... + $this->me['array'][]=&new xmlrpcval($val, $type); + return 1; + default: + // a scalar, so set the value and remember we're scalar + $this->me[$type]=$val; + $this->mytype=$typeof; + return 1; + } + } + + /** + * Add an array of xmlrpcval objects to an xmlrpcval + * @param array $vals + * @return int 1 or 0 on failure + * @access public + * + * @todo add some checking for $vals to be an array of xmlrpcvals? + */ + function addArray($vals) + { + if($this->mytype==0) + { + $this->mytype=$GLOBALS['xmlrpcTypes']['array']; + $this->me['array']=$vals; + return 1; + } + elseif($this->mytype==2) + { + // we're adding to an array here + $this->me['array'] = array_merge($this->me['array'], $vals); + return 1; + } + else + { + error_log('XML-RPC: xmlrpcval::addArray: already initialized as a [' . $this->kindOf() . ']'); + return 0; + } + } + + /** + * Add an array of named xmlrpcval objects to an xmlrpcval + * @param array $vals + * @return int 1 or 0 on failure + * @access public + * + * @todo add some checking for $vals to be an array? + */ + function addStruct($vals) + { + if($this->mytype==0) + { + $this->mytype=$GLOBALS['xmlrpcTypes']['struct']; + $this->me['struct']=$vals; + return 1; + } + elseif($this->mytype==3) + { + // we're adding to a struct here + $this->me['struct'] = array_merge($this->me['struct'], $vals); + return 1; + } + else + { + error_log('XML-RPC: xmlrpcval::addStruct: already initialized as a [' . $this->kindOf() . ']'); + return 0; + } + } + + // poor man's version of print_r ??? + // DEPRECATED! + function dump($ar) + { + foreach($ar as $key => $val) + { + echo "$key => $val
| + 课程列表: + + | +|||
|
+ 章节 ++ |
+
+
+ 引用的编程练习 + + + ++ |
+
+
+
+ + + + |
+
+ 所有编程练习 + +
+ ..下条
+
+
+
+
+ + + + + + |
+
|
+ ** 按照课程搜索会显示该课程下所有编程练习。 + ** 关键字搜索会显示所有编程练习中符合条件的编程练习。 + ** 添加编程插件引用后,需更新课程缓存。 + |
+ |||
| '; + if (has_capability('mod/programming:viewotherprogram', $context)) { + echo $OUTPUT->single_button(new moodle_url('/mod/programming/package.php', array('id' => $cm->id)), get_string('package', 'programming'), 'get'); + } + echo ' | '; + if (has_capability('mod/programming:edittestcase', $context)) { + echo $OUTPUT->single_button(new moodle_url('/mod/programming/rejudge.php', array('id' => $cm->id)), get_string('rejudge', 'programming'), 'get'); + } + echo ' |
$sqldefine_columns(array('seq', 'weight', 'pub', 'ac', 'wa', 're')); + $headers = array( + get_string('testcase', 'programming'), + get_string('weight', 'programming'), + get_string('public', 'programming'), + get_string('AC', 'programming'), + get_string('WA', 'programming'), + get_string('RE', 'programming'), + ); + $table->define_headers($headers); + + #$table->pagesize($perpage, $total); + $table->set_attribute('cellspacing', '0'); + $table->set_attribute('id', 'detail-table'); + $table->set_attribute('class', 'generaltable generalbox'); + $table->set_attribute('align', 'center'); + $table->set_attribute('cellpadding', '3'); + $table->set_attribute('cellspacing', '1'); + $table->setup(); + + $rst = get_recordset_sql($sql); + while ($row = $rst->FetchNextObject(false)) { + $data = array(); + $data[] = $row->seq; + $data[] = $row->weight; + $data[] = programming_testcase_pub_getstring($row->pub); + $data[] = $row->AC; + $data[] = $row->WA; + $data[] = $row->RE; + $table->add_data($data); + } + $rst->Close(); + + $table->print_html(); + + return 0; +} + +?> diff --git a/resemble/analyze.php b/resemble/analyze.php new file mode 100644 index 0000000..90826f5 --- /dev/null +++ b/resemble/analyze.php @@ -0,0 +1,133 @@ + $id, 'group' => $group, 'action' => $action, 'max' => $max, 'lowest' => $lowest); +$PAGE->set_url('/mod/programming/resemble/analyze.php', $params); + +if (!$cm = get_coursemodule_from_id('programming', $id)) { + print_error('invalidcoursemodule'); +} + +if (!$course = $DB->get_record('course', array('id' => $cm->course))) { + print_error('coursemisconf'); +} + +if (!$programming = $DB->get_record('programming', array('id' => $cm->instance))) { + print_error('invalidprogrammingid', 'programming'); +} + +require_login($course->id, true, $cm); + +$context = context_module::instance($cm->id); +require_capability('mod/programming:updateresemble', $context); + + +/// Print the page header +$PAGE->set_title($programming->name); +$PAGE->set_heading(format_string($course->fullname)); +echo $OUTPUT->header(); + +/// Print tabs +$renderer = $PAGE->get_renderer('mod_programming'); +$tabs = programming_navtab('resemble', 'resemble-analyze', $course, $programming, $cm); +echo $renderer->render_navtab($tabs); + +/// Print page content + +if ($action) { + if ($group != 0) { + $users = get_group_users($group); + } else { + if ($usergrps = groups_get_all_groups($course->id, $USER->id)) { + foreach ($usergrps as $ug) { + $users = array_merge($users, get_group_users($ug->id)); + } + } else { + $users = False; + } + } + + $sql = "SELECT * FROM {programming_submits} WHERE programmingid={$programming->id}"; + if (is_array($users)) { + $sql .= ' AND userid IN (' . implode(',', array_keys($users)) . ')'; + } + $sql .= ' ORDER BY timemodified DESC'; + $submits = $DB->get_records_sql($sql); + + $users = array(); + $latestsubmits = array(); + if (is_array($submits)) { + foreach ($submits as $submit) { + if (in_array($submit->userid, $users)) + continue; + $users[] = $submit->userid; + $latestsubmits[] = $submit; + } + } + $sql = 'SELECT * FROM {user} WHERE id IN (' . implode(',', $users) . ')'; + $users = $DB->get_records_sql($sql); + + // create dir + $dirname = $CFG->dataroot . '/temp'; + if (!file_exists($dirname)) { + mkdir($dirname, 0777) or ( 'Failed to create dir'); + } + $dirname .= '/programming'; + if (!file_exists($dirname)) { + mkdir($dirname, 0777) or ( 'Failed to create dir'); + } + $dirname .= '/' . $programming->id; + if (file_exists($dirname)) { + if (is_dir($dirname)) { + fulldelete($dirname) or error('Failed to remove dir contents'); + //rmdir($dirname) or error('Failed to remove dir'); + } else { + unlink($dirname) or error('Failed to delete file'); + } + } + mkdir($dirname, 0700) or error('Failed to create dir'); + + $files = array(); + // write files + $exts = array('.txt', '.c', '.cxx', '.java', '.java', '.pas', '.py', '.cs'); + foreach ($latestsubmits as $submit) { + $ext = $exts[$submit->language]; + $filename = "{$dirname}/{$submit->userid}-{$submit->id}{$ext}"; + $files[] = $filename; + $f = fopen($filename, 'w'); + fwrite($f, $submit->code); + fwrite($f, "\r\n"); + fclose($f); + } + //echo "dir is $dirname
| + user_picture($user1, array('courseid' => $course->id)); ?> + + | +timemodified); ?> | ++ user_picture($user2, array('courseid' => $course->id)); ?> + + | +timemodified); ?> | +
'.get_string('cannotfindyoursubmit', 'programming').'
'; + } else { + echo ''.$currentstate = get_string('currentstatus', 'programming', programming_get_submit_status_desc($submit)).'
'; + + if (!empty($submit->compilemessage)) { + echo html_writer::tag('h3', get_string('compilemessage', 'programming')); + echo $OUTPUT->box_start('compilemessage'); + echo programming_format_compile_message($submit->compilemessage); + echo $OUTPUT->box_end(); + } + + if (!empty($submit->judgeresult)) { + echo html_writer::tag('h3', get_string('judgeresult', 'programming')); + $results = $DB->get_records('programming_test_results', array('submitid' => $submit->id), 'testid'); + + if (!empty($results)) { + if ($programming->showmode == PROGRAMMING_SHOWMODE_NORMAL || has_capability('mod/programming:viewdetailresultincontest', $context)) { + $tests = $DB->get_records('programming_tests', array('programmingid' => $programming->id), 'id'); + uasort($results, 'cmp_results_by_test_seq'); + echo html_writer::start_tag('div', array('id' => 'test-result-detail')); + echo html_writer::tag('p', get_string('testresult', 'programming', programming_get_test_results_desc($submit, $results))); + echo html_writer::tag('p', get_string('iostripped', 'programming', '1')); + print_test_result_table(); + echo html_writer::end_tag('div'); + } else { + echo html_writer::tag('p', programming_contest_get_judgeresult($results)); + } + } + } + + $strviewprogram = get_string('viewprogram', 'programming'); + $viewprogramurl = 'history.php?id='.$id; + if ($submitid) $viewprogramurl .= '&userid='.$submit->userid; + echo ""; + } + +/// Finish the page + echo $OUTPUT->footer($course); + +function print_test_result_table() +{ + global $CFG, $OUTPUT, $PAGE; + global $tests, $results; + global $cm, $programming, $viewhiddentestcase, $params; + + $strsecuretestcase = get_string('securetestcase', 'programming'); + $strshowasplaintext = get_string('showasplaintext', 'programming'); + $strdownload = get_string('download', 'programming'); + + $table = new html_table(); + $headers = array( + get_string('testcasenumber', 'programming'), + get_string('weight', 'programming'), //.$OUTPUT->help_icon('weight', 'programming'), + get_string('timelimit', 'programming'), //.helpbutton('timelimit', 'timelimit', 'programming', true, false, '', true), + get_string('memlimit', 'programming'), //.helpbutton('memlimit', 'memlimit', 'programming', true, false, '', true), + get_string('input', 'programming'), //.helpbutton('input', 'input', 'programming', true, false, '', true), + get_string('expectedoutput', 'programming'), //.helpbutton('expectedoutput', 'expectedoutput', 'programming', true, false, '', true), + get_string('output', 'programming'), //.helpbutton('output', 'output', 'programming', true, false, '', true), + get_string('errormessage', 'programming'), //.helpbutton('stderr', 'stderr', 'programming', true, false, '', true), + get_string('timeused', 'programming'), //.helpbutton('timeused', 'timeused', 'programming', true, false, '', true), + get_string('memused', 'programming'), //.helpbutton('memused', 'memused', 'programming', true, false, '', true), + get_string('exitcode', 'programming'), //.helpbutton('exitcode', 'exitcode', 'programming', true, false, '', true), + get_string('passed', 'programming'), + get_string('judgeresult', 'programming')); + $table->head = $headers; + + $table->attributes = array('id' => 'test-result-detail-table', 'class' => 'generaltable generalbox'); + $table->cellpadding = 3; + $table->cellspacing = 1; + $table->tablealign = 'center'; + $table->colclasses[4] = 'programming-io'; + $table->colclasses[5] = 'programming-io'; + $table->colclasses[6] = 'programming-io'; + $table->colclasses[7] = 'programming-io'; + + if (!is_array($results)) $results = array(); + $i = 0; $id = 0; + $rowclazz = array(); + foreach ($results as $result) { + $rowclazz[] = $result->passed ? 'passed' : 'notpassed'; + $data = array(); + $data[] = $tests[$result->testid]->seq; + $data[] = $tests[$result->testid]->weight; + $data[] = programming_format_timelimit($tests[$result->testid]->timelimit); + $data[] = programming_format_memlimit($tests[$result->testid]->memlimit); + $downloadurl = new moodle_url($CFG->wwwroot.'/mod/programming/testcase/download_io.php', array('id' => $cm->id, 'test' => $result->testid)); + if (true||$viewhiddentestcase || programming_testcase_visible($tests, $result, true, $programming->timediscount <= time())) { + // input + $downloadurl->params(array('type' => 'in', 'download' => 0)); + $action = new popup_action('click', $downloadurl, '_blank', array('height' => 300, 'width' => 400)); + $html = $OUTPUT->action_link($downloadurl, $strshowasplaintext, $action); + $html.= ' '; + $downloadurl->remove_params('download'); + $html.= $OUTPUT->action_link($downloadurl, $strdownload); + $html.= programming_format_io($tests[$result->testid]->input, true); + $data[] = $html; + + // expected output + $downloadurl->params(array('type' => 'out', 'download' => 0)); + $action = new popup_action('click', $downloadurl, '_blank', array('height' => 300, 'width' => 400)); + $html = $OUTPUT->action_link($downloadurl, $strshowasplaintext, $action); + $html.= ' '; + $downloadurl->remove_params('download'); + $html.= $OUTPUT->action_link($downloadurl, $strdownload); + $html.= programming_format_io($tests[$result->testid]->output, true); + $data[] = $html; + + // output + if (!empty($result->output)) { + $downloadurl->params(array('submit' => $result->submitid, 'type' => 'out', 'download' => 0)); + $action = new popup_action('click', $downloadurl, '_blank', array('height' => 300, 'width' => 400)); + $html = $OUTPUT->action_link($downloadurl, $strshowasplaintext, $action); + + $html.= ' '; + $downloadurl->remove_params('download'); + $html.= $OUTPUT->action_link($downloadurl, $strdownload); + $html.= programming_format_io($result->output, false); + $data[] = $html; + } else { + $data[] = get_string('noresult', 'programming'); + } + + // error message + if (!empty($result->stderr)) { + $downloadurl->params(array('submit' => $result->submitid, 'type' => 'err', 'download' => 0)); + $action = new popup_action('click', $downloadurl, '_blank', array('height' => 300, 'width' => 400)); + $html = $OUTPUT->action_link($downloadurl, $strshowasplaintext, $action); + $html.= ' '; + $downloadurl->remove_params('download'); + $html.= $OUTPUT->action_link($downloadurl, $strdownload); + $html.= programming_format_io($result->stderr, false); + $data[] = $html; + } else { + $data[] = get_string('n/a', 'programming'); + } + } else { + $data[] = $strsecuretestcase; $data[] = $strsecuretestcase; + $data[] = $strsecuretestcase; $data[] = $strsecuretestcase; + } + + $data[] = round($result->timeused, 3); + $data[] = $result->memused; + + if ($viewhiddentestcase || programming_testcase_visible($tests, $results)) { + $data[] = $result->exitcode; + } else { + $data[] = $strsecuretestcase; + } + + $data[] = get_string($result->passed ? 'yes' : 'no'); + $data[] = programming_get_judgeresult($result); + $table->data[] = $data; + } + + $table->rowclasses = $rowclazz; + echo html_writer::table($table); +} + +function cmp_results_by_test_seq($a, $b) { + global $tests; + return $tests[$a->testid]->seq - $tests[$b->testid]->seq; +} + +?> diff --git a/settings.php b/settings.php new file mode 100644 index 0000000..ea26787 --- /dev/null +++ b/settings.php @@ -0,0 +1,11 @@ +fulltree) { + + $settings->add(new admin_setting_configtext('programming_ojip', get_string('programming_ojip', 'programming'), + get_string('configojip', 'programming'), '')); + $settings->add(new admin_setting_configtext('programming_moss_userid', get_string('programming_moss_userid', 'programming'), + get_string('programming_moss_useridinfo', 'programming'), '')); +} diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..77d2e55 --- /dev/null +++ b/styles.css @@ -0,0 +1,506 @@ +.path-mod-programming .userpicture { + vertical-align: middle; +} + +.path-mod-programming #region-main .region-content { + font-size: 14px; +} + +.path-mod-programming #region-main .region-content h2 { + text-align: center; + padding: 4px; +} + +.path-mod-programming #region-main .region-content h3 { + text-align: center; +} + +.path-mod-programming #region-main .region-content .generaltable { + margin-left: auto; + margin-right: auto; +} + +.path-mod-programming #region-main .region-content .cell { + padding: 3px; + vertical-align: middle; +} + +.path-mod-programming #region-main .region-content .description, +.path-mod-programming #region-main .region-content form .felement { + text-align: left; +} + +.path-mod-programming .buttons { + margin: 1em; + text-align: center; +} + +.path-mod-programming .filters dl { + font-size: 12px; +} + +.path-mod-programming .filters dl { + margin: 0 0 5px; +} + +.path-mod-programming .filters dt { + float: left; + width: 80px; + font-weight: bold; + text-align: right; + line-height: 25px; +} + +.path-mod-programming .filters dd { + float: none; + height: auto; + margin: 0 0 0 80px; + padding: 0; + text-align: left; + display: block; +} + +.path-mod-programming .filters dd span { + line-height: 23px; + margin-left: 10px; +} + +.path-mod-programming .filters a { + padding: 1px 2px; + text-decoration: none; + white-space: nowrap; + overflow: hidden; +} + +.path-mod-programming .filters a.here, +.path-mod-programming .filters a:hover, +.path-mod-programming .filters a:active { + background-color: #4598D2; + color: white; +} + +#page-mod-programming-view #region-main .region-content h3 { text-align: left; } +.path-mod-programming #intro { text-align: left; padding: 0em 1em 0em 1em; } +.path-mod-programming #datafile { text-align: left; padding: 0em 1em 0em 1em; } +.path-mod-programming #presetcode { text-align: left; padding: 0em 1em 0em 1em; } +.path-mod-programming #presetcode pre { background-color: #eeeeee; padding: 0em 0.5em 0em 2.5em; } +.path-mod-programming #testcase-table { padding: 1em; } +.path-mod-programming #time-table th { text-align: right } +.path-mod-programming #time-table td { text-align: left } + +.path-mod-programming #submitagainconfirm, +.path-mod-programming #submitagainconfirm p { + text-align: center; +} + +.path-mod-programming .compilemessage { + padding: 1px; + margin: 0 auto; + font-family: "Consolas","Courier New",Courier,mono,serif; + font-weight: bold; + text-align: left; + background: #F0A0A0; +} +.path-mod-programming .compilemessage ol { + list-style-type: none; +} +.path-mod-programming .compilemessage li, .compilemessage li.warning, +.path-mod-programming .compilemessage li.normal, .compilemessage li.error { + font-family: "Consolas","Courier New",Courier,mono,serif; + font-style: normal; + font-weight: bold; +} +.path-mod-programming .compilemessage li.warning { + color: black; +} +.path-mod-programming .compilemessage li.normal { + color: blue; +} +.path-mod-programming .compilemessage li.error { + color: #800000; +} + +.path-mod-programming #test-result-detail .passed, +.path-mod-programming #test-result-detail .passed .cell { + background-color: #A0EEA0; /* light green */ +} +.path-mod-programming #test-result-detail .notpassed, +.path-mod-programming #test-result-detail .notpassed .cell { + background-color: #F0A0A0; /* light red */ +} + +#page-mod-programming-history td { vertical-align: top; } +#page-mod-programming-history #submitlist { + display: block; + width: 14em; +} +#page-mod-programming-history #submitlist table { + margin: 0.5em 0; +} +#page-mod-programming-history #submitlist table th, +#page-mod-programming-history #submitlist table td { + padding: 1px; +} + +.path-mod-programming #codeview { + display: block; + width: 46em; + height: 29em; + text-align: left; + overflow: scroll; +} + +.path-mod-programming .diff { + margin: 0 auto; + width: 52em; +} + +.path-mod-programming .diff th { + color: #666666; + font-weight: normal; + padding: 0 0.6em; + text-align: right; + width: 2em; +} + +.path-mod-programming .diff td { + font-family: "Consolas","Courier New",Courier,mono,serif; +} + +.path-mod-programming .diff .added { + background-color: #99FF99; + border-color: #33AA33; + border-style: solid; + border-width: 0 1px; +} + +.path-mod-programming .diff .deleted { + background-color: #FF8888; + border-color: #AA3333; + border-style: solid; + border-width: 0 1px; +} + +.path-mod-programming .diff .first { + border-top-width: 1px; +} + +.path-mod-programming .diff .last { + border-bottom-width: 1px; +} + +.path-mod-programming .diff .added-changed { + background-color: #DDF8CC; + border-color: #33AA33; + border-style: solid; + border-width: 0 1px; +} + +.path-mod-programming .diff .deleted-changed { + background-color: #FFD8D8; + border-color: #AA3333; + border-style: solid; + border-width: 0 1px; +} + +.path-mod-programming .resemble-compare-programs { + height: 30em; +} +.path-mod-programming .resemble-compare-programs div { + overflow: scroll; + height: 30em; + max-width: 480px; + float: left; +} + +.path-mod-programming .code { + font-family: monospace; + background-color: #e7e7e7; +} +.path-mod-programming .match1 { color: Red; } +.path-mod-programming .match2 { color: BlueViolet; } +.path-mod-programming .match3 { color: Brown; } +.path-mod-programming .match4 { color: BurlyWood; } +.path-mod-programming .match5 { color: CadetBlue; } +.path-mod-programming .match6 { color: DarkGoldenRod; } +.path-mod-programming .match7 { color: Chocolate; } +.path-mod-programming .match8 { color: DarkGreen; } + +.path-mod-programming .resemble-list .cell { text-align: center; } +.path-mod-programming .resemble-list .warned { color: orange; } +.path-mod-programming .resemble-list .confirmed { color: red; } +.path-mod-programming .resemble-list .flag3 { color: #888888; } + +.path-mod-programming-reports table { + margin: 0 auto; + text-align: center; + width: 100%; +} + +.path-mod-programming-reports table .fullname { + text-align: left; +} + +.path-mod-programming .chart { + height: 300px; + width: 400px; + margin: 10px auto; +} + +.path-mod-programming .noticebox { + margin: 0 auto; + padding: 5px; + text-align: center; + background-color: #FFAAAA; + width: 60%; +} + +.path-mod-programming td.programming-io a { + font-size: 9pt; +} + +.path-mod-programming td.programming-io div { + vertical-align: top; + padding: 0px 5px 0px 0px; + width: 10em; + height: 10em; + overflow: auto; +} + +.path-mod-programming td.programming-io ol { + text-align: left; + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + padding: 0 0 0 auto; + overflow: visible; +} + +.path-mod-programming td.programming-io li { + font-family: "Consolas","Courier New",Courier,mono,serif; + margin: 0; + padding: 0; + overflow: visible; +} + +.path-mod-programming td.programming-io span { + background-color: #D3D3D3; + margin: 0; + padding: 0; + white-space: nowrap; +} + +.hl-default { + color: Black; +} +.hl-code { + color: Gray; +} +.hl-brackets { + color: Olive; +} +.hl-comment { + color: Orange; +} +.hl-quotes { + color: Darkred; +} +.hl-string { + color: Red; +} +.hl-identifier { + color: Blue; +} +.hl-builtin { + color: Teal; +} +.hl-reserved { + color: Green; +} +.hl-inlinedoc { + color: Blue; +} +.hl-var { + color: Darkblue; +} +.hl-url { + color: Blue; +} +.hl-special { + color: Navy; +} +.hl-number { + color: Maroon; +} +.hl-inlinetags { + color: Blue; +} +.hl-main { + background-color: White; +} +.hl-gutter { + background-color: #999999; + color: White +} +.hl-table { + font-family: "Consolas","Courier New",Courier,mono,serif; + font-size: 12px; + border: solid 1px Lightgrey; +} +.dp-highlighter li { + font-family: "Consolas","Courier New",Courier,mono,serif; +} + +.default_input +{ + border:1px solid #666666; + height:18px; + font-size:12px; +} +.default_input2 +{ + border:1px solid #666666; + height:18px; + font-size:12px; +} +.nowrite_input +{ + border:1px solid #849EB5; + height:18px; + font-size:12px; + background-color:#EBEAE7; + color: #9E9A9E; +} +.default_list +{ + font-size:12px; + border:1px solid #849EB5; +} +.default_textarea +{ + font-size:12px; + border:1px solid #849EB5; +} +.nowrite_textarea +{ + border:1px solid #849EB5; + font-size:12px; + background-color:#EBEAE7; + color: #9E9A9E; +} +.wordtd5 { + font-size: 12px; + text-align: center; + vertical-align:top; + padding-top: 6px; + padding-right: 5px; + padding-bottom: 3px; + padding-left: 5px; + background-color: #b8c4f4; +} +.wordtd { + font-size: 12px; + text-align: left; + vertical-align:top; + padding-top: 6px; + padding-right: 5px; + padding-bottom: 3px; + padding-left: 5px; + background-color: #b8c4f4; +} +.wordtd_1 { + font-size: 12px; + vertical-align:top; + padding-top: 6px; + padding-right: 5px; + padding-bottom: 3px; + padding-left: 5px; + background-color: #516CD6; + color:#fff; +} +.wordtd_2{ + font-size: 12px; + text-align: right; + vertical-align:top; + padding-top: 6px; + padding-right: 5px; + padding-bottom: 3px; + padding-left: 5px; + background-color: #64BDF9; +} +.wordtd_3{ + font-size: 12px; + text-align: right; + vertical-align:top; + padding-top: 6px; + padding-right: 5px; + padding-bottom: 3px; + padding-left: 5px; + background-color: #F1DD34; +} +.inputtd +{ + font-size:12px; + vertical-align:top; + padding-top: 3px; + padding-right: 3px; + padding-bottom: 3px; + padding-left: 3px; +} +.inputtd2 +{ + text-align: center; + font-size:12px; + vertical-align:top; + padding-top: 3px; + padding-right: 3px; + padding-bottom: 3px; + padding-left: 3px; +} +.tablebg +{ + font-size:12px; +} +.tb{ + border-collapse: collapse; + border: 1px outset #999999; + background-color: #FFFFFF; +} +.td2{line-height:22px; text-align:center; background-color:#F6F6F6;} +.td3{background-color:#B8D3F4; text-align:center; line-height:20px;} +.td4{background-color:#F6F6F6;line-height:20px;} +.td5{border:#000000 solid; + border-right-width:0px; + border-left-width:0px; + border-top-width:0px; + border-bottom-width:1px;} +.tb td{ +font-size: 12px; +border: 2px groove #ffffff; +} +.noborder { + border: none; +} +.button { + border: 1px ridge #ffffff; + line-height:18px; + height: 40px; + width: 45px; + padding-top: 0px; + background:#CBDAF7; + color:#000000; + font-size: 9pt; +} +.textarea { + font-family: Arial, Helvetica, sans-serif, "??"; + font-size: 9pt; + color: #000000; + border-bottom-width: 1px; + border-top-style: none; + border-right-style: none; + border-bottom-style: solid; + border-left-style: none; + border-bottom-color: #000000; + background-color:transparent; + text-align: left +} \ No newline at end of file diff --git a/submit.php b/submit.php new file mode 100644 index 0000000..64b62f0 --- /dev/null +++ b/submit.php @@ -0,0 +1,227 @@ +sessioncookie; + $default_language = 0; + if (isset($_COOKIE[$cookiename])) { + $default_language = $_COOKIE[$cookiename]; + } + if (!isset($language)) $language = $default_language; + + $params = array('id' => $id); + $PAGE->set_url('/mod/programming/submit.php', $params); + $PAGE->requires->css('/mod/programming/codemirror/lib/codemirror.css'); + // $PAGE->requires->css('/mod/programming/codemirror/doc/docs.css'); //把这个注释掉,CSS就不会错位了。 + $PAGE->requires->css('/mod/programming/codemirror/theme/eclipse.css'); +// $PAGE->requires->js('/mod/programming/codemirror/lib/codemirror.js'); +// $PAGE->requires->js('/mod/programming/codemirror/mode/clike/clike.js'); +// $PAGE->requires->js('/mod/programming/codemirror/mode/pascal/pascal.js'); +// $PAGE->requires->js('/mod/programming/codemirror/mode/python/python.js'); +// $PAGE->requires->js('/mod/programming/codemirror/mode/shell/shell.js'); +/* echo ' + + + + + +';//*/ + if (! $cm = get_coursemodule_from_id('programming', $id)) { + print_error('invalidcoursemodule'); + } + + if (! $course = $DB->get_record('course', array('id' => $cm->course))) { + print_error('coursemisconf'); + } + + if (! $programming = $DB->get_record('programming', array('id' => $cm->instance))) { + print_error('invalidprogrammingid', 'programming'); + } + + require_login($course->id, true, $cm); + + $context = context_module::instance($cm->id); + $PAGE->set_context($context); + + require_capability('mod/programming:submitprogram', $context); + $submitatanytime = has_capability('mod/programming:submitatanytime', $context); + + $result = $DB->get_record('programming_result', array('programmingid' => $programming->id, 'userid' => $USER->id)); + $submitcount = is_object($result) ? $result->submitcount : 0; + $time = time(); + $isearly = $time < $programming->timeopen; + $islate = !$programming->allowlate && $time > $programming->timeclose; + $istoomore = $programming->attempts != 0 && $submitcount > $programming->attempts; + $allowpost = $submitatanytime || (!$isearly && !$islate && !$istoomore); + + // Check if user has passed the practice + $haspassed = false; + if ($submitcount > 0) { + $latestsubmit = $DB->get_record('programming_submits', array('id' => $result->latestsubmitid)); + $haspassed = is_object($latestsubmit) && $latestsubmit->passed; + } + + $mform = new submit_form(); + if ($mform->is_cancelled()) { + redirect(new moodle_url('view.php', array('id' => $cm->id))); + } else { + if ($allowpost && $submit = $mform->get_data()) { + $submits_count = $DB->count_records('programming_submits', array('programmingid' => $programming->id, 'userid' => $USER->id)); + if (!$submitatanytime && ($programming->attempts != 0 && $programming->attempts <= $submits_count)) { + $error = get_string('submitfailednoattempts', 'programming'); + $submit = False; + } + + if ($submit) { + $submit->userid = $USER->id; + $submit->programmingid = $programming->id; + $code = $submit->code; + if ($sourcefile = $mform->get_file_content('sourcefile')) { + $code = $sourcefile; + } + if ($programming->presetcode) { + $code = programming_submit_remove_preset($code); + } + $submit->code = trim($code); + if($submit->language==6){ + // $submit->code='import sys;import codecs;sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach())'. PHP_EOL.$submit->code; + } + if ($submit->code == '') { + $error = get_string('submitfailedemptycode', 'programming'); + $submit = False; + } + + if ($submit) { + unset($submit->id); + programming_submit_add_instance($programming, $submit); + + // Send events + $ue = new stdClass(); + $ue->userid = $USER->id; + $ue->programmingid = $programming->id; + $ue->language = $submit->language; + $ue->timemodified = $submit->timemodified; + + } + } + } + } + +/// Print the page header + setcookie($cookiename, $language, time() + 3600 * 24 * 60, $CFG->sessioncookiepath); + + if (!empty($action) && is_object($submit)) { + $PAGE->requires->css('/mod/programming/js/dp/SyntaxHighlighter.css'); + $PAGE->requires->js('/mod/programming/js/dp/shCore.js'); + $PAGE->requires->js('/mod/programming/js/dp/shBrushCSharp.js'); + } + $PAGE->set_title($programming->name); + $PAGE->set_heading(format_string($course->fullname)); + echo $OUTPUT->header(); + +/// Print tabs + $renderer = $PAGE->get_renderer('mod_programming'); + $tabs = programming_navtab('submit', null, $course, $programming, $cm); + echo $renderer->render_navtab($tabs); + +/// Print the main part of the page + echo html_writer::tag('h2', $programming->name); + echo html_writer::tag('h3', get_string('submit', 'programming').$OUTPUT->help_icon('submit', 'programming')); + + if (is_object($submit)) { + echo html_writer::tag('h1', get_string('submitsuccess', 'programming')); + echo $OUTPUT->action_link(new moodle_url('result.php', array('id' => $cm->id)), get_string('viewresults', 'programming')); + } else { + print_submit(); +//js code + echo ' + + + + + + +'; + + } + +/// Finish the page + + echo $OUTPUT->footer($course); + + +function print_submit() { + global $PAGE,$DB, $OUTPUT, $cm, $programming, $mform; + global $allowpost, $haspassed, $islate, $isearly; + if ($allowpost) { + if ($haspassed) { + echo html_writer::start_tag('div', array('id' => 'submitagainconfirm')); + echo html_writer::tag('p', get_string('youhavepassed', 'programming')); + echo html_writer::empty_tag('input', array('type' => 'button', 'id' => 'submitagain', 'name' => 'submitagain', 'value' => get_string('submitagain', 'programming'))); + $PAGE->requires->js_init_call('M.mod_programming.init_submit'); + echo html_writer::end_tag('div'); + } + + echo html_writer::start_tag('div', array('id' => 'submit')); + $mform->display(); + echo html_writer::end_tag('div'); + } + + if ($isearly) { + echo html_writer::tag('p', get_string('programmingnotopen', 'programming')); + } + + if ($islate) { + echo html_writer::tag('p', get_string('timeexceed', 'programming')); + } + +} + diff --git a/submit_form.php b/submit_form.php new file mode 100644 index 0000000..487916b --- /dev/null +++ b/submit_form.php @@ -0,0 +1,24 @@ +libdir.'/formslib.php'); + +class submit_form extends moodleform { + + function definition() { + global $CFG, $COURSE, $OUTPUT, $cm, $programming; + global $default_language, $submitfor; + $mform =& $this->_form; +//------------------------------------------------------------------------------- + $mform->addElement('hidden', 'id', $cm->id); + $mform->setType('id', PARAM_INT); + $mform->addElement('textarea', 'code', get_string('programcode', 'programming'), 'rows="20" cols="90"'); + $attributes = 'onchange ="change()"'; + + $mform->addElement('select', 'language', get_string('programminglanguage', 'programming'), programming_get_language_options($programming),$attributes); + $mform->setDefault('language', $default_language); + $mform->addElement('filepicker', 'sourcefile', get_string('sourcefile', 'programming'), null, array('maxbytes' => 65536)); + +// buttons + $this->add_action_buttons(); + } + +} diff --git a/testcase/add.php b/testcase/add.php new file mode 100644 index 0000000..1d5af87 --- /dev/null +++ b/testcase/add.php @@ -0,0 +1,81 @@ +libdir . '/weblib.php'); +require_once('../lib.php'); +require_once('form.php'); + +$id = required_param('id', PARAM_INT); // programming ID +$params = array('id' => $id); +$PAGE->set_url('/mod/programming/testcase/add.php', $params); + +if (!$cm = get_coursemodule_from_id('programming', $id,0,false,MUST_EXIST)) { + print_error('invalidcoursemodule'); +} + +if (!$course = $DB->get_record('course', array('id' => $cm->course))) { + print_error('coursemisconf'); +} + +if (!$programming = $DB->get_record('programming', array('id' => $cm->instance))) { + print_error('invalidprogrammingid', 'programming'); +} + +require_login($course->id, true, $cm); +$context = context_module::instance($cm->id); +require_capability('mod/programming:edittestcase', $context); + +$mform = new testcase_form(); +if ($mform->is_cancelled()) { + redirect(new moodle_url('list.php', array('id' => $cm->id))); +} else if ($data = $mform->get_data()) { + unset($data->id); + $data->programmingid = $programming->id; + $data->seq = $DB->count_records('programming_tests', array('programmingid' => $programming->id), 'MAX(seq)') + 1; + $infile = $mform->get_file_content('inputfile'); + if (empty($infile)) { + $data->input = stripcslashes($data->input); + } else { + $data->input = $infile; + } + $outfile = $mform->get_file_content('outputfile'); + if (empty($outfile)) { + $data->output = stripcslashes($data->output); + } else { + $data->output = $outfile; + } +// $data->input = str_replace("\r\n\r\n", "", $data->input); + // $data->output = str_replace("\r\n\r\n", "", $data->output); + // $data->input = str_replace("\r\n\n", "", $data->input); + // $data->output = str_replace("\r\n\n", "", $data->output); +// $data->input = str_replace(chr(13), "", $data->input); + // $data->output = str_replace(chr(13), "", $data->output); + // $data->input = str_replace("\n\n", "\n", $data->input); + // $data->output = str_replace("\n\n", "\n", $data->output); + $data->input = rtrim($data->input); + $data->output = rtrim($data->output); +// $data->input .= "\n"; + // $data->output .= "\n"; + programming_test_add_instance($data); + + redirect(new moodle_url('list.php', array('id' => $cm->id)), get_string('testcasemodified', 'programming'), 0); +} else { + /// Print the page header + $PAGE->set_title(format_string($course->shortname) . ': ' . $programming->name) . ': ' . get_string('addtestcase', 'programming'); + $PAGE->set_heading(format_string($course->fullname)); + echo $OUTPUT->header(); + + /// Print tabs + $renderer = $PAGE->get_renderer('mod_programming'); + $tabs = programming_navtab('edittest', 'testcase', $course, $programming, $cm); + echo $renderer->render_navtab($tabs); + + echo html_writer::tag('h2', $programming->name); + echo html_writer::tag('h3', get_string('addtestcase', 'programming')); + + /// Print page content + $mform->display(); + + /// Finish the page + echo $OUTPUT->footer($course); +} diff --git a/testcase/delete.php b/testcase/delete.php new file mode 100644 index 0000000..e5e5296 --- /dev/null +++ b/testcase/delete.php @@ -0,0 +1,39 @@ +set_url('/mod/programming/view.php', $params); + + if ($id) { + if (! $cm = get_coursemodule_from_id('programming', $id)) { + error('Course Module ID was incorrect'); + } + + if (! $course = $DB->get_record('course', array('id' => $cm->course))) { + error('Course is misconfigured'); + } + + if (! $programming = $DB->get_record('programming', array('id' => $cm->instance))) { + error('Course module is incorrect'); + } + } + + require_login($course->id, true, $cm); + $context = context_module::instance($cm->id); + require_capability('mod/programming:edittestcase', $context); + + $DB->delete_records('programming_test_results', array('testid' => $case_id)); + $DB->delete_records('programming_tests', array('id' => $case_id)); + programming_testcase_adjust_sequence($programming->id); + redirect(new moodle_url('list.php', array('id' => $cm->id)), get_string('testcasedeleted', 'programming'), 0); diff --git a/testcase/download_io.php b/testcase/download_io.php new file mode 100644 index 0000000..ba497a9 --- /dev/null +++ b/testcase/download_io.php @@ -0,0 +1,89 @@ +set_url('/mod/programming/download_io.php', $params); + + if ($id) { + if (! $cm = get_coursemodule_from_id('programming', $id)) { + error('Course Module ID was incorrect'); + } + + if (! $course = $DB->get_record('course', array('id' => $cm->course))) { + error('Course is misconfigured'); + } + + if (! $programming = $DB->get_record('programming', array('id' => $cm->instance))) { + error('Course module is incorrect'); + } + } + + require_login($course->id, true, $cm); + $context = context_module::instance($cm->id); + + // Download input and output of testcase + if ($type == 'in' or ($type == 'out' and $submitid == -1)) { + if (! $test = $DB->get_record('programming_tests', array('id' => $testid))) { + error('Test ID was incorrect'); + } + programming_testcase_require_view_capability($context, $test); + $filename = sprintf('test-%d.%s', $testid, $type); + if ($type == 'in') { + $content = !empty($test->gzinput) ? bzdecompress($test->gzinput) : $test->input; + } else { + $content = !empty($test->gzoutput) ? bzdecompress($test->gzoutput) : $test->output; + } + } + // Download output and error message of user program + else if ($type == 'out' or $type == 'err') { + require_capability('mod/programming:viewdetailresult', $context); + if (! $result = $DB->get_record('programming_test_results', array('submitid' => $submitid, 'testid' => $testid))) { + error('Test ID or submit ID was incorrect.'); + } + $test = $DB->get_record('programming_tests', array('id' => $testid)); + if ($test->pub >= 0) { + require_capability('mod/programming:viewpubtestcase', $context); + } else { + require_capability('mod/programming:viewhiddentestcase', $context); + } + $submit = $DB->get_record('programming_submits', array('id' => $submitid)); + if ($submit->userid != $USER->id) { + require_capability('mod/programming:viewotherresult', $context); + } + if ($result->judgeresult == 'AC' && strlen($result->output) == 0) { + $result->output = $test->output; + } + $filename = sprintf('test-%d-%d.%s', $testid, $submitid, $type); + $content = $type == 'out' ? $result->output : $result->stderr; + } + + if ($filename && $download) { + header('Content-Type: application/octet-stream'); + header('Content-Disposition: attachment; filename="'.$filename.'"'); + } else { + header('Content-Type: text/plain'); + } + echo $content; diff --git a/testcase/edit.php b/testcase/edit.php new file mode 100644 index 0000000..d7b21b0 --- /dev/null +++ b/testcase/edit.php @@ -0,0 +1,85 @@ +libdir . '/weblib.php'); +require_once('../lib.php'); +require_once('form.php'); + +$id = required_param('id', PARAM_INT); // programming ID +$case_id = required_param('case', PARAM_INT); // testcase ID +$params = array('id' => $id, 'case' => $case_id); +$PAGE->set_url('/mod/programming/testcase/edit.php', $params); + +if (!$cm = get_coursemodule_from_id('programming', $id)) { + print_error('invalidcoursemodule'); +} + +if (!$course = $DB->get_record('course', array('id' => $cm->course))) { + print_error('coursemisconf'); +} + +if (!$programming = $DB->get_record('programming', array('id' => $cm->instance))) { + print_error('invalidprogrammingid', 'programming'); +} + +require_login($course->id, true, $cm); +$context = context_module::instance($cm->id); +require_capability('mod/programming:edittestcase', $context); + +$mform = new testcase_form(); +if ($mform->is_cancelled()) { + redirect(new moodle_url('list.php', array('id' => $cm->id))); +} else if ($data = $mform->get_data()) { + $data->id = $data->case; + $data->programmingid = $programming->id; + unset($data->case); + $infile = $mform->get_file_content('inputfile'); + if (empty($infile)) { + $data->input = stripcslashes($data->input); + } else { + $data->input = $infile; + } + $outfile = $mform->get_file_content('outputfile'); + if (empty($outfile)) { + $data->output = stripcslashes($data->output); + } else { + $data->output = $outfile; + } +// $data->input = str_replace("\r\n\r\n", "", $data->input); + // $data->output = str_replace("\r\n\r\n", "", $data->output); +// $data->input = str_replace("\r\n\n", "", $data->input); + // $data->output = str_replace("\r\n\n", "", $data->output); +// $data->input = str_replace(chr(13), "", $data->input); + // $data->output = str_replace(chr(13), "", $data->output); + // $data->input = str_replace("\n\n", "\n", $data->input); + // $data->output = str_replace("\n\n", "\n", $data->output); + $data->input = rtrim($data->input); + $data->output = rtrim($data->output); +// $data->input .= "\n"; + // $data->output .= "\n"; + programming_test_update_instance($data); + + redirect(new moodle_url('list.php', array('id' => $cm->id)), get_string('testcasemodified', 'programming'), 0); +} else { + $data = $DB->get_record('programming_tests', array('id' => $case_id)); + $mform->set_data($data); + + /// Print the page header + $PAGE->set_title(format_string($course->shortname) . ': ' . $programming->name) . ': ' . get_string('edittestcase', 'programming'); + $PAGE->set_heading(format_string($course->fullname)); + $PAGE->requires->css('/mod/programming/programming.css'); + echo $OUTPUT->header(); + + /// Print tabs + $renderer = $PAGE->get_renderer('mod_programming'); + $tabs = programming_navtab('edittest', 'testcase', $course, $programming, $cm); + echo $renderer->render_navtab($tabs); + + /// Print page content + echo html_writer::tag('h2', $programming->name); + echo html_writer::tag('h3', get_string('edittestcase', 'programming')); + $mform->display(); + + /// Finish the page + echo $OUTPUT->footer($course); +} diff --git a/testcase/form.php b/testcase/form.php new file mode 100644 index 0000000..85309bd --- /dev/null +++ b/testcase/form.php @@ -0,0 +1,65 @@ +libdir.'/formslib.php'); + +class testcase_form extends moodleform { + + function definition() { + global $CFG, $COURSE, $OUTPUT, $cm, $programming; + $mform =& $this->_form; + +//------------------------------------------------------------------------------- + $mform->addElement('hidden', 'id', $cm->id); + $mform->setType('id', PARAM_INT); + $mform->addElement('hidden', 'case'); + $mform->setType('case', PARAM_INT); + +// $mform->addElement('textarea', 'input', get_string('input', 'programming').$OUTPUT->help_icon('input', 'programming'), 'rows="2" cols="50"'); + $mform->addElement('filepicker', 'inputfile', get_string('usefile', 'programming')); +// $mform->addElement('textarea', 'output', get_string('output', 'programming').$OUTPUT->help_icon('output', 'programming'), 'rows="2" cols="50"'); + $mform->addElement('filepicker', 'outputfile', get_string('usefile', 'programming')); + + $mform->addElement('select', 'timelimit', get_string('timelimit', 'programming').$OUTPUT->help_icon('timelimit', 'programming'), programming_get_timelimit_options()); + $mform->setDefault('timelimit', $programming->timelimit); + + $mform->addElement('select', 'memlimit', get_string('memlimit', 'programming').$OUTPUT->help_icon('memlimit', 'programming'), programming_get_memlimit_options()); + $mform->setDefault('memlimit', $programming->memlimit); + + $mform->addElement('select', 'nproc', get_string('extraproc', 'programming').$OUTPUT->help_icon('nproc', 'programming'), programming_get_nproc_options()); + $mform->setDefault('nproc', $programming->nproc); + + $mform->addElement('select', 'weight', get_string('weight', 'programming').$OUTPUT->help_icon('weight', 'programming'), programming_get_weight_options()); + $mform->setDefault('weight', 1); + + $mform->addElement('select', 'pub', get_string('testcasepub', 'programming').$OUTPUT->help_icon('testcasepub', 'programming'), programming_testcase_pub_options()); + $mform->setDefault('pub',-1); + +// $mform->addElement('textarea', 'memo', get_string('memo', 'programming'), 'rows="2" cols="50"'); + +// buttons + $this->add_action_buttons(); + } + + function set_data($data) { + $data->case = $data->id; + unset($data->id); + if (strlen($data->input) > 1023) { + $data->input = ''; + } + if (strlen($data->output) > 1023) { + $data->output = ''; + } + parent::set_data($data); + } + + /* + function validation($data, $files) { + $errors = array(); + + if (empty($data['output']) or trim($data['output']) == '') + if (empty($files['outputfile'])) + $errors['output'] = get_string('required'); + + return $errors; + }*/ + +} diff --git a/testcase/list.php b/testcase/list.php new file mode 100644 index 0000000..7d71e45 --- /dev/null +++ b/testcase/list.php @@ -0,0 +1,150 @@ +libdir.'/tablelib.php'); + require_once('../lib.php'); + + $id = required_param('id', PARAM_INT); // programming ID + $params = array('id' => $id); + $PAGE->set_url('/mod/programming/testcase/list.php', $params); + + if (! $cm = get_coursemodule_from_id('programming', $id)) { + print_error('invalidcoursemodule'); + } + + if (! $course = $DB->get_record('course', array('id' => $cm->course))) { + print_error('coursemisconf'); + } + + if (! $programming = $DB->get_record('programming', array('id' => $cm->instance))) { + print_error('invalidprogrammingid', 'programming'); + } + + require_login($course->id, true, $cm); + $context = context_module::instance($cm->id); + + require_capability('mod/programming:viewhiddentestcase', $context); + +/// Print the page header + $PAGE->set_title(format_string($course->shortname).': '.$programming->name).': '.get_string('testcase', 'programming'); + $PAGE->set_heading(format_string($course->fullname)); + echo $OUTPUT->header(); + +/// Print tabs + $renderer = $PAGE->get_renderer('mod_programming'); + $tabs = programming_navtab('edittest', 'testcase', $course, $programming, $cm); + echo $renderer->render_navtab($tabs); + +/// Print page content + echo html_writer::tag('h2', $programming->name); + echo html_writer::tag('h3', get_string('testcase', 'programming').$OUTPUT->help_icon('testcase', 'programming')); + print_testcase_table(); + +/// Finish the page + echo $OUTPUT->footer($course); + +function print_testcase_table() { + global $CFG, $OUTPUT, $DB, $cm, $params, $programming, $course, $language, $groupid; + + $table = new html_table(); + $table->head = array( + get_string('sequence', 'programming'), + get_string('testcasepub', 'programming').$OUTPUT->help_icon('testcasepub', 'programming'), + get_string('input', 'programming').$OUTPUT->help_icon('input', 'programming'), + get_string('output', 'programming').$OUTPUT->help_icon('output', 'programming'), + get_string('timelimit', 'programming').$OUTPUT->help_icon('timelimit', 'programming'), + get_string('memlimit', 'programming').$OUTPUT->help_icon('memlimit', 'programming'), + get_string('extraproc', 'programming').$OUTPUT->help_icon('nproc', 'programming'), + get_string('weight', 'programming').$OUTPUT->help_icon('weight', 'programming'), + get_string('action'), + ); + + //$table->set_attribute('id', 'presetcode-table'); + //$table->set_attribute('class', 'generaltable generalbox'); + $table->tablealign = 'center'; + $table->cellpadding = 3; + $table->cellspacing = 1; + $table->colclasses[2] = 'programming-io'; + $table->colclasses[3] = 'programming-io'; + //$table->no_sorting('code'); + $table->data = array(); + + $strshowasplaintext = get_string('showasplaintext', 'programming'); + $strdownload = get_string('download', 'programming'); + $stredit = get_string('edit'); + $strdelete = get_string('delete'); + $strmoveup = get_string('moveup'); + $strmovedown = get_string('movedown'); + $fields = 'id,programmingid,seq,input,output,cmdargs,timelimit,memlimit,nproc,pub,weight,memo,timemodified'; + $tests = $DB->get_records('programming_tests', array('programmingid' => $programming->id), 'seq',$fields); + + if (is_array($tests)) { + $tests_count = count($tests)-1; + $i = 0; + foreach ($tests as $case) { + $data = array(); + $data[] = $case->seq; + $data[] = programming_testcase_pub_getstring($case->pub); + + // stdin + $url = new moodle_url('/mod/programming/testcase/download_io.php', array('id' => $cm->id, 'test' => $case->id, 'type'=> 'in', 'download' => 0)); + $html = $OUTPUT->action_link($url, $strshowasplaintext, new popup_action('click', $url), array('class' => 'showasplaintext small')); + $html .= ' '; + $url->param('download', 1); + $html .= $OUTPUT->action_link($url, $strdownload, null, array('class' => 'download small')); + $html .= programming_format_io($case->input, false); + $data[] = $html; + + // stdout + $url->params(array('type' => 'out', 'download' => 0)); + $html = $OUTPUT->action_link($url, $strshowasplaintext, new popup_action('click', $url), array('class' => 'showasplaintext small')); + $html .= ' '; + $url->param('download', 1); + $html .= $OUTPUT->action_link($url, $strdownload, null, array('class' => 'download small')); + $html .= programming_format_io($case->output, false); + $data[] = $html; + + // limits + $data[] = get_string('nseconds', 'programming', $case->timelimit); + $data[] = get_string('nkb', 'programming', $case->memlimit); + $data[] = $case->nproc; + + $data[] = get_string('nweight', 'programming', $case->weight); + + // actions + $actions = array(); + $actions[] = $OUTPUT->action_link( + new moodle_url('edit.php', array('id' => $cm->id, 'case' => $case->id)), + html_writer::empty_tag('img', array('title' => $stredit, 'src' => $OUTPUT->image_url('t/edit'))), + null, + array('class' => 'icon edit')); + $url = new moodle_url('/mod/programming/testcase/delete.php', array('id' => $cm->id, 'case' => $case->id)); + $txt = html_writer::empty_tag('img', array('title' => $strdelete, 'src' => $OUTPUT->image_url('t/delete'))); + $act = new confirm_action(get_string('deletetestcaseconfirm', 'programming')); + $actions[] = $OUTPUT->action_link($url, $txt, $act, array('class' => 'icon delete')); + if ($i > 0) { + $actions[] = $OUTPUT->action_link( + new moodle_url('move.php', array('id' => $cm->id, 'case' => $case->id, 'direction' => 1)), + html_writer::empty_tag('img', array('title' => $strmoveup, 'src' => $OUTPUT->image_url('t/up'))), + null, + array('class' => 'icon up')); + } + if ($i < $tests_count) { + $actions[] = $OUTPUT->action_link( + new moodle_url('move.php', array('id' => $cm->id, 'case' => $case->id, 'direction' => 2)), + html_writer::empty_tag('img', array('title' => $strmovedown, 'src' => $OUTPUT->image_url('t/down'))), + null, + array('class' => 'icon down')); + } + $data[] = implode("\n",$actions); + + $table->data[] = $data; + $i++; + } + + echo html_writer::table($table); + } else { + echo html_writer::tag('p'.get_string('notestcase', 'programming')); + } + echo html_writer::tag('p', $OUTPUT->action_link(new moodle_url('add.php', array('id' => $cm->id)), get_string('addtestcase', 'programming'))); +} diff --git a/testcase/move.php b/testcase/move.php new file mode 100644 index 0000000..7367e58 --- /dev/null +++ b/testcase/move.php @@ -0,0 +1,29 @@ +get_record('course', array('id' => $cm->course))) { + error('Course is misconfigured'); + } + + if (! $programming = $DB->get_record('programming', array('id' => $cm->instance))) { + error('Course module is incorrect'); + } + } + + require_login($course->id, true, $cm); + $context = context_module::instance($cm->id); + require_capability('mod/programming:edittestcase', $context); + + programming_testcase_adjust_sequence($programming->id, $case, $direction); + redirect(new moodle_url('/mod/programming/testcase/list.php', array('id' => $cm->id)), get_string('testcasemoved', 'programming'), 0); diff --git a/text_diff_render_html.php b/text_diff_render_html.php new file mode 100644 index 0000000..ab0c2c6 --- /dev/null +++ b/text_diff_render_html.php @@ -0,0 +1,79 @@ +_x = $xbeg; + $this->_y = $ybeg; + } + + function _startBlock($header) { + return '