##// END OF EJS Templates
update CodeMirror2 to 2.32
Matthias BUSSONNIER -
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,453
1 <!doctype html>
2 <html>
3 <head>
4 <title>CodeMirror</title>
5 <link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Droid+Sans|Droid+Sans:bold"/>
6 <link rel="stylesheet" type="text/css" href="doc/docs.css"/>
7 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
8 <link rel="alternate" href="http://twitter.com/statuses/user_timeline/242283288.rss" type="application/rss+xml"/>
9 </head>
10 <body style="margin-top: 9em">
11
12 <div style="background: #eee; border-bottom: 2px solid #df0019; position: absolute; left: 0; top: 0; right: 0; padding: 18px 0;">
13 <div style="max-width: 64.3em; margin: 0 auto;">
14 <a href="http://www.pledgie.com/campaigns/17784">
15 <img style="width: 149px; height: 37px; vertical-align: bottom" alt='Click here to lend your support to: Fund CodeMirror development and make a donation at www.pledgie.com !' src='http://www.pledgie.com/campaigns/17784.png?skin_name=chrome' border=0/>
16 </a> &nbsp; <span style="font-size: 1.23em; font-weight: bold;">Please check out our development fundraiser.
17 </div>
18 </div>
19
20 <h1><span class="logo-braces">{ }</span> <a href="http://codemirror.net/">CodeMirror</a></h1>
21
22 <pre class="grey">
23 <img src="doc/baboon.png" class="logo" alt="logo"/>/* In-browser code editing
24 made bearable */
25 </pre>
26
27 <div class="clear"><div class="left blk">
28
29 <p style="margin-top: 0">CodeMirror is a JavaScript component that
30 provides a code editor in the browser. When a mode is available for
31 the language you are coding in, it will color your code, and
32 optionally help with indentation.</p>
33
34 <p>A <a href="doc/manual.html">rich programming API</a> and a CSS
35 theming system are available for customizing CodeMirror to fit your
36 application, and extending it with new functionality.</p>
37
38 <div class="clear"><div class="left1 blk">
39
40 <h2 style="margin-top: 0">Supported modes:</h2>
41
42 <ul>
43 <li><a href="mode/clike/index.html">C, C++, C#</a></li>
44 <li><a href="mode/clojure/index.html">Clojure</a></li>
45 <li><a href="mode/coffeescript/index.html">CoffeeScript</a></li>
46 <li><a href="mode/css/index.html">CSS</a></li>
47 <li><a href="mode/diff/index.html">diff</a></li>
48 <li><a href="mode/ecl/index.html">ECL</a></li>
49 <li><a href="mode/erlang/index.html">Erlang</a></li>
50 <li><a href="mode/go/index.html">Go</a></li>
51 <li><a href="mode/groovy/index.html">Groovy</a></li>
52 <li><a href="mode/haskell/index.html">Haskell</a></li>
53 <li><a href="mode/haxe/index.html">Haxe</a></li>
54 <li><a href="mode/htmlembedded/index.html">HTML embedded scripts</a></li>
55 <li><a href="mode/htmlmixed/index.html">HTML mixed-mode</a></li>
56 <li><a href="mode/clike/index.html">Java</a></li>
57 <li><a href="mode/javascript/index.html">JavaScript</a></li>
58 <li><a href="mode/jinja2/index.html">Jinja2</a></li>
59 <li><a href="mode/less/index.html">LESS</a></li>
60 <li><a href="mode/lua/index.html">Lua</a></li>
61 <li><a href="mode/markdown/index.html">Markdown</a> (<a href="mode/gfm/index.html">Github-flavour</a>)</li>
62 <li><a href="mode/mysql/index.html">MySQL</a></li>
63 <li><a href="mode/ntriples/index.html">NTriples</a></li>
64 <li><a href="mode/ocaml/index.html">OCaml</a></li>
65 <li><a href="mode/pascal/index.html">Pascal</a></li>
66 <li><a href="mode/perl/index.html">Perl</a></li>
67 <li><a href="mode/php/index.html">PHP</a></li>
68 <li><a href="mode/pig/index.html">Pig Latin</a></li>
69 <li><a href="mode/plsql/index.html">PL/SQL</a></li>
70 <li><a href="mode/properties/index.html">Properties files</a></li>
71 <li><a href="mode/python/index.html">Python</a></li>
72 <li><a href="mode/r/index.html">R</a></li>
73 <li>RPM <a href="mode/rpm/spec/index.html">spec</a> and <a href="mode/rpm/changes/index.html">changelog</a></li>
74 <li><a href="mode/rst/index.html">reStructuredText</a></li>
75 <li><a href="mode/ruby/index.html">Ruby</a></li>
76 <li><a href="mode/rust/index.html">Rust</a></li>
77 <li><a href="mode/clike/scala.html">Scala</a></li>
78 <li><a href="mode/scheme/index.html">Scheme</a></li>
79 <li><a href="mode/shell/index.html">Shell</a></li>
80 <li><a href="mode/smalltalk/index.html">Smalltalk</a></li>
81 <li><a href="mode/smarty/index.html">Smarty</a></li>
82 <li><a href="mode/sparql/index.html">SPARQL</a></li>
83 <li><a href="mode/stex/index.html">sTeX, LaTeX</a></li>
84 <li><a href="mode/tiddlywiki/index.html">Tiddlywiki</a></li>
85 <li><a href="mode/tiki/index.html">Tiki wiki</a></li>
86 <li><a href="mode/vb/index.html">VB.NET</a></li>
87 <li><a href="mode/vbscript/index.html">VBScript</a></li>
88 <li><a href="mode/velocity/index.html">Velocity</a></li>
89 <li><a href="mode/verilog/index.html">Verilog</a></li>
90 <li><a href="mode/xml/index.html">XML/HTML</a></li>
91 <li><a href="mode/xquery/index.html">XQuery</a></li>
92 <li><a href="mode/yaml/index.html">YAML</a></li>
93 </ul>
94
95 </div><div class="left2 blk">
96
97 <h2 style="margin-top: 0">Usage demos:</h2>
98
99 <ul>
100 <li><a href="demo/complete.html">Autocompletion</a> (<a href="demo/xmlcomplete.html">XML</a>)</li>
101 <li><a href="demo/search.html">Search/replace</a></li>
102 <li><a href="demo/folding.html">Code folding</a></li>
103 <li><a href="demo/mustache.html">Mode overlays</a></li>
104 <li><a href="demo/multiplex.html">Mode multiplexer</a></li>
105 <li><a href="demo/preview.html">HTML editor with preview</a></li>
106 <li><a href="demo/resize.html">Auto-resizing editor</a></li>
107 <li><a href="demo/marker.html">Setting breakpoints</a></li>
108 <li><a href="demo/activeline.html">Highlighting the current line</a></li>
109 <li><a href="demo/matchhighlighter.html">Highlighting selection matches</a></li>
110 <li><a href="demo/theme.html">Theming</a></li>
111 <li><a href="demo/runmode.html">Stand-alone highlighting</a></li>
112 <li><a href="demo/fullscreen.html">Full-screen editing</a></li>
113 <li><a href="demo/changemode.html">Mode auto-changing</a></li>
114 <li><a href="demo/visibletabs.html">Visible tabs</a></li>
115 <li><a href="demo/formatting.html">Autoformatting of code</a></li>
116 <li><a href="demo/emacs.html">Emacs keybindings</a></li>
117 <li><a href="demo/vim.html">Vim keybindings</a></li>
118 <li><a href="demo/closetag.html">Automatic xml tag closing</a></li>
119 <li><a href="demo/loadmode.html">Lazy mode loading</a></li>
120 </ul>
121
122 <h2>Real-world uses:</h2>
123
124 <ul>
125 <li><a href="http://jsbin.com">jsbin.com</a> (JS playground)</li>
126 <li><a href="http://www.chris-granger.com/2012/04/12/light-table---a-new-ide-concept/">Light Table</a> (experimental IDE)</li>
127 <li><a href="http://brackets.io">Adobe Brackets</a> (code editor)</li>
128 <li><a href="http://www.mergely.com/">Mergely</a> (interactive diffing)</li>
129 <li><a href="https://script.google.com/">Google Apps Script</a></li>
130 <li><a href="http://eloquentjavascript.net/chapter1.html">Eloquent JavaScript</a> (book)</li>
131 <li><a href="http://media.chikuyonok.ru/codemirror2/">Zen Coding</a> (fast XML editing)</li>
132 <li><a href="http://paperjs.org/">Paper.js</a> (graphics scripting)</li>
133 <li><a href="http://tour.golang.org">Go language tour</a></li>
134 <li><a href="http://enjalot.com/tributary/2636296/sinwaves.js">Tributary</a> (augmented editing)</li>
135 <li><a href="http://prose.io/">Prose.io</a> (github content editor)</li>
136 <li><a href="http://www.wescheme.org/">WeScheme</a> (learning tool)</li>
137 <li><a href="http://webglplayground.net/">WebGL playground</a></li>
138 <li><a href="http://ql.io/">ql.io</a> (http API query helper)</li>
139 <li><a href="http://elm-lang.org/Examples.elm">Elm language examples</a></li>
140 <li><a href="https://thefiletree.com">The File Tree</a> (collab editor)</li>
141 <li><a href="http://bluegriffon.org/">BlueGriffon</a> (HTML editor)</li>
142 <li><a href="http://www.jshint.com/">JSHint</a> (JS linter)</li>
143 <li><a href="http://kl1p.com/cmtest/1">kl1p</a> (paste service)</li>
144 <li><a href="http://sqlfiddle.com">SQLFiddle</a> (SQL playground)</li>
145 <li><a href="http://try.haxe.org">Try Haxe</a> (Haxe Playground) </li>
146 <li><a href="http://cssdeck.com/">CSSDeck</a> (CSS showcase)</li>
147 <li><a href="http://www.ckwnc.com/">CKWNC</a> (UML editor)</li>
148 <li><a href="http://www.sketchpatch.net/labs/livecodelabIntro.html">sketchPatch Livecodelab</a></li>
149 </ul>
150
151 </div></div>
152
153 <h2 id="code">Getting the code</h2>
154
155 <p>All of CodeMirror is released under a <a
156 href="LICENSE">MIT-style</a> license. To get it, you can download
157 the <a href="http://codemirror.net/codemirror.zip">latest
158 release</a> or the current <a
159 href="http://codemirror.net/codemirror2-latest.zip">development
160 snapshot</a> as zip files. To create a custom minified script file,
161 you can use the <a href="doc/compress.html">compression API</a>.</p>
162
163 <p>We use <a href="http://git-scm.com/">git</a> for version control.
164 The main repository can be fetched in this way:</p>
165
166 <pre class="code">git clone http://marijnhaverbeke.nl/git/codemirror2</pre>
167
168 <p>CodeMirror can also be found on GitHub at <a
169 href="http://github.com/marijnh/CodeMirror2">marijnh/CodeMirror2</a>.
170 If you plan to hack on the code and contribute patches, the best way
171 to do it is to create a GitHub fork, and send pull requests.</p>
172
173 <h2 id="documention">Documentation</h2>
174
175 <p>The <a href="doc/manual.html">manual</a> is your first stop for
176 learning how to use this library. It starts with a quick explanation
177 of how to use the editor, and then describes the API in detail.</p>
178
179 <p>For those who want to learn more about the code, there is
180 an <a href="doc/internals.html">overview of the internals</a> available.
181 The <a href="http://github.com/marijnh/CodeMirror2">source code</a>
182 itself is, for the most part, also well commented.</p>
183
184 <h2 id="support">Support and bug reports</h2>
185
186 <p>There is
187 a <a href="http://groups.google.com/group/codemirror">Google
188 group</a> (a sort of mailing list/newsgroup thing) for discussion
189 and news related to CodeMirror. When reporting a bug,
190 <a href="doc/reporting.html">read this first</a>. If you have
191 a <a href="http://github.com">github</a> account,
192 simply <a href="http://github.com/marijnh/CodeMirror2/issues">open
193 an issue there</a>. Otherwise, post something to
194 the <a href="http://groups.google.com/group/codemirror">group</a>,
195 or e-mail me directly: <a href="mailto:marijnh@gmail.com">Marijn
196 Haverbeke</a>.</p>
197
198 <h2 id="supported">Supported browsers</h2>
199
200 <p>The following browsers are able to run CodeMirror:</p>
201
202 <ul>
203 <li>Firefox 2 or higher</li>
204 <li>Chrome, any version</li>
205 <li>Safari 3 or higher</li>
206 <li>Opera 9 or higher (with some key-handling problems on OS X)</li>
207 <li>Internet Explorer 7 or higher in standards mode<br>
208 <em>(So not quirks mode. But quasi-standards mode with a
209 transitional doctype is also flaky. <code>&lt;!doctype
210 html></code> is recommended.)</em></li>
211 </ul>
212
213 <p>I am not actively testing against every new browser release, and
214 vendors have a habit of introducing bugs all the time, so I am
215 relying on the community to tell me when something breaks.
216 See <a href="#support">here</a> for information on how to contact
217 me.</p>
218
219 <h2 id="commercial">Commercial support</h2>
220
221 <p>CodeMirror is developed and maintained by me, Marijn Haverbeke,
222 in my own time. If your company is getting value out of CodeMirror,
223 please consider purchasing a support contract.</p>
224
225 <ul>
226 <li>You'll be funding further work on CodeMirror.</li>
227 <li>You ensure that you get a quick response when you have a
228 problem, even when I am otherwise busy.</li>
229 </ul>
230
231 <p>CodeMirror support contracts exist in two
232 forms—<strong>basic</strong> at €100 per month,
233 and <strong>premium</strong> at €500 per
234 month. <a href="mailto:marijnh@gmail.com">Contact me</a> for further
235 information.</p>
236
237 </div>
238
239 <div class="right blk">
240
241 <a href="http://codemirror.net/codemirror.zip" class="download">Download the latest release</a>
242
243 <h2>Support CodeMirror</h2>
244
245 <ul>
246 <li>Donate
247 (<span onclick="document.getElementById('paypal').submit();"
248 class="quasilink">Paypal</span>
249 or <span onclick="document.getElementById('bankinfo').style.display = 'block';"
250 class="quasilink">bank</span>)</li>
251 <li>Purchase <a href="#commercial">commercial support</a></li>
252 </ul>
253
254 <p id="bankinfo" style="display: none;">
255 Bank: <i>Rabobank</i><br/>
256 Country: <i>Netherlands</i><br/>
257 SWIFT: <i>RABONL2U</i><br/>
258 Account: <i>147850770</i><br/>
259 Name: <i>Marijn Haverbeke</i><br/>
260 IBAN: <i>NL26 RABO 0147 8507 70</i>
261 </p>
262
263 <h2>Reading material</h2>
264
265 <ul>
266 <li><a href="doc/manual.html">User manual</a></li>
267 <li><a href="http://github.com/marijnh/CodeMirror2">Browse the code</a></li>
268 </ul>
269
270 <h2 id=releases>Releases</h2>
271
272 <p class="rel">23-07-2012: <a href="http://codemirror.net/codemirror-2.32.zip">Version 2.32</a>:</p>
273
274 <p class="rel-note">Emergency fix for a bug where an editor with
275 line wrapping on IE will break when there is <em>no</em>
276 scrollbar.</p>
277
278 <p class="rel">20-07-2012: <a href="http://codemirror.net/codemirror-2.31.zip">Version 2.31</a>:</p>
279
280 <ul class="rel-note">
281 <li>New modes: <a href="mode/ocaml/index.html">OCaml</a>, <a href="mode/haxe/index.html">Haxe</a>, and <a href="mode/vb/index.html">VB.NET</a>.</li>
282 <li>Several fixes to the new scrolling model.</li>
283 <li>Add a <a href="doc/manual.html#setSize"><code>setSize</code></a> method for programmatic resizing.</li>
284 <li>Add <a href="doc/manual.html#getHistory"><code>getHistory</code></a> and <a href="doc/manual.html#setHistory"><code>setHistory</code></a> methods.</li>
285 <li>Allow custom line separator string in <a href="doc/manual.html#getValue"><code>getValue</code></a> and <a href="doc/manual.html#getRange"><code>getRange</code></a>.</li>
286 <li>Support double- and triple-click drag, double-clicking whitespace.</li>
287 <li>And more... <a href="https://github.com/marijnh/CodeMirror2/compare/v2.3...v2.31">(all patches)</a></li>
288 </ul>
289
290 <p class="rel">22-06-2012: <a href="http://codemirror.net/codemirror-2.3.zip">Version 2.3</a>:</p>
291
292 <ul class="rel-note">
293 <li><strong>New scrollbar implementation</strong>. Should flicker less. Changes DOM structure of the editor.</li>
294 <li>New theme: <a href="demo/theme.html?vibrant-ink">vibrant-ink</a>.</li>
295 <li>Many extensions to the VIM keymap (including text objects).</li>
296 <li>Add <a href="demo/multiplex.html">mode-multiplexing</a> utility script.</li>
297 <li>Fix bug where right-click paste works in read-only mode.</li>
298 <li>Add a <a href="doc/manual.html#getScrollInfo"><code>getScrollInfo</code></a> method.</li>
299 <li>Lots of other <a href="https://github.com/marijnh/CodeMirror2/compare/v2.25...v2.3">fixes</a>.</li>
300 </ul>
301
302 <p class="rel">23-05-2012: <a href="http://codemirror.net/codemirror-2.25.zip">Version 2.25</a>:</p>
303
304 <ul class="rel-note">
305 <li>New mode: <a href="mode/erlang/index.html">Erlang</a>.</li>
306 <li><strong>Remove xmlpure mode</strong> (use <a href="mode/xml/index.html">xml.js</a>).</li>
307 <li>Fix line-wrapping in Opera.</li>
308 <li>Fix X Windows middle-click paste in Chrome.</li>
309 <li>Fix bug that broke pasting of huge documents.</li>
310 <li>Fix backspace and tab key repeat in Opera.</li>
311 </ul>
312
313 <p class="rel">23-04-2012: <a href="http://codemirror.net/codemirror-2.24.zip">Version 2.24</a>:</p>
314
315 <ul class="rel-note">
316 <li><strong>Drop support for Internet Explorer 6</strong>.</li>
317 <li>New
318 modes: <a href="mode/shell/index.html">Shell</a>, <a href="mode/tiki/index.html">Tiki
319 wiki</a>, <a href="mode/pig/index.html">Pig Latin</a>.</li>
320 <li>New themes: <a href="demo/theme.html?ambiance">Ambiance</a>, <a href="demo/theme.html?blackboard">Blackboard</a>.</li>
321 <li>More control over drag/drop
322 with <a href="doc/manual.html#option_dragDrop"><code>dragDrop</code></a>
323 and <a href="doc/manual.html#option_onDragEvent"><code>onDragEvent</code></a>
324 options.</li>
325 <li>Make HTML mode a bit less pedantic.</li>
326 <li>Add <a href="doc/manual.html#compoundChange"><code>compoundChange</code></a> API method.</li>
327 <li>Several fixes in undo history and line hiding.</li>
328 <li>Remove (broken) support for <code>catchall</code> in key maps,
329 add <code>nofallthrough</code> boolean field instead.</li>
330 </ul>
331
332 <p class="rel">26-03-2012: <a href="http://codemirror.net/codemirror-2.23.zip">Version 2.23</a>:</p>
333
334 <ul class="rel-note">
335 <li>Change <strong>default binding for tab</strong> <a href="javascript:void(document.getElementById('tabbinding').style.display='')">[more]</a>
336 <div style="display: none" id=tabbinding>
337 Starting in 2.23, these bindings are default:
338 <ul><li>Tab: Insert tab character</li>
339 <li>Shift-tab: Reset line indentation to default</li>
340 <li>Ctrl/Cmd-[: Reduce line indentation (old tab behaviour)</li>
341 <li>Ctrl/Cmd-]: Increase line indentation (old shift-tab behaviour)</li>
342 </ul>
343 </div>
344 </li>
345 <li>New modes: <a href="mode/xquery/index.html">XQuery</a> and <a href="mode/vbscript/index.html">VBScript</a>.</li>
346 <li>Two new themes: <a href="mode/less/index.html">lesser-dark</a> and <a href="mode/xquery/index.html">xq-dark</a>.</li>
347 <li>Differentiate between background and text styles in <a href="doc/manual.html#setLineClass"><code>setLineClass</code></a>.</li>
348 <li>Fix drag-and-drop in IE9+.</li>
349 <li>Extend <a href="doc/manual.html#charCoords"><code>charCoords</code></a>
350 and <a href="doc/manual.html#cursorCoords"><code>cursorCoords</code></a> with a <code>mode</code> argument.</li>
351 <li>Add <a href="doc/manual.html#option_autofocus"><code>autofocus</code></a> option.</li>
352 <li>Add <a href="doc/manual.html#findMarksAt"><code>findMarksAt</code></a> method.</li>
353 </ul>
354
355 <p class="rel">27-02-2012: <a href="http://codemirror.net/codemirror-2.22.zip">Version 2.22</a>:</p>
356
357 <ul class="rel-note">
358 <li>Allow <a href="doc/manual.html#keymaps">key handlers</a> to pass up events, allow binding characters.</li>
359 <li>Add <a href="doc/manual.html#option_autoClearEmptyLines"><code>autoClearEmptyLines</code></a> option.</li>
360 <li>Properly use tab stops when rendering tabs.</li>
361 <li>Make PHP mode more robust.</li>
362 <li>Support indentation blocks in <a href="doc/manual.html#util_foldcode">code folder</a>.</li>
363 <li>Add a script for <a href="doc/manual.html#util_match-highlighter">highlighting instances of the selection</a>.</li>
364 <li>New <a href="mode/properties/index.html">.properties</a> mode.</li>
365 <li>Fix many bugs.</li>
366 </ul>
367
368 <p class="rel">27-01-2012: <a href="http://codemirror.net/codemirror-2.21.zip">Version 2.21</a>:</p>
369
370 <ul class="rel-note">
371 <li>Added <a href="mode/less/index.html">LESS</a>, <a href="mode/mysql/index.html">MySQL</a>,
372 <a href="mode/go/index.html">Go</a>, and <a href="mode/verilog/index.html">Verilog</a> modes.</li>
373 <li>Add <a href="doc/manual.html#option_smartIndent"><code>smartIndent</code></a>
374 option.</li>
375 <li>Support a cursor in <a href="doc/manual.html#option_readOnly"><code>readOnly</code></a>-mode.</li>
376 <li>Support assigning multiple styles to a token.</li>
377 <li>Use a new approach to drawing the selection.</li>
378 <li>Add <a href="doc/manual.html#scrollTo"><code>scrollTo</code></a> method.</li>
379 <li>Allow undo/redo events to span non-adjacent lines.</li>
380 <li>Lots and lots of bugfixes.</li>
381 </ul>
382
383 <p class="rel">20-12-2011: <a href="http://codemirror.net/codemirror-2.2.zip">Version 2.2</a>:</p>
384
385 <ul class="rel-note">
386 <li>Slightly incompatible API changes. Read <a href="doc/upgrade_v2.2.html">this</a>.</li>
387 <li>New approach
388 to <a href="doc/manual.html#option_extraKeys">binding</a> keys,
389 support for <a href="doc/manual.html#option_keyMap">custom
390 bindings</a>.</li>
391 <li>Support for overwrite (insert).</li>
392 <li><a href="doc/manual.html#option_tabSize">Custom-width</a>
393 and <a href="demo/visibletabs.html">stylable</a> tabs.</li>
394 <li>Moved more code into <a href="doc/manual.html#addons">add-on scripts</a>.</li>
395 <li>Support for sane vertical cursor movement in wrapped lines.</li>
396 <li>More reliable handling of
397 editing <a href="doc/manual.html#markText">marked text</a>.</li>
398 <li>Add minimal <a href="demo/emacs.html">emacs</a>
399 and <a href="demo/vim.html">vim</a> bindings.</li>
400 <li>Rename <code>coordsFromIndex</code>
401 to <a href="doc/manual.html#posFromIndex"><code>posFromIndex</code></a>,
402 add <a href="doc/manual.html#indexFromPos"><code>indexFromPos</code></a>
403 method.</li>
404 </ul>
405
406 <p class="rel">21-11-2011: <a href="http://codemirror.net/codemirror-2.18.zip">Version 2.18</a>:</p>
407 <p class="rel-note">Fixes <code>TextMarker.clear</code>, which is broken in 2.17.</p>
408
409 <p class="rel">21-11-2011: <a href="http://codemirror.net/codemirror-2.17.zip">Version 2.17</a>:</p>
410 <ul class="rel-note">
411 <li>Add support for <a href="doc/manual.html#option_lineWrapping">line
412 wrapping</a> and <a href="doc/manual.html#hideLine">code
413 folding</a>.</li>
414 <li>Add <a href="mode/gfm/index.html">Github-style Markdown</a> mode.</li>
415 <li>Add <a href="theme/monokai.css">Monokai</a>
416 and <a href="theme/rubyblue.css">Rubyblue</a> themes.</li>
417 <li>Add <a href="doc/manual.html#setBookmark"><code>setBookmark</code></a> method.</li>
418 <li>Move some of the demo code into reusable components
419 under <a href="lib/util/"><code>lib/util</code></a>.</li>
420 <li>Make screen-coord-finding code faster and more reliable.</li>
421 <li>Fix drag-and-drop in Firefox.</li>
422 <li>Improve support for IME.</li>
423 <li>Speed up content rendering.</li>
424 <li>Fix browser's built-in search in Webkit.</li>
425 <li>Make double- and triple-click work in IE.</li>
426 <li>Various fixes to modes.</li>
427 </ul>
428
429 <p class="rel">27-10-2011: <a href="http://codemirror.net/codemirror-2.16.zip">Version 2.16</a>:</p>
430 <ul class="rel-note">
431 <li>Add <a href="mode/perl/index.html">Perl</a>, <a href="mode/rust/index.html">Rust</a>, <a href="mode/tiddlywiki/index.html">TiddlyWiki</a>, and <a href="mode/groovy/index.html">Groovy</a> modes.</li>
432 <li>Dragging text inside the editor now moves, rather than copies.</li>
433 <li>Add a <a href="doc/manual.html#coordsFromIndex"><code>coordsFromIndex</code></a> method.</li>
434 <li><strong>API change</strong>: <code>setValue</code> now no longer clears history. Use <a href="doc/manual.html#clearHistory"><code>clearHistory</code></a> for that.</li>
435 <li><strong>API change</strong>: <a href="doc/manual.html#markText"><code>markText</code></a> now
436 returns an object with <code>clear</code> and <code>find</code>
437 methods. Marked text is now more robust when edited.</li>
438 <li>Fix editing code with tabs in Internet Explorer.</li>
439 </ul>
440
441 <p><a href="doc/oldrelease.html">Older releases...</a></p>
442
443 </div></div>
444
445 <div style="height: 2em">&nbsp;</div>
446
447 <form action="https://www.paypal.com/cgi-bin/webscr" method="post" id="paypal">
448 <input type="hidden" name="cmd" value="_s-xclick"/>
449 <input type="hidden" name="hosted_button_id" value="3FVHS5FGUY7CC"/>
450 </form>
451
452 </body>
453 </html>
@@ -0,0 +1,29
1 // TODO number prefixes
2 (function() {
3 // Really primitive kill-ring implementation.
4 var killRing = [];
5 function addToRing(str) {
6 killRing.push(str);
7 if (killRing.length > 50) killRing.shift();
8 }
9 function getFromRing() { return killRing[killRing.length - 1] || ""; }
10 function popFromRing() { if (killRing.length > 1) killRing.pop(); return getFromRing(); }
11
12 CodeMirror.keyMap.emacs = {
13 "Ctrl-X": function(cm) {cm.setOption("keyMap", "emacs-Ctrl-X");},
14 "Ctrl-W": function(cm) {addToRing(cm.getSelection()); cm.replaceSelection("");},
15 "Ctrl-Alt-W": function(cm) {addToRing(cm.getSelection()); cm.replaceSelection("");},
16 "Alt-W": function(cm) {addToRing(cm.getSelection());},
17 "Ctrl-Y": function(cm) {cm.replaceSelection(getFromRing());},
18 "Alt-Y": function(cm) {cm.replaceSelection(popFromRing());},
19 "Ctrl-/": "undo", "Shift-Ctrl--": "undo", "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd",
20 "Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": "clearSearch", "Shift-Alt-5": "replace",
21 "Ctrl-Z": "undo", "Cmd-Z": "undo", "Alt-/": "autocomplete",
22 fallthrough: ["basic", "emacsy"]
23 };
24
25 CodeMirror.keyMap["emacs-Ctrl-X"] = {
26 "Ctrl-S": "save", "Ctrl-W": "save", "S": "saveAll", "F": "open", "U": "undo", "K": "close",
27 auto: "emacs", nofallthrough: true
28 };
29 })();
This diff has been collapsed as it changes many lines, (785 lines changed) Show them Hide them
@@ -0,0 +1,785
1 // Supported keybindings:
2 //
3 // Cursor movement:
4 // h, j, k, l
5 // e, E, w, W, b, B
6 // Ctrl-f, Ctrl-b
7 // Ctrl-n, Ctrl-p
8 // $, ^, 0
9 // G
10 // ge, gE
11 // gg
12 // f<char>, F<char>, t<char>, T<char>
13 // Ctrl-o, Ctrl-i TODO (FIXME - Ctrl-O wont work in Chrome)
14 // /, ?, n, N TODO (does not work)
15 // #, * TODO
16 //
17 // Entering insert mode:
18 // i, I, a, A, o, O
19 // s
20 // ce, cb (without support for number of actions like c3e - TODO)
21 // cc
22 // S, C TODO
23 // cf<char>, cF<char>, ct<char>, cT<char>
24 //
25 // Deleting text:
26 // x, X
27 // J
28 // dd, D
29 // de, db (without support for number of actions like d3e - TODO)
30 // df<char>, dF<char>, dt<char>, dT<char>
31 //
32 // Yanking and pasting:
33 // yy, Y
34 // p, P
35 // p'<char> TODO - test
36 // y'<char> TODO - test
37 // m<char> TODO - test
38 //
39 // Changing text in place:
40 // ~
41 // r<char>
42 //
43 // Visual mode:
44 // v, V TODO
45 //
46 // Misc:
47 // . TODO
48 //
49
50 (function() {
51 var count = "";
52 var sdir = "f";
53 var buf = "";
54 var yank = 0;
55 var mark = [];
56 var reptTimes = 0;
57 function emptyBuffer() { buf = ""; }
58 function pushInBuffer(str) { buf += str; }
59 function pushCountDigit(digit) { return function(cm) {count += digit;}; }
60 function popCount() { var i = parseInt(count, 10); count = ""; return i || 1; }
61 function iterTimes(func) {
62 for (var i = 0, c = popCount(); i < c; ++i) func(i, i == c - 1);
63 }
64 function countTimes(func) {
65 if (typeof func == "string") func = CodeMirror.commands[func];
66 return function(cm) { iterTimes(function () { func(cm); }); };
67 }
68
69 function iterObj(o, f) {
70 for (var prop in o) if (o.hasOwnProperty(prop)) f(prop, o[prop]);
71 }
72 function iterList(l, f) {
73 for (var i in l) f(l[i]);
74 }
75 function toLetter(ch) {
76 // T -> t, Shift-T -> T, '*' -> *, "Space" -> " "
77 if (ch.slice(0, 6) == "Shift-") {
78 return ch.slice(0, 1);
79 } else {
80 if (ch == "Space") return " ";
81 if (ch.length == 3 && ch[0] == "'" && ch[2] == "'") return ch[1];
82 return ch.toLowerCase();
83 }
84 }
85 var SPECIAL_SYMBOLS = "~`!@#$%^&*()_-+=[{}]\\|/?.,<>:;\"\'1234567890";
86 function toCombo(ch) {
87 // t -> T, T -> Shift-T, * -> '*', " " -> "Space"
88 if (ch == " ") return "Space";
89 var specialIdx = SPECIAL_SYMBOLS.indexOf(ch);
90 if (specialIdx != -1) return "'" + ch + "'";
91 if (ch.toLowerCase() == ch) return ch.toUpperCase();
92 return "Shift-" + ch.toUpperCase();
93 }
94
95 var word = [/\w/, /[^\w\s]/], bigWord = [/\S/];
96 function findWord(line, pos, dir, regexps) {
97 var stop = 0, next = -1;
98 if (dir > 0) { stop = line.length; next = 0; }
99 var start = stop, end = stop;
100 // Find bounds of next one.
101 outer: for (; pos != stop; pos += dir) {
102 for (var i = 0; i < regexps.length; ++i) {
103 if (regexps[i].test(line.charAt(pos + next))) {
104 start = pos;
105 for (; pos != stop; pos += dir) {
106 if (!regexps[i].test(line.charAt(pos + next))) break;
107 }
108 end = pos;
109 break outer;
110 }
111 }
112 }
113 return {from: Math.min(start, end), to: Math.max(start, end)};
114 }
115 function moveToWord(cm, regexps, dir, times, where) {
116 var cur = cm.getCursor();
117
118 for (var i = 0; i < times; i++) {
119 var line = cm.getLine(cur.line), startCh = cur.ch, word;
120 while (true) {
121 // If we're at start/end of line, start on prev/next respectivly
122 if (cur.ch == line.length && dir > 0) {
123 cur.line++;
124 cur.ch = 0;
125 line = cm.getLine(cur.line);
126 } else if (cur.ch == 0 && dir < 0) {
127 cur.line--;
128 cur.ch = line.length;
129 line = cm.getLine(cur.line);
130 }
131 if (!line) break;
132
133 // On to the actual searching
134 word = findWord(line, cur.ch, dir, regexps);
135 cur.ch = word[where == "end" ? "to" : "from"];
136 if (startCh == cur.ch && word.from != word.to) cur.ch = word[dir < 0 ? "from" : "to"];
137 else break;
138 }
139 }
140 return cur;
141 }
142 function joinLineNext(cm) {
143 var cur = cm.getCursor(), ch = cur.ch, line = cm.getLine(cur.line);
144 CodeMirror.commands.goLineEnd(cm);
145 if (cur.line != cm.lineCount()) {
146 CodeMirror.commands.goLineEnd(cm);
147 cm.replaceSelection(" ", "end");
148 CodeMirror.commands.delCharRight(cm);
149 }
150 }
151 function delTillMark(cm, cHar) {
152 var i = mark[cHar];
153 if (i === undefined) {
154 // console.log("Mark not set"); // TODO - show in status bar
155 return;
156 }
157 var l = cm.getCursor().line, start = i > l ? l : i, end = i > l ? i : l;
158 cm.setCursor(start);
159 for (var c = start; c <= end; c++) {
160 pushInBuffer("\n"+cm.getLine(start));
161 cm.removeLine(start);
162 }
163 }
164 function yankTillMark(cm, cHar) {
165 var i = mark[cHar];
166 if (i === undefined) {
167 // console.log("Mark not set"); // TODO - show in status bar
168 return;
169 }
170 var l = cm.getCursor().line, start = i > l ? l : i, end = i > l ? i : l;
171 for (var c = start; c <= end; c++) {
172 pushInBuffer("\n"+cm.getLine(c));
173 }
174 cm.setCursor(start);
175 }
176 function goLineStartText(cm) {
177 // Go to the start of the line where the text begins, or the end for whitespace-only lines
178 var cur = cm.getCursor(), firstNonWS = cm.getLine(cur.line).search(/\S/);
179 cm.setCursor(cur.line, firstNonWS == -1 ? line.length : firstNonWS, true);
180 }
181
182 function charIdxInLine(cm, cHar, motion_options) {
183 // Search for cHar in line.
184 // motion_options: {forward, inclusive}
185 // If inclusive = true, include it too.
186 // If forward = true, search forward, else search backwards.
187 // If char is not found on this line, do nothing
188 var cur = cm.getCursor(), line = cm.getLine(cur.line), idx;
189 var ch = toLetter(cHar), mo = motion_options;
190 if (mo.forward) {
191 idx = line.indexOf(ch, cur.ch + 1);
192 if (idx != -1 && mo.inclusive) idx += 1;
193 } else {
194 idx = line.lastIndexOf(ch, cur.ch);
195 if (idx != -1 && !mo.inclusive) idx += 1;
196 }
197 return idx;
198 }
199
200 function moveTillChar(cm, cHar, motion_options) {
201 // Move to cHar in line, as found by charIdxInLine.
202 var idx = charIdxInLine(cm, cHar, motion_options), cur = cm.getCursor();
203 if (idx != -1) cm.setCursor({line: cur.line, ch: idx});
204 }
205
206 function delTillChar(cm, cHar, motion_options) {
207 // delete text in this line, untill cHar is met,
208 // as found by charIdxInLine.
209 // If char is not found on this line, do nothing
210 var idx = charIdxInLine(cm, cHar, motion_options);
211 var cur = cm.getCursor();
212 if (idx !== -1) {
213 if (motion_options.forward) {
214 cm.replaceRange("", {line: cur.line, ch: cur.ch}, {line: cur.line, ch: idx});
215 } else {
216 cm.replaceRange("", {line: cur.line, ch: idx}, {line: cur.line, ch: cur.ch});
217 }
218 }
219 }
220
221 function enterInsertMode(cm) {
222 // enter insert mode: switch mode and cursor
223 popCount();
224 cm.setOption("keyMap", "vim-insert");
225 }
226
227 function dialog(cm, text, shortText, f) {
228 if (cm.openDialog) cm.openDialog(text, f);
229 else f(prompt(shortText, ""));
230 }
231 function showAlert(cm, text) {
232 if (cm.openDialog) cm.openDialog(CodeMirror.htmlEscape(text) + " <button type=button>OK</button>");
233 else alert(text);
234 }
235
236 // main keymap
237 var map = CodeMirror.keyMap.vim = {
238 // Pipe (|); TODO: should be *screen* chars, so need a util function to turn tabs into spaces?
239 "'|'": function(cm) {
240 cm.setCursor(cm.getCursor().line, popCount() - 1, true);
241 },
242 "A": function(cm) {
243 cm.setCursor(cm.getCursor().line, cm.getCursor().ch+1, true);
244 enterInsertMode(cm);
245 },
246 "Shift-A": function(cm) { CodeMirror.commands.goLineEnd(cm); enterInsertMode(cm);},
247 "I": function(cm) { enterInsertMode(cm);},
248 "Shift-I": function(cm) { goLineStartText(cm); enterInsertMode(cm);},
249 "O": function(cm) {
250 CodeMirror.commands.goLineEnd(cm);
251 CodeMirror.commands.newlineAndIndent(cm);
252 enterInsertMode(cm);
253 },
254 "Shift-O": function(cm) {
255 CodeMirror.commands.goLineStart(cm);
256 cm.replaceSelection("\n", "start");
257 cm.indentLine(cm.getCursor().line);
258 enterInsertMode(cm);
259 },
260 "G": function(cm) { cm.setOption("keyMap", "vim-prefix-g");},
261 "Shift-D": function(cm) {
262 // commented out verions works, but I left original, cause maybe
263 // I don't know vim enouth to see what it does
264 /* var cur = cm.getCursor();
265 var f = {line: cur.line, ch: cur.ch}, t = {line: cur.line};
266 pushInBuffer(cm.getRange(f, t));
267 */
268 emptyBuffer();
269 mark["Shift-D"] = cm.getCursor(false).line;
270 cm.setCursor(cm.getCursor(true).line);
271 delTillMark(cm,"Shift-D"); mark = [];
272 },
273
274 "S": function (cm) {
275 countTimes(function (_cm) {
276 CodeMirror.commands.delCharRight(_cm);
277 })(cm);
278 enterInsertMode(cm);
279 },
280 "M": function(cm) {cm.setOption("keyMap", "vim-prefix-m"); mark = [];},
281 "Y": function(cm) {cm.setOption("keyMap", "vim-prefix-y"); emptyBuffer(); yank = 0;},
282 "Shift-Y": function(cm) {
283 emptyBuffer();
284 mark["Shift-D"] = cm.getCursor(false).line;
285 cm.setCursor(cm.getCursor(true).line);
286 yankTillMark(cm,"Shift-D"); mark = [];
287 },
288 "/": function(cm) {var f = CodeMirror.commands.find; f && f(cm); sdir = "f";},
289 "'?'": function(cm) {
290 var f = CodeMirror.commands.find;
291 if (f) { f(cm); CodeMirror.commands.findPrev(cm); sdir = "r"; }
292 },
293 "N": function(cm) {
294 var fn = CodeMirror.commands.findNext;
295 if (fn) sdir != "r" ? fn(cm) : CodeMirror.commands.findPrev(cm);
296 },
297 "Shift-N": function(cm) {
298 var fn = CodeMirror.commands.findNext;
299 if (fn) sdir != "r" ? CodeMirror.commands.findPrev(cm) : fn.findNext(cm);
300 },
301 "Shift-G": function(cm) {
302 count == "" ? cm.setCursor(cm.lineCount()) : cm.setCursor(parseInt(count, 10)-1);
303 popCount();
304 CodeMirror.commands.goLineStart(cm);
305 },
306 "':'": function(cm) {
307 var exModeDialog = ': <input type="text" style="width: 90%"/>';
308 dialog(cm, exModeDialog, ':', function(command) {
309 if (command.match(/^\d+$/)) {
310 cm.setCursor(command - 1, cm.getCursor().ch);
311 } else {
312 showAlert(cm, "Bad command: " + command);
313 }
314 });
315 },
316 nofallthrough: true, style: "fat-cursor"
317 };
318
319 // standard mode switching
320 iterList(["d", "t", "T", "f", "F", "c", "r"],
321 function (ch) {
322 CodeMirror.keyMap.vim[toCombo(ch)] = function (cm) {
323 cm.setOption("keyMap", "vim-prefix-" + ch);
324 emptyBuffer();
325 };
326 });
327
328 function addCountBindings(keyMap) {
329 // Add bindings for number keys
330 keyMap["0"] = function(cm) {
331 count.length > 0 ? pushCountDigit("0")(cm) : CodeMirror.commands.goLineStart(cm);
332 };
333 for (var i = 1; i < 10; ++i) keyMap[i] = pushCountDigit(i);
334 }
335 addCountBindings(CodeMirror.keyMap.vim);
336
337 // main num keymap
338 // Add bindings that are influenced by number keys
339 iterObj({
340 "Left": "goColumnLeft", "Right": "goColumnRight",
341 "Down": "goLineDown", "Up": "goLineUp", "Backspace": "goCharLeft",
342 "Space": "goCharRight",
343 "X": function(cm) {CodeMirror.commands.delCharRight(cm);},
344 "P": function(cm) {
345 var cur = cm.getCursor().line;
346 if (buf!= "") {
347 if (buf[0] == "\n") CodeMirror.commands.goLineEnd(cm);
348 cm.replaceRange(buf, cm.getCursor());
349 }
350 },
351 "Shift-X": function(cm) {CodeMirror.commands.delCharLeft(cm);},
352 "Shift-J": function(cm) {joinLineNext(cm);},
353 "Shift-P": function(cm) {
354 var cur = cm.getCursor().line;
355 if (buf!= "") {
356 CodeMirror.commands.goLineUp(cm);
357 CodeMirror.commands.goLineEnd(cm);
358 cm.replaceSelection(buf, "end");
359 }
360 cm.setCursor(cur+1);
361 },
362 "'~'": function(cm) {
363 var cur = cm.getCursor(), cHar = cm.getRange({line: cur.line, ch: cur.ch}, {line: cur.line, ch: cur.ch+1});
364 cHar = cHar != cHar.toLowerCase() ? cHar.toLowerCase() : cHar.toUpperCase();
365 cm.replaceRange(cHar, {line: cur.line, ch: cur.ch}, {line: cur.line, ch: cur.ch+1});
366 cm.setCursor(cur.line, cur.ch+1);
367 },
368 "Ctrl-B": function(cm) {CodeMirror.commands.goPageUp(cm);},
369 "Ctrl-F": function(cm) {CodeMirror.commands.goPageDown(cm);},
370 "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
371 "U": "undo", "Ctrl-R": "redo"
372 }, function(key, cmd) { map[key] = countTimes(cmd); });
373
374 // empty key maps
375 iterList([
376 "vim-prefix-d'",
377 "vim-prefix-y'",
378 "vim-prefix-df",
379 "vim-prefix-dF",
380 "vim-prefix-dt",
381 "vim-prefix-dT",
382 "vim-prefix-c",
383 "vim-prefix-cf",
384 "vim-prefix-cF",
385 "vim-prefix-ct",
386 "vim-prefix-cT",
387 "vim-prefix-",
388 "vim-prefix-f",
389 "vim-prefix-F",
390 "vim-prefix-t",
391 "vim-prefix-T",
392 "vim-prefix-r",
393 "vim-prefix-m"
394 ],
395 function (prefix) {
396 CodeMirror.keyMap[prefix] = {
397 auto: "vim",
398 nofallthrough: true,
399 style: "fat-cursor"
400 };
401 });
402
403 CodeMirror.keyMap["vim-prefix-g"] = {
404 "E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, word, -1, 1, "start"));}),
405 "Shift-E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, bigWord, -1, 1, "start"));}),
406 "G": function (cm) { cm.setCursor({line: 0, ch: cm.getCursor().ch});},
407 auto: "vim", nofallthrough: true, style: "fat-cursor"
408 };
409
410 CodeMirror.keyMap["vim-prefix-d"] = {
411 "D": countTimes(function(cm) {
412 pushInBuffer("\n"+cm.getLine(cm.getCursor().line));
413 cm.removeLine(cm.getCursor().line);
414 cm.setOption("keyMap", "vim");
415 }),
416 "'": function(cm) {
417 cm.setOption("keyMap", "vim-prefix-d'");
418 emptyBuffer();
419 },
420 "B": function(cm) {
421 var cur = cm.getCursor();
422 var line = cm.getLine(cur.line);
423 var index = line.lastIndexOf(" ", cur.ch);
424
425 pushInBuffer(line.substring(index, cur.ch));
426 cm.replaceRange("", {line: cur.line, ch: index}, cur);
427 cm.setOption("keyMap", "vim");
428 },
429 nofallthrough: true, style: "fat-cursor"
430 };
431 // FIXME - does not work for bindings like "d3e"
432 addCountBindings(CodeMirror.keyMap["vim-prefix-d"]);
433
434 CodeMirror.keyMap["vim-prefix-c"] = {
435 "B": function (cm) {
436 countTimes("delWordLeft")(cm);
437 enterInsertMode(cm);
438 },
439 "C": function (cm) {
440 iterTimes(function (i, last) {
441 CodeMirror.commands.deleteLine(cm);
442 if (i) {
443 CodeMirror.commands.delCharRight(cm);
444 if (last) CodeMirror.commands.deleteLine(cm);
445 }
446 });
447 enterInsertMode(cm);
448 },
449 nofallthrough: true, style: "fat-cursor"
450 };
451
452 iterList(["vim-prefix-d", "vim-prefix-c", "vim-prefix-"], function (prefix) {
453 iterList(["f", "F", "T", "t"],
454 function (ch) {
455 CodeMirror.keyMap[prefix][toCombo(ch)] = function (cm) {
456 cm.setOption("keyMap", prefix + ch);
457 emptyBuffer();
458 };
459 });
460 });
461
462 var MOTION_OPTIONS = {
463 "t": {inclusive: false, forward: true},
464 "f": {inclusive: true, forward: true},
465 "T": {inclusive: false, forward: false},
466 "F": {inclusive: true, forward: false}
467 };
468
469 function setupPrefixBindingForKey(m) {
470 CodeMirror.keyMap["vim-prefix-m"][m] = function(cm) {
471 mark[m] = cm.getCursor().line;
472 };
473 CodeMirror.keyMap["vim-prefix-d'"][m] = function(cm) {
474 delTillMark(cm,m);
475 };
476 CodeMirror.keyMap["vim-prefix-y'"][m] = function(cm) {
477 yankTillMark(cm,m);
478 };
479 CodeMirror.keyMap["vim-prefix-r"][m] = function (cm) {
480 var cur = cm.getCursor();
481 cm.replaceRange(toLetter(m),
482 {line: cur.line, ch: cur.ch},
483 {line: cur.line, ch: cur.ch + 1});
484 CodeMirror.commands.goColumnLeft(cm);
485 };
486 // all commands, related to motions till char in line
487 iterObj(MOTION_OPTIONS, function (ch, options) {
488 CodeMirror.keyMap["vim-prefix-" + ch][m] = function(cm) {
489 moveTillChar(cm, m, options);
490 };
491 CodeMirror.keyMap["vim-prefix-d" + ch][m] = function(cm) {
492 delTillChar(cm, m, options);
493 };
494 CodeMirror.keyMap["vim-prefix-c" + ch][m] = function(cm) {
495 delTillChar(cm, m, options);
496 enterInsertMode(cm);
497 };
498 });
499 }
500 for (var i = 65; i < 65 + 26; i++) { // uppercase alphabet char codes
501 var ch = String.fromCharCode(i);
502 setupPrefixBindingForKey(toCombo(ch));
503 setupPrefixBindingForKey(toCombo(ch.toLowerCase()));
504 }
505 iterList(SPECIAL_SYMBOLS, function (ch) {
506 setupPrefixBindingForKey(toCombo(ch));
507 });
508 setupPrefixBindingForKey("Space");
509
510 CodeMirror.keyMap["vim-prefix-y"] = {
511 "Y": countTimes(function(cm) { pushInBuffer("\n"+cm.getLine(cm.getCursor().line+yank)); yank++; }),
512 "'": function(cm) {cm.setOption("keyMap", "vim-prefix-y'"); emptyBuffer();},
513 nofallthrough: true, style: "fat-cursor"
514 };
515
516 CodeMirror.keyMap["vim-insert"] = {
517 // TODO: override navigation keys so that Esc will cancel automatic indentation from o, O, i_<CR>
518 "Esc": function(cm) {
519 cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1, true);
520 cm.setOption("keyMap", "vim");
521 },
522 "Ctrl-N": "autocomplete",
523 "Ctrl-P": "autocomplete",
524 fallthrough: ["default"]
525 };
526
527 function findMatchedSymbol(cm, cur, symb) {
528 var line = cur.line;
529 var symb = symb ? symb : cm.getLine(line)[cur.ch];
530
531 // Are we at the opening or closing char
532 var forwards = ['(', '[', '{'].indexOf(symb) != -1;
533
534 var reverseSymb = (function(sym) {
535 switch (sym) {
536 case '(' : return ')';
537 case '[' : return ']';
538 case '{' : return '}';
539 case ')' : return '(';
540 case ']' : return '[';
541 case '}' : return '{';
542 default : return null;
543 }
544 })(symb);
545
546 // Couldn't find a matching symbol, abort
547 if (reverseSymb == null) return cur;
548
549 // Tracking our imbalance in open/closing symbols. An opening symbol wii be
550 // the first thing we pick up if moving forward, this isn't true moving backwards
551 var disBal = forwards ? 0 : 1;
552
553 while (true) {
554 if (line == cur.line) {
555 // First pass, do some special stuff
556 var currLine = forwards ? cm.getLine(line).substr(cur.ch).split('') : cm.getLine(line).substr(0,cur.ch).split('').reverse();
557 } else {
558 var currLine = forwards ? cm.getLine(line).split('') : cm.getLine(line).split('').reverse();
559 }
560
561 for (var index = 0; index < currLine.length; index++) {
562 if (currLine[index] == symb) disBal++;
563 else if (currLine[index] == reverseSymb) disBal--;
564
565 if (disBal == 0) {
566 if (forwards && cur.line == line) return {line: line, ch: index + cur.ch};
567 else if (forwards) return {line: line, ch: index};
568 else return {line: line, ch: currLine.length - index - 1 };
569 }
570 }
571
572 if (forwards) line++;
573 else line--;
574 }
575 }
576
577 function selectCompanionObject(cm, revSymb, inclusive) {
578 var cur = cm.getCursor();
579
580 var end = findMatchedSymbol(cm, cur, revSymb);
581 var start = findMatchedSymbol(cm, end);
582 start.ch += inclusive ? 1 : 0;
583 end.ch += inclusive ? 0 : 1;
584
585 return {start: start, end: end};
586 }
587
588 // These are our motion commands to be used for navigation and selection with
589 // certian other commands. All should return a cursor object.
590 var motionList = ['B', 'E', 'J', 'K', 'H', 'L', 'W', 'Shift-W', "'^'", "'$'", "'%'", 'Esc'];
591
592 motions = {
593 'B': function(cm, times) { return moveToWord(cm, word, -1, times); },
594 'Shift-B': function(cm, times) { return moveToWord(cm, bigWord, -1, times); },
595 'E': function(cm, times) { return moveToWord(cm, word, 1, times, 'end'); },
596 'Shift-E': function(cm, times) { return moveToWord(cm, bigWord, 1, times, 'end'); },
597 'J': function(cm, times) {
598 var cur = cm.getCursor();
599 return {line: cur.line+times, ch : cur.ch};
600 },
601
602 'K': function(cm, times) {
603 var cur = cm.getCursor();
604 return {line: cur.line-times, ch: cur.ch};
605 },
606
607 'H': function(cm, times) {
608 var cur = cm.getCursor();
609 return {line: cur.line, ch: cur.ch-times};
610 },
611
612 'L': function(cm, times) {
613 var cur = cm.getCursor();
614 return {line: cur.line, ch: cur.ch+times};
615 },
616 'W': function(cm, times) { return moveToWord(cm, word, 1, times); },
617 'Shift-W': function(cm, times) { return moveToWord(cm, bigWord, 1, times); },
618 "'^'": function(cm, times) {
619 var cur = cm.getCursor();
620 var line = cm.getLine(cur.line).split('');
621
622 // Empty line :o
623 if (line.length == 0) return cur;
624
625 for (var index = 0; index < line.length; index++) {
626 if (line[index].match(/[^\s]/)) return {line: cur.line, ch: index};
627 }
628 },
629 "'$'": function(cm) {
630 var cur = cm.getCursor();
631 var line = cm.getLine(cur.line);
632 return {line: cur.line, ch: line.length};
633 },
634 "'%'": function(cm) { return findMatchedSymbol(cm, cm.getCursor()); },
635 "Esc" : function(cm) {
636 cm.setOption('vim');
637 reptTimes = 0;
638
639 return cm.getCursor();
640 }
641 };
642
643 // Map our movement actions each operator and non-operational movement
644 motionList.forEach(function(key, index, array) {
645 CodeMirror.keyMap['vim-prefix-d'][key] = function(cm) {
646 // Get our selected range
647 var start = cm.getCursor();
648 var end = motions[key](cm, reptTimes ? reptTimes : 1);
649
650 // Set swap var if range is of negative length
651 if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true;
652
653 // Take action, switching start and end if swap var is set
654 pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end));
655 cm.replaceRange("", swap ? end : start, swap ? start : end);
656
657 // And clean up
658 reptTimes = 0;
659 cm.setOption("keyMap", "vim");
660 };
661
662 CodeMirror.keyMap['vim-prefix-c'][key] = function(cm) {
663 var start = cm.getCursor();
664 var end = motions[key](cm, reptTimes ? reptTimes : 1);
665
666 if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true;
667 pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end));
668 cm.replaceRange("", swap ? end : start, swap ? start : end);
669
670 reptTimes = 0;
671 cm.setOption('keyMap', 'vim-insert');
672 };
673
674 CodeMirror.keyMap['vim-prefix-y'][key] = function(cm) {
675 var start = cm.getCursor();
676 var end = motions[key](cm, reptTimes ? reptTimes : 1);
677
678 if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true;
679 pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end));
680
681 reptTimes = 0;
682 cm.setOption("keyMap", "vim");
683 };
684
685 CodeMirror.keyMap['vim'][key] = function(cm) {
686 var cur = motions[key](cm, reptTimes ? reptTimes : 1);
687 cm.setCursor(cur.line, cur.ch);
688
689 reptTimes = 0;
690 };
691 });
692
693 var nums = [1,2,3,4,5,6,7,8,9];
694 nums.forEach(function(key, index, array) {
695 CodeMirror.keyMap['vim'][key] = function (cm) {
696 reptTimes = (reptTimes * 10) + key;
697 };
698 CodeMirror.keyMap['vim-prefix-d'][key] = function (cm) {
699 reptTimes = (reptTimes * 10) + key;
700 };
701 CodeMirror.keyMap['vim-prefix-y'][key] = function (cm) {
702 reptTimes = (reptTimes * 10) + key;
703 };
704 CodeMirror.keyMap['vim-prefix-c'][key] = function (cm) {
705 reptTimes = (reptTimes * 10) + key;
706 };
707 });
708
709 // Create our keymaps for each operator and make xa and xi where x is an operator
710 // change to the corrosponding keymap
711 var operators = ['d', 'y', 'c'];
712 operators.forEach(function(key, index, array) {
713 CodeMirror.keyMap['vim-prefix-'+key+'a'] = {
714 auto: 'vim', nofallthrough: true, style: "fat-cursor"
715 };
716 CodeMirror.keyMap['vim-prefix-'+key+'i'] = {
717 auto: 'vim', nofallthrough: true, style: "fat-cursor"
718 };
719
720 CodeMirror.keyMap['vim-prefix-'+key]['A'] = function(cm) {
721 reptTimes = 0;
722 cm.setOption('keyMap', 'vim-prefix-' + key + 'a');
723 };
724
725 CodeMirror.keyMap['vim-prefix-'+key]['I'] = function(cm) {
726 reptTimes = 0;
727 cm.setOption('keyMap', 'vim-prefix-' + key + 'i');
728 };
729 });
730
731 function regexLastIndexOf(string, pattern, startIndex) {
732 for (var i = startIndex == null ? string.length : startIndex; i >= 0; --i)
733 if (pattern.test(string.charAt(i))) return i;
734 return -1;
735 }
736
737 // Create our text object functions. They work similar to motions but they
738 // return a start cursor as well
739 var textObjectList = ['W', 'Shift-[', 'Shift-9', '['];
740 var textObjects = {
741 'W': function(cm, inclusive) {
742 var cur = cm.getCursor();
743 var line = cm.getLine(cur.line);
744
745 var line_to_char = new String(line.substring(0, cur.ch));
746 var start = regexLastIndexOf(line_to_char, /[^a-zA-Z0-9]/) + 1;
747 var end = motions["E"](cm, 1) ;
748
749 end.ch += inclusive ? 1 : 0 ;
750 return {start: {line: cur.line, ch: start}, end: end };
751 },
752 'Shift-[': function(cm, inclusive) { return selectCompanionObject(cm, '}', inclusive); },
753 'Shift-9': function(cm, inclusive) { return selectCompanionObject(cm, ')', inclusive); },
754 '[': function(cm, inclusive) { return selectCompanionObject(cm, ']', inclusive); }
755 };
756
757 // One function to handle all operation upon text objects. Kinda funky but it works
758 // better than rewriting this code six times
759 function textObjectManipulation(cm, object, remove, insert, inclusive) {
760 // Object is the text object, delete object if remove is true, enter insert
761 // mode if insert is true, inclusive is the difference between a and i
762 var tmp = textObjects[object](cm, inclusive);
763 var start = tmp.start;
764 var end = tmp.end;
765
766 if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true ;
767
768 pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end));
769 if (remove) cm.replaceRange("", swap ? end : start, swap ? start : end);
770 if (insert) cm.setOption('keyMap', 'vim-insert');
771 }
772
773 // And finally build the keymaps up from the text objects
774 for (var i = 0; i < textObjectList.length; ++i) {
775 var object = textObjectList[i];
776 (function(object) {
777 CodeMirror.keyMap['vim-prefix-di'][object] = function(cm) { textObjectManipulation(cm, object, true, false, false); };
778 CodeMirror.keyMap['vim-prefix-da'][object] = function(cm) { textObjectManipulation(cm, object, true, false, true); };
779 CodeMirror.keyMap['vim-prefix-yi'][object] = function(cm) { textObjectManipulation(cm, object, false, false, false); };
780 CodeMirror.keyMap['vim-prefix-ya'][object] = function(cm) { textObjectManipulation(cm, object, false, false, true); };
781 CodeMirror.keyMap['vim-prefix-ci'][object] = function(cm) { textObjectManipulation(cm, object, true, true, false); };
782 CodeMirror.keyMap['vim-prefix-ca'][object] = function(cm) { textObjectManipulation(cm, object, true, true, true); };
783 })(object)
784 }
785 })();
@@ -0,0 +1,164
1 /**
2 * Tag-closer extension for CodeMirror.
3 *
4 * This extension adds a "closeTag" utility function that can be used with key bindings to
5 * insert a matching end tag after the ">" character of a start tag has been typed. It can
6 * also complete "</" if a matching start tag is found. It will correctly ignore signal
7 * characters for empty tags, comments, CDATA, etc.
8 *
9 * The function depends on internal parser state to identify tags. It is compatible with the
10 * following CodeMirror modes and will ignore all others:
11 * - htmlmixed
12 * - xml
13 *
14 * See demos/closetag.html for a usage example.
15 *
16 * @author Nathan Williams <nathan@nlwillia.net>
17 * Contributed under the same license terms as CodeMirror.
18 */
19 (function() {
20 /** Option that allows tag closing behavior to be toggled. Default is true. */
21 CodeMirror.defaults['closeTagEnabled'] = true;
22
23 /** Array of tag names to add indentation after the start tag for. Default is the list of block-level html tags. */
24 CodeMirror.defaults['closeTagIndent'] = ['applet', 'blockquote', 'body', 'button', 'div', 'dl', 'fieldset', 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html', 'iframe', 'layer', 'legend', 'object', 'ol', 'p', 'select', 'table', 'ul'];
25
26 /** Array of tag names where an end tag is forbidden. */
27 CodeMirror.defaults['closeTagVoid'] = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
28
29 /**
30 * Call during key processing to close tags. Handles the key event if the tag is closed, otherwise throws CodeMirror.Pass.
31 * - cm: The editor instance.
32 * - ch: The character being processed.
33 * - indent: Optional. An array of tag names to indent when closing. Omit or pass true to use the default indentation tag list defined in the 'closeTagIndent' option.
34 * Pass false to disable indentation. Pass an array to override the default list of tag names.
35 * - vd: Optional. An array of tag names that should not be closed. Omit to use the default void (end tag forbidden) tag list defined in the 'closeTagVoid' option. Ignored in xml mode.
36 */
37 CodeMirror.defineExtension("closeTag", function(cm, ch, indent, vd) {
38 if (!cm.getOption('closeTagEnabled')) {
39 throw CodeMirror.Pass;
40 }
41
42 var mode = cm.getOption('mode');
43
44 if (mode == 'text/html' || mode == 'xml') {
45
46 /*
47 * Relevant structure of token:
48 *
49 * htmlmixed
50 * className
51 * state
52 * htmlState
53 * type
54 * tagName
55 * context
56 * tagName
57 * mode
58 *
59 * xml
60 * className
61 * state
62 * tagName
63 * type
64 */
65
66 var pos = cm.getCursor();
67 var tok = cm.getTokenAt(pos);
68 var state = tok.state;
69
70 if (state.mode && state.mode != 'html') {
71 throw CodeMirror.Pass; // With htmlmixed, we only care about the html sub-mode.
72 }
73
74 if (ch == '>') {
75 var type = state.htmlState ? state.htmlState.type : state.type; // htmlmixed : xml
76
77 if (tok.className == 'tag' && type == 'closeTag') {
78 throw CodeMirror.Pass; // Don't process the '>' at the end of an end-tag.
79 }
80
81 cm.replaceSelection('>'); // Mode state won't update until we finish the tag.
82 pos = {line: pos.line, ch: pos.ch + 1};
83 cm.setCursor(pos);
84
85 tok = cm.getTokenAt(cm.getCursor());
86 state = tok.state;
87 type = state.htmlState ? state.htmlState.type : state.type; // htmlmixed : xml
88
89 if (tok.className == 'tag' && type != 'selfcloseTag') {
90 var tagName = state.htmlState ? state.htmlState.tagName : state.tagName; // htmlmixed : xml
91 if (tagName.length > 0 && shouldClose(cm, vd, tagName)) {
92 insertEndTag(cm, indent, pos, tagName);
93 }
94 return;
95 }
96
97 // Undo the '>' insert and allow cm to handle the key instead.
98 cm.setSelection({line: pos.line, ch: pos.ch - 1}, pos);
99 cm.replaceSelection("");
100
101 } else if (ch == '/') {
102 if (tok.className == 'tag' && tok.string == '<') {
103 var tagName = state.htmlState ? (state.htmlState.context ? state.htmlState.context.tagName : '') : (state.context ? state.context.tagName : ''); // htmlmixed : xml
104 if (tagName.length > 0) {
105 completeEndTag(cm, pos, tagName);
106 return;
107 }
108 }
109 }
110
111 }
112
113 throw CodeMirror.Pass; // Bubble if not handled
114 });
115
116 function insertEndTag(cm, indent, pos, tagName) {
117 if (shouldIndent(cm, indent, tagName)) {
118 cm.replaceSelection('\n\n</' + tagName + '>', 'end');
119 cm.indentLine(pos.line + 1);
120 cm.indentLine(pos.line + 2);
121 cm.setCursor({line: pos.line + 1, ch: cm.getLine(pos.line + 1).length});
122 } else {
123 cm.replaceSelection('</' + tagName + '>');
124 cm.setCursor(pos);
125 }
126 }
127
128 function shouldIndent(cm, indent, tagName) {
129 if (typeof indent == 'undefined' || indent == null || indent == true) {
130 indent = cm.getOption('closeTagIndent');
131 }
132 if (!indent) {
133 indent = [];
134 }
135 return indexOf(indent, tagName.toLowerCase()) != -1;
136 }
137
138 function shouldClose(cm, vd, tagName) {
139 if (cm.getOption('mode') == 'xml') {
140 return true; // always close xml tags
141 }
142 if (typeof vd == 'undefined' || vd == null) {
143 vd = cm.getOption('closeTagVoid');
144 }
145 if (!vd) {
146 vd = [];
147 }
148 return indexOf(vd, tagName.toLowerCase()) == -1;
149 }
150
151 // C&P from codemirror.js...would be nice if this were visible to utilities.
152 function indexOf(collection, elt) {
153 if (collection.indexOf) return collection.indexOf(elt);
154 for (var i = 0, e = collection.length; i < e; ++i)
155 if (collection[i] == elt) return i;
156 return -1;
157 }
158
159 function completeEndTag(cm, pos, tagName) {
160 cm.replaceSelection('/' + tagName + '>');
161 cm.setCursor({line: pos.line, ch: pos.ch + tagName.length + 2 });
162 }
163
164 })();
@@ -0,0 +1,51
1 (function() {
2 if (!CodeMirror.modeURL) CodeMirror.modeURL = "../mode/%N/%N.js";
3
4 var loading = {};
5 function splitCallback(cont, n) {
6 var countDown = n;
7 return function() { if (--countDown == 0) cont(); }
8 }
9 function ensureDeps(mode, cont) {
10 var deps = CodeMirror.modes[mode].dependencies;
11 if (!deps) return cont();
12 var missing = [];
13 for (var i = 0; i < deps.length; ++i) {
14 if (!CodeMirror.modes.hasOwnProperty(deps[i]))
15 missing.push(deps[i]);
16 }
17 if (!missing.length) return cont();
18 var split = splitCallback(cont, missing.length);
19 for (var i = 0; i < missing.length; ++i)
20 CodeMirror.requireMode(missing[i], split);
21 }
22
23 CodeMirror.requireMode = function(mode, cont) {
24 if (typeof mode != "string") mode = mode.name;
25 if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont);
26 if (loading.hasOwnProperty(mode)) return loading[mode].push(cont);
27
28 var script = document.createElement("script");
29 script.src = CodeMirror.modeURL.replace(/%N/g, mode);
30 var others = document.getElementsByTagName("script")[0];
31 others.parentNode.insertBefore(script, others);
32 var list = loading[mode] = [cont];
33 var count = 0, poll = setInterval(function() {
34 if (++count > 100) return clearInterval(poll);
35 if (CodeMirror.modes.hasOwnProperty(mode)) {
36 clearInterval(poll);
37 loading[mode] = null;
38 ensureDeps(mode, function() {
39 for (var i = 0; i < list.length; ++i) list[i]();
40 });
41 }
42 }, 200);
43 };
44
45 CodeMirror.autoLoadMode = function(instance, mode) {
46 if (!CodeMirror.modes.hasOwnProperty(mode))
47 CodeMirror.requireMode(mode, function() {
48 instance.setOption("mode", instance.getOption("mode"));
49 });
50 };
51 }());
@@ -0,0 +1,44
1 // Define match-highlighter commands. Depends on searchcursor.js
2 // Use by attaching the following function call to the onCursorActivity event:
3 //myCodeMirror.matchHighlight(minChars);
4 // And including a special span.CodeMirror-matchhighlight css class (also optionally a separate one for .CodeMirror-focused -- see demo matchhighlighter.html)
5
6 (function() {
7 var DEFAULT_MIN_CHARS = 2;
8
9 function MatchHighlightState() {
10 this.marked = [];
11 }
12 function getMatchHighlightState(cm) {
13 return cm._matchHighlightState || (cm._matchHighlightState = new MatchHighlightState());
14 }
15
16 function clearMarks(cm) {
17 var state = getMatchHighlightState(cm);
18 for (var i = 0; i < state.marked.length; ++i)
19 state.marked[i].clear();
20 state.marked = [];
21 }
22
23 function markDocument(cm, className, minChars) {
24 clearMarks(cm);
25 minChars = (typeof minChars !== 'undefined' ? minChars : DEFAULT_MIN_CHARS);
26 if (cm.somethingSelected() && cm.getSelection().replace(/^\s+|\s+$/g, "").length >= minChars) {
27 var state = getMatchHighlightState(cm);
28 var query = cm.getSelection();
29 cm.operation(function() {
30 if (cm.lineCount() < 2000) { // This is too expensive on big documents.
31 for (var cursor = cm.getSearchCursor(query); cursor.findNext();) {
32 //Only apply matchhighlight to the matches other than the one actually selected
33 if (!(cursor.from().line === cm.getCursor(true).line && cursor.from().ch === cm.getCursor(true).ch))
34 state.marked.push(cm.markText(cursor.from(), cursor.to(), className));
35 }
36 }
37 });
38 }
39 }
40
41 CodeMirror.defineExtension("matchHighlight", function(className, minChars) {
42 markDocument(this, className, minChars);
43 });
44 })();
@@ -0,0 +1,81
1 CodeMirror.multiplexingMode = function(outer /*, others */) {
2 // Others should be {open, close, mode [, delimStyle]} objects
3 var others = Array.prototype.slice.call(arguments, 1);
4 var n_others = others.length;
5
6 function indexOf(string, pattern, from) {
7 if (typeof pattern == "string") return string.indexOf(pattern, from);
8 var m = pattern.exec(from ? string.slice(from) : string);
9 return m ? m.index + from : -1;
10 }
11
12 return {
13 startState: function() {
14 return {
15 outer: CodeMirror.startState(outer),
16 innerActive: null,
17 inner: null
18 };
19 },
20
21 copyState: function(state) {
22 return {
23 outer: CodeMirror.copyState(outer, state.outer),
24 innerActive: state.innerActive,
25 inner: state.innerActive && CodeMirror.copyState(state.innerActive.mode, state.inner)
26 };
27 },
28
29 token: function(stream, state) {
30 if (!state.innerActive) {
31 var cutOff = Infinity, oldContent = stream.string;
32 for (var i = 0; i < n_others; ++i) {
33 var other = others[i];
34 var found = indexOf(oldContent, other.open, stream.pos);
35 if (found == stream.pos) {
36 stream.match(other.open);
37 state.innerActive = other;
38 state.inner = CodeMirror.startState(other.mode, outer.indent(state.outer, ""));
39 return other.delimStyle;
40 } else if (found != -1 && found < cutOff) {
41 cutOff = found;
42 }
43 }
44 if (cutOff != Infinity) stream.string = oldContent.slice(0, cutOff);
45 var outerToken = outer.token(stream, state.outer);
46 if (cutOff != Infinity) stream.string = oldContent;
47 return outerToken;
48 } else {
49 var curInner = state.innerActive, oldContent = stream.string;
50 var found = indexOf(oldContent, curInner.close, stream.pos);
51 if (found == stream.pos) {
52 stream.match(curInner.close);
53 state.innerActive = state.inner = null;
54 return curInner.delimStyle;
55 }
56 if (found > -1) stream.string = oldContent.slice(0, found);
57 var innerToken = curInner.mode.token(stream, state.inner);
58 if (found > -1) stream.string = oldContent;
59 var cur = stream.current(), found = cur.indexOf(curInner.close);
60 if (found > -1) stream.backUp(cur.length - found);
61 return innerToken;
62 }
63 },
64
65 indent: function(state, textAfter) {
66 var mode = state.innerActive ? state.innerActive.mode : outer;
67 if (!mode.indent) return CodeMirror.Pass;
68 return mode.indent(state.innerActive ? state.inner : state.outer, textAfter);
69 },
70
71 compareStates: function(a, b) {
72 if (a.innerActive != b.innerActive) return false;
73 var mode = a.innerActive || outer;
74 if (!mode.compareStates) return CodeMirror.Pass;
75 return mode.compareStates(a.innerActive ? a.inner : a.outer,
76 b.innerActive ? b.inner : b.outer);
77 },
78
79 electricChars: outer.electricChars
80 };
81 };
@@ -0,0 +1,123
1 (function () {
2 function forEach(arr, f) {
3 for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
4 }
5
6 function arrayContains(arr, item) {
7 if (!Array.prototype.indexOf) {
8 var i = arr.length;
9 while (i--) {
10 if (arr[i] === item) {
11 return true;
12 }
13 }
14 return false;
15 }
16 return arr.indexOf(item) != -1;
17 }
18
19 function scriptHint(editor, keywords, getToken) {
20 // Find the token at the cursor
21 var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token;
22 // If it's not a 'word-style' token, ignore the token.
23
24 if (!/^[\w$_]*$/.test(token.string)) {
25 token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state,
26 className: token.string == ":" ? "pig-type" : null};
27 }
28
29 if (!context) var context = [];
30 context.push(tprop);
31
32 var completionList = getCompletions(token, context);
33 completionList = completionList.sort();
34 //prevent autocomplete for last word, instead show dropdown with one word
35 if(completionList.length == 1) {
36 completionList.push(" ");
37 }
38
39 return {list: completionList,
40 from: {line: cur.line, ch: token.start},
41 to: {line: cur.line, ch: token.end}};
42 }
43
44 CodeMirror.pigHint = function(editor) {
45 return scriptHint(editor, pigKeywordsU, function (e, cur) {return e.getTokenAt(cur);});
46 }
47
48 function toTitleCase(str) {
49 return str.replace(/(?:^|\s)\w/g, function(match) {
50 return match.toUpperCase();
51 });
52 }
53
54 var pigKeywords = "VOID IMPORT RETURNS DEFINE LOAD FILTER FOREACH ORDER CUBE DISTINCT COGROUP "
55 + "JOIN CROSS UNION SPLIT INTO IF OTHERWISE ALL AS BY USING INNER OUTER ONSCHEMA PARALLEL "
56 + "PARTITION GROUP AND OR NOT GENERATE FLATTEN ASC DESC IS STREAM THROUGH STORE MAPREDUCE "
57 + "SHIP CACHE INPUT OUTPUT STDERROR STDIN STDOUT LIMIT SAMPLE LEFT RIGHT FULL EQ GT LT GTE LTE "
58 + "NEQ MATCHES TRUE FALSE";
59 var pigKeywordsU = pigKeywords.split(" ");
60 var pigKeywordsL = pigKeywords.toLowerCase().split(" ");
61
62 var pigTypes = "BOOLEAN INT LONG FLOAT DOUBLE CHARARRAY BYTEARRAY BAG TUPLE MAP";
63 var pigTypesU = pigTypes.split(" ");
64 var pigTypesL = pigTypes.toLowerCase().split(" ");
65
66 var pigBuiltins = "ABS ACOS ARITY ASIN ATAN AVG BAGSIZE BINSTORAGE BLOOM BUILDBLOOM CBRT CEIL "
67 + "CONCAT COR COS COSH COUNT COUNT_STAR COV CONSTANTSIZE CUBEDIMENSIONS DIFF DISTINCT DOUBLEABS "
68 + "DOUBLEAVG DOUBLEBASE DOUBLEMAX DOUBLEMIN DOUBLEROUND DOUBLESUM EXP FLOOR FLOATABS FLOATAVG "
69 + "FLOATMAX FLOATMIN FLOATROUND FLOATSUM GENERICINVOKER INDEXOF INTABS INTAVG INTMAX INTMIN "
70 + "INTSUM INVOKEFORDOUBLE INVOKEFORFLOAT INVOKEFORINT INVOKEFORLONG INVOKEFORSTRING INVOKER "
71 + "ISEMPTY JSONLOADER JSONMETADATA JSONSTORAGE LAST_INDEX_OF LCFIRST LOG LOG10 LOWER LONGABS "
72 + "LONGAVG LONGMAX LONGMIN LONGSUM MAX MIN MAPSIZE MONITOREDUDF NONDETERMINISTIC OUTPUTSCHEMA "
73 + "PIGSTORAGE PIGSTREAMING RANDOM REGEX_EXTRACT REGEX_EXTRACT_ALL REPLACE ROUND SIN SINH SIZE "
74 + "SQRT STRSPLIT SUBSTRING SUM STRINGCONCAT STRINGMAX STRINGMIN STRINGSIZE TAN TANH TOBAG "
75 + "TOKENIZE TOMAP TOP TOTUPLE TRIM TEXTLOADER TUPLESIZE UCFIRST UPPER UTF8STORAGECONVERTER";
76 var pigBuiltinsU = pigBuiltins.split(" ").join("() ").split(" ");
77 var pigBuiltinsL = pigBuiltins.toLowerCase().split(" ").join("() ").split(" ");
78 var pigBuiltinsC = ("BagSize BinStorage Bloom BuildBloom ConstantSize CubeDimensions DoubleAbs "
79 + "DoubleAvg DoubleBase DoubleMax DoubleMin DoubleRound DoubleSum FloatAbs FloatAvg FloatMax "
80 + "FloatMin FloatRound FloatSum GenericInvoker IntAbs IntAvg IntMax IntMin IntSum "
81 + "InvokeForDouble InvokeForFloat InvokeForInt InvokeForLong InvokeForString Invoker "
82 + "IsEmpty JsonLoader JsonMetadata JsonStorage LongAbs LongAvg LongMax LongMin LongSum MapSize "
83 + "MonitoredUDF Nondeterministic OutputSchema PigStorage PigStreaming StringConcat StringMax "
84 + "StringMin StringSize TextLoader TupleSize Utf8StorageConverter").split(" ").join("() ").split(" ");
85
86 function getCompletions(token, context) {
87 var found = [], start = token.string;
88 function maybeAdd(str) {
89 if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str);
90 }
91
92 function gatherCompletions(obj) {
93 if(obj == ":") {
94 forEach(pigTypesL, maybeAdd);
95 }
96 else {
97 forEach(pigBuiltinsU, maybeAdd);
98 forEach(pigBuiltinsL, maybeAdd);
99 forEach(pigBuiltinsC, maybeAdd);
100 forEach(pigTypesU, maybeAdd);
101 forEach(pigTypesL, maybeAdd);
102 forEach(pigKeywordsU, maybeAdd);
103 forEach(pigKeywordsL, maybeAdd);
104 }
105 }
106
107 if (context) {
108 // If this is a property, see if it belongs to some object we can
109 // find in the current environment.
110 var obj = context.pop(), base;
111
112 if (obj.className == "pig-word")
113 base = obj.string;
114 else if(obj.className == "pig-type")
115 base = ":" + obj.string;
116
117 while (base != null && context.length)
118 base = base[context.pop().string];
119 if (base != null) gatherCompletions(base);
120 }
121 return found;
122 }
123 })();
@@ -0,0 +1,137
1
2 (function() {
3
4 CodeMirror.xmlHints = [];
5
6 CodeMirror.xmlHint = function(cm, simbol) {
7
8 if(simbol.length > 0) {
9 var cursor = cm.getCursor();
10 cm.replaceSelection(simbol);
11 cursor = {line: cursor.line, ch: cursor.ch + 1};
12 cm.setCursor(cursor);
13 }
14
15 // dirty hack for simple-hint to receive getHint event on space
16 var getTokenAt = editor.getTokenAt;
17
18 editor.getTokenAt = function() { return 'disabled'; }
19 CodeMirror.simpleHint(cm, getHint);
20
21 editor.getTokenAt = getTokenAt;
22 };
23
24 var getHint = function(cm) {
25
26 var cursor = cm.getCursor();
27
28 if (cursor.ch > 0) {
29
30 var text = cm.getRange({line: 0, ch: 0}, cursor);
31 var typed = '';
32 var simbol = '';
33 for(var i = text.length - 1; i >= 0; i--) {
34 if(text[i] == ' ' || text[i] == '<') {
35 simbol = text[i];
36 break;
37 }
38 else {
39 typed = text[i] + typed;
40 }
41 }
42
43 text = text.substr(0, text.length - typed.length);
44
45 var path = getActiveElement(cm, text) + simbol;
46 var hints = CodeMirror.xmlHints[path];
47
48 if(typeof hints === 'undefined')
49 hints = [''];
50 else {
51 hints = hints.slice(0);
52 for (var i = hints.length - 1; i >= 0; i--) {
53 if(hints[i].indexOf(typed) != 0)
54 hints.splice(i, 1);
55 }
56 }
57
58 return {
59 list: hints,
60 from: { line: cursor.line, ch: cursor.ch - typed.length },
61 to: cursor,
62 };
63 };
64 }
65
66 var getActiveElement = function(codeMirror, text) {
67
68 var element = '';
69
70 if(text.length >= 0) {
71
72 var regex = new RegExp('<([^!?][^\\s/>]*).*?>', 'g');
73
74 var matches = [];
75 var match;
76 while ((match = regex.exec(text)) != null) {
77 matches.push({
78 tag: match[1],
79 selfclose: (match[0].substr(-1) === '/>')
80 });
81 }
82
83 for (var i = matches.length - 1, skip = 0; i >= 0; i--) {
84
85 var item = matches[i];
86
87 if (item.tag[0] == '/')
88 {
89 skip++;
90 }
91 else if (item.selfclose == false)
92 {
93 if (skip > 0)
94 {
95 skip--;
96 }
97 else
98 {
99 element = '<' + item.tag + '>' + element;
100 }
101 }
102 }
103
104 element += getOpenTag(text);
105 }
106
107 return element;
108 };
109
110 var getOpenTag = function(text) {
111
112 var open = text.lastIndexOf('<');
113 var close = text.lastIndexOf('>');
114
115 if (close < open)
116 {
117 text = text.substr(open);
118
119 if(text != '<') {
120
121 var space = text.indexOf(' ');
122 if(space < 0)
123 space = text.indexOf('\t');
124 if(space < 0)
125 space = text.indexOf('\n');
126
127 if (space < 0)
128 space = text.length;
129
130 return text.substr(0, space);
131 }
132 }
133
134 return '';
135 };
136
137 })();
@@ -0,0 +1,280
1 CodeMirror.defineMode("clike", function(config, parserConfig) {
2 var indentUnit = config.indentUnit,
3 keywords = parserConfig.keywords || {},
4 builtin = parserConfig.builtin || {},
5 blockKeywords = parserConfig.blockKeywords || {},
6 atoms = parserConfig.atoms || {},
7 hooks = parserConfig.hooks || {},
8 multiLineStrings = parserConfig.multiLineStrings;
9 var isOperatorChar = /[+\-*&%=<>!?|\/]/;
10
11 var curPunc;
12
13 function tokenBase(stream, state) {
14 var ch = stream.next();
15 if (hooks[ch]) {
16 var result = hooks[ch](stream, state);
17 if (result !== false) return result;
18 }
19 if (ch == '"' || ch == "'") {
20 state.tokenize = tokenString(ch);
21 return state.tokenize(stream, state);
22 }
23 if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
24 curPunc = ch;
25 return null;
26 }
27 if (/\d/.test(ch)) {
28 stream.eatWhile(/[\w\.]/);
29 return "number";
30 }
31 if (ch == "/") {
32 if (stream.eat("*")) {
33 state.tokenize = tokenComment;
34 return tokenComment(stream, state);
35 }
36 if (stream.eat("/")) {
37 stream.skipToEnd();
38 return "comment";
39 }
40 }
41 if (isOperatorChar.test(ch)) {
42 stream.eatWhile(isOperatorChar);
43 return "operator";
44 }
45 stream.eatWhile(/[\w\$_]/);
46 var cur = stream.current();
47 if (keywords.propertyIsEnumerable(cur)) {
48 if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
49 return "keyword";
50 }
51 if (builtin.propertyIsEnumerable(cur)) {
52 if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
53 return "builtin";
54 }
55 if (atoms.propertyIsEnumerable(cur)) return "atom";
56 return "variable";
57 }
58
59 function tokenString(quote) {
60 return function(stream, state) {
61 var escaped = false, next, end = false;
62 while ((next = stream.next()) != null) {
63 if (next == quote && !escaped) {end = true; break;}
64 escaped = !escaped && next == "\\";
65 }
66 if (end || !(escaped || multiLineStrings))
67 state.tokenize = null;
68 return "string";
69 };
70 }
71
72 function tokenComment(stream, state) {
73 var maybeEnd = false, ch;
74 while (ch = stream.next()) {
75 if (ch == "/" && maybeEnd) {
76 state.tokenize = null;
77 break;
78 }
79 maybeEnd = (ch == "*");
80 }
81 return "comment";
82 }
83
84 function Context(indented, column, type, align, prev) {
85 this.indented = indented;
86 this.column = column;
87 this.type = type;
88 this.align = align;
89 this.prev = prev;
90 }
91 function pushContext(state, col, type) {
92 return state.context = new Context(state.indented, col, type, null, state.context);
93 }
94 function popContext(state) {
95 var t = state.context.type;
96 if (t == ")" || t == "]" || t == "}")
97 state.indented = state.context.indented;
98 return state.context = state.context.prev;
99 }
100
101 // Interface
102
103 return {
104 startState: function(basecolumn) {
105 return {
106 tokenize: null,
107 context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
108 indented: 0,
109 startOfLine: true
110 };
111 },
112
113 token: function(stream, state) {
114 var ctx = state.context;
115 if (stream.sol()) {
116 if (ctx.align == null) ctx.align = false;
117 state.indented = stream.indentation();
118 state.startOfLine = true;
119 }
120 if (stream.eatSpace()) return null;
121 curPunc = null;
122 var style = (state.tokenize || tokenBase)(stream, state);
123 if (style == "comment" || style == "meta") return style;
124 if (ctx.align == null) ctx.align = true;
125
126 if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state);
127 else if (curPunc == "{") pushContext(state, stream.column(), "}");
128 else if (curPunc == "[") pushContext(state, stream.column(), "]");
129 else if (curPunc == "(") pushContext(state, stream.column(), ")");
130 else if (curPunc == "}") {
131 while (ctx.type == "statement") ctx = popContext(state);
132 if (ctx.type == "}") ctx = popContext(state);
133 while (ctx.type == "statement") ctx = popContext(state);
134 }
135 else if (curPunc == ctx.type) popContext(state);
136 else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement"))
137 pushContext(state, stream.column(), "statement");
138 state.startOfLine = false;
139 return style;
140 },
141
142 indent: function(state, textAfter) {
143 if (state.tokenize != tokenBase && state.tokenize != null) return 0;
144 var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
145 if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
146 var closing = firstChar == ctx.type;
147 if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit);
148 else if (ctx.align) return ctx.column + (closing ? 0 : 1);
149 else return ctx.indented + (closing ? 0 : indentUnit);
150 },
151
152 electricChars: "{}"
153 };
154 });
155
156 (function() {
157 function words(str) {
158 var obj = {}, words = str.split(" ");
159 for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
160 return obj;
161 }
162 var cKeywords = "auto if break int case long char register continue return default short do sizeof " +
163 "double static else struct entry switch extern typedef float union for unsigned " +
164 "goto while enum void const signed volatile";
165
166 function cppHook(stream, state) {
167 if (!state.startOfLine) return false;
168 stream.skipToEnd();
169 return "meta";
170 }
171
172 // C#-style strings where "" escapes a quote.
173 function tokenAtString(stream, state) {
174 var next;
175 while ((next = stream.next()) != null) {
176 if (next == '"' && !stream.eat('"')) {
177 state.tokenize = null;
178 break;
179 }
180 }
181 return "string";
182 }
183
184 CodeMirror.defineMIME("text/x-csrc", {
185 name: "clike",
186 keywords: words(cKeywords),
187 blockKeywords: words("case do else for if switch while struct"),
188 atoms: words("null"),
189 hooks: {"#": cppHook}
190 });
191 CodeMirror.defineMIME("text/x-c++src", {
192 name: "clike",
193 keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try bool explicit new " +
194 "static_cast typeid catch operator template typename class friend private " +
195 "this using const_cast inline public throw virtual delete mutable protected " +
196 "wchar_t"),
197 blockKeywords: words("catch class do else finally for if struct switch try while"),
198 atoms: words("true false null"),
199 hooks: {"#": cppHook}
200 });
201 CodeMirror.defineMIME("text/x-java", {
202 name: "clike",
203 keywords: words("abstract assert boolean break byte case catch char class const continue default " +
204 "do double else enum extends final finally float for goto if implements import " +
205 "instanceof int interface long native new package private protected public " +
206 "return short static strictfp super switch synchronized this throw throws transient " +
207 "try void volatile while"),
208 blockKeywords: words("catch class do else finally for if switch try while"),
209 atoms: words("true false null"),
210 hooks: {
211 "@": function(stream, state) {
212 stream.eatWhile(/[\w\$_]/);
213 return "meta";
214 }
215 }
216 });
217 CodeMirror.defineMIME("text/x-csharp", {
218 name: "clike",
219 keywords: words("abstract as base break case catch checked class const continue" +
220 " default delegate do else enum event explicit extern finally fixed for" +
221 " foreach goto if implicit in interface internal is lock namespace new" +
222 " operator out override params private protected public readonly ref return sealed" +
223 " sizeof stackalloc static struct switch this throw try typeof unchecked" +
224 " unsafe using virtual void volatile while add alias ascending descending dynamic from get" +
225 " global group into join let orderby partial remove select set value var yield"),
226 blockKeywords: words("catch class do else finally for foreach if struct switch try while"),
227 builtin: words("Boolean Byte Char DateTime DateTimeOffset Decimal Double" +
228 " Guid Int16 Int32 Int64 Object SByte Single String TimeSpan UInt16 UInt32" +
229 " UInt64 bool byte char decimal double short int long object" +
230 " sbyte float string ushort uint ulong"),
231 atoms: words("true false null"),
232 hooks: {
233 "@": function(stream, state) {
234 if (stream.eat('"')) {
235 state.tokenize = tokenAtString;
236 return tokenAtString(stream, state);
237 }
238 stream.eatWhile(/[\w\$_]/);
239 return "meta";
240 }
241 }
242 });
243 CodeMirror.defineMIME("text/x-scala", {
244 name: "clike",
245 keywords: words(
246
247 /* scala */
248 "abstract case catch class def do else extends false final finally for forSome if " +
249 "implicit import lazy match new null object override package private protected return " +
250 "sealed super this throw trait try trye type val var while with yield _ : = => <- <: " +
251 "<% >: # @ " +
252
253 /* package scala */
254 "assert assume require print println printf readLine readBoolean readByte readShort " +
255 "readChar readInt readLong readFloat readDouble " +
256
257 "AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either " +
258 "Enumeration Equiv Error Exception Fractional Function IndexedSeq Integral Iterable " +
259 "Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering " +
260 "Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder " +
261 "StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector :: #:: " +
262
263 /* package java.lang */
264 "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " +
265 "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " +
266 "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " +
267 "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void"
268
269
270 ),
271 blockKeywords: words("catch class do else finally for forSome if match switch try while"),
272 atoms: words("true false null"),
273 hooks: {
274 "@": function(stream, state) {
275 stream.eatWhile(/[\w\$_]/);
276 return "meta";
277 }
278 }
279 });
280 }());
@@ -0,0 +1,101
1 <!doctype html>
2 <html>
3 <head>
4 <title>CodeMirror: C-like mode</title>
5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 <script src="../../lib/codemirror.js"></script>
7 <script src="clike.js"></script>
8 <link rel="stylesheet" href="../../doc/docs.css">
9 <style>.CodeMirror {border: 2px inset #dee;}</style>
10 </head>
11 <body>
12 <h1>CodeMirror: C-like mode</h1>
13
14 <form><textarea id="code" name="code">
15 /* C demo code */
16
17 #include <zmq.h>
18 #include <pthread.h>
19 #include <semaphore.h>
20 #include <time.h>
21 #include <stdio.h>
22 #include <fcntl.h>
23 #include <malloc.h>
24
25 typedef struct {
26 void* arg_socket;
27 zmq_msg_t* arg_msg;
28 char* arg_string;
29 unsigned long arg_len;
30 int arg_int, arg_command;
31
32 int signal_fd;
33 int pad;
34 void* context;
35 sem_t sem;
36 } acl_zmq_context;
37
38 #define p(X) (context->arg_##X)
39
40 void* zmq_thread(void* context_pointer) {
41 acl_zmq_context* context = (acl_zmq_context*)context_pointer;
42 char ok = 'K', err = 'X';
43 int res;
44
45 while (1) {
46 while ((res = sem_wait(&amp;context->sem)) == EINTR);
47 if (res) {write(context->signal_fd, &amp;err, 1); goto cleanup;}
48 switch(p(command)) {
49 case 0: goto cleanup;
50 case 1: p(socket) = zmq_socket(context->context, p(int)); break;
51 case 2: p(int) = zmq_close(p(socket)); break;
52 case 3: p(int) = zmq_bind(p(socket), p(string)); break;
53 case 4: p(int) = zmq_connect(p(socket), p(string)); break;
54 case 5: p(int) = zmq_getsockopt(p(socket), p(int), (void*)p(string), &amp;p(len)); break;
55 case 6: p(int) = zmq_setsockopt(p(socket), p(int), (void*)p(string), p(len)); break;
56 case 7: p(int) = zmq_send(p(socket), p(msg), p(int)); break;
57 case 8: p(int) = zmq_recv(p(socket), p(msg), p(int)); break;
58 case 9: p(int) = zmq_poll(p(socket), p(int), p(len)); break;
59 }
60 p(command) = errno;
61 write(context->signal_fd, &amp;ok, 1);
62 }
63 cleanup:
64 close(context->signal_fd);
65 free(context_pointer);
66 return 0;
67 }
68
69 void* zmq_thread_init(void* zmq_context, int signal_fd) {
70 acl_zmq_context* context = malloc(sizeof(acl_zmq_context));
71 pthread_t thread;
72
73 context->context = zmq_context;
74 context->signal_fd = signal_fd;
75 sem_init(&amp;context->sem, 1, 0);
76 pthread_create(&amp;thread, 0, &amp;zmq_thread, context);
77 pthread_detach(thread);
78 return context;
79 }
80 </textarea></form>
81
82 <script>
83 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
84 lineNumbers: true,
85 matchBrackets: true,
86 mode: "text/x-csrc"
87 });
88 </script>
89
90 <p>Simple mode that tries to handle C-like languages as well as it
91 can. Takes two configuration parameters: <code>keywords</code>, an
92 object whose property names are the keywords in the language,
93 and <code>useCPP</code>, which determines whether C preprocessor
94 directives are recognized.</p>
95
96 <p><strong>MIME types defined:</strong> <code>text/x-csrc</code>
97 (C code), <code>text/x-c++src</code> (C++
98 code), <code>text/x-java</code> (Java
99 code), <code>text/x-csharp</code> (C#).</p>
100 </body>
101 </html>
This diff has been collapsed as it changes many lines, (765 lines changed) Show them Hide them
@@ -0,0 +1,765
1 <!doctype html>
2 <html>
3 <head>
4 <title>CodeMirror: C-like mode</title>
5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 <link rel="stylesheet" href="../../theme/ambiance.css">
7 <script src="../../lib/codemirror.js"></script>
8 <script src="clike.js"></script>
9 <link rel="stylesheet" href="../../doc/docs.css">
10 <style>
11 body
12 {
13 margin: 0;
14 padding: 0;
15 max-width:inherit;
16 height: 100%;
17 }
18 html, form, .CodeMirror, .CodeMirror-scroll
19 {
20 height: 100%;
21 }
22 </style>
23 </head>
24 <body>
25 <form>
26 <textarea id="code" name="code">
27
28 /* __ *\
29 ** ________ ___ / / ___ Scala API **
30 ** / __/ __// _ | / / / _ | (c) 2003-2011, LAMP/EPFL **
31 ** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
32 ** /____/\___/_/ |_/____/_/ | | **
33 ** |/ **
34 \* */
35
36 package scala.collection
37
38 import generic._
39 import mutable.{ Builder, ListBuffer }
40 import annotation.{tailrec, migration, bridge}
41 import annotation.unchecked.{ uncheckedVariance => uV }
42 import parallel.ParIterable
43
44 /** A template trait for traversable collections of type `Traversable[A]`.
45 *
46 * $traversableInfo
47 * @define mutability
48 * @define traversableInfo
49 * This is a base trait of all kinds of $mutability Scala collections. It
50 * implements the behavior common to all collections, in terms of a method
51 * `foreach` with signature:
52 * {{{
53 * def foreach[U](f: Elem => U): Unit
54 * }}}
55 * Collection classes mixing in this trait provide a concrete
56 * `foreach` method which traverses all the
57 * elements contained in the collection, applying a given function to each.
58 * They also need to provide a method `newBuilder`
59 * which creates a builder for collections of the same kind.
60 *
61 * A traversable class might or might not have two properties: strictness
62 * and orderedness. Neither is represented as a type.
63 *
64 * The instances of a strict collection class have all their elements
65 * computed before they can be used as values. By contrast, instances of
66 * a non-strict collection class may defer computation of some of their
67 * elements until after the instance is available as a value.
68 * A typical example of a non-strict collection class is a
69 * <a href="../immutable/Stream.html" target="ContentFrame">
70 * `scala.collection.immutable.Stream`</a>.
71 * A more general class of examples are `TraversableViews`.
72 *
73 * If a collection is an instance of an ordered collection class, traversing
74 * its elements with `foreach` will always visit elements in the
75 * same order, even for different runs of the program. If the class is not
76 * ordered, `foreach` can visit elements in different orders for
77 * different runs (but it will keep the same order in the same run).'
78 *
79 * A typical example of a collection class which is not ordered is a
80 * `HashMap` of objects. The traversal order for hash maps will
81 * depend on the hash codes of its elements, and these hash codes might
82 * differ from one run to the next. By contrast, a `LinkedHashMap`
83 * is ordered because it's `foreach` method visits elements in the
84 * order they were inserted into the `HashMap`.
85 *
86 * @author Martin Odersky
87 * @version 2.8
88 * @since 2.8
89 * @tparam A the element type of the collection
90 * @tparam Repr the type of the actual collection containing the elements.
91 *
92 * @define Coll Traversable
93 * @define coll traversable collection
94 */
95 trait TraversableLike[+A, +Repr] extends HasNewBuilder[A, Repr]
96 with FilterMonadic[A, Repr]
97 with TraversableOnce[A]
98 with GenTraversableLike[A, Repr]
99 with Parallelizable[A, ParIterable[A]]
100 {
101 self =>
102
103 import Traversable.breaks._
104
105 /** The type implementing this traversable */
106 protected type Self = Repr
107
108 /** The collection of type $coll underlying this `TraversableLike` object.
109 * By default this is implemented as the `TraversableLike` object itself,
110 * but this can be overridden.
111 */
112 def repr: Repr = this.asInstanceOf[Repr]
113
114 /** The underlying collection seen as an instance of `$Coll`.
115 * By default this is implemented as the current collection object itself,
116 * but this can be overridden.
117 */
118 protected[this] def thisCollection: Traversable[A] = this.asInstanceOf[Traversable[A]]
119
120 /** A conversion from collections of type `Repr` to `$Coll` objects.
121 * By default this is implemented as just a cast, but this can be overridden.
122 */
123 protected[this] def toCollection(repr: Repr): Traversable[A] = repr.asInstanceOf[Traversable[A]]
124
125 /** Creates a new builder for this collection type.
126 */
127 protected[this] def newBuilder: Builder[A, Repr]
128
129 protected[this] def parCombiner = ParIterable.newCombiner[A]
130
131 /** Applies a function `f` to all elements of this $coll.
132 *
133 * Note: this method underlies the implementation of most other bulk operations.
134 * It's important to implement this method in an efficient way.
135 *
136 *
137 * @param f the function that is applied for its side-effect to every element.
138 * The result of function `f` is discarded.
139 *
140 * @tparam U the type parameter describing the result of function `f`.
141 * This result will always be ignored. Typically `U` is `Unit`,
142 * but this is not necessary.
143 *
144 * @usecase def foreach(f: A => Unit): Unit
145 */
146 def foreach[U](f: A => U): Unit
147
148 /** Tests whether this $coll is empty.
149 *
150 * @return `true` if the $coll contain no elements, `false` otherwise.
151 */
152 def isEmpty: Boolean = {
153 var result = true
154 breakable {
155 for (x <- this) {
156 result = false
157 break
158 }
159 }
160 result
161 }
162
163 /** Tests whether this $coll is known to have a finite size.
164 * All strict collections are known to have finite size. For a non-strict collection
165 * such as `Stream`, the predicate returns `true` if all elements have been computed.
166 * It returns `false` if the stream is not yet evaluated to the end.
167 *
168 * Note: many collection methods will not work on collections of infinite sizes.
169 *
170 * @return `true` if this collection is known to have finite size, `false` otherwise.
171 */
172 def hasDefiniteSize = true
173
174 def ++[B >: A, That](that: GenTraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
175 val b = bf(repr)
176 if (that.isInstanceOf[IndexedSeqLike[_, _]]) b.sizeHint(this, that.seq.size)
177 b ++= thisCollection
178 b ++= that.seq
179 b.result
180 }
181
182 @bridge
183 def ++[B >: A, That](that: TraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That =
184 ++(that: GenTraversableOnce[B])(bf)
185
186 /** Concatenates this $coll with the elements of a traversable collection.
187 * It differs from ++ in that the right operand determines the type of the
188 * resulting collection rather than the left one.
189 *
190 * @param that the traversable to append.
191 * @tparam B the element type of the returned collection.
192 * @tparam That $thatinfo
193 * @param bf $bfinfo
194 * @return a new collection of type `That` which contains all elements
195 * of this $coll followed by all elements of `that`.
196 *
197 * @usecase def ++:[B](that: TraversableOnce[B]): $Coll[B]
198 *
199 * @return a new $coll which contains all elements of this $coll
200 * followed by all elements of `that`.
201 */
202 def ++:[B >: A, That](that: TraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
203 val b = bf(repr)
204 if (that.isInstanceOf[IndexedSeqLike[_, _]]) b.sizeHint(this, that.size)
205 b ++= that
206 b ++= thisCollection
207 b.result
208 }
209
210 /** This overload exists because: for the implementation of ++: we should reuse
211 * that of ++ because many collections override it with more efficient versions.
212 * Since TraversableOnce has no '++' method, we have to implement that directly,
213 * but Traversable and down can use the overload.
214 */
215 def ++:[B >: A, That](that: Traversable[B])(implicit bf: CanBuildFrom[Repr, B, That]): That =
216 (that ++ seq)(breakOut)
217
218 def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
219 val b = bf(repr)
220 b.sizeHint(this)
221 for (x <- this) b += f(x)
222 b.result
223 }
224
225 def flatMap[B, That](f: A => GenTraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
226 val b = bf(repr)
227 for (x <- this) b ++= f(x).seq
228 b.result
229 }
230
231 /** Selects all elements of this $coll which satisfy a predicate.
232 *
233 * @param p the predicate used to test elements.
234 * @return a new $coll consisting of all elements of this $coll that satisfy the given
235 * predicate `p`. The order of the elements is preserved.
236 */
237 def filter(p: A => Boolean): Repr = {
238 val b = newBuilder
239 for (x <- this)
240 if (p(x)) b += x
241 b.result
242 }
243
244 /** Selects all elements of this $coll which do not satisfy a predicate.
245 *
246 * @param p the predicate used to test elements.
247 * @return a new $coll consisting of all elements of this $coll that do not satisfy the given
248 * predicate `p`. The order of the elements is preserved.
249 */
250 def filterNot(p: A => Boolean): Repr = filter(!p(_))
251
252 def collect[B, That](pf: PartialFunction[A, B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
253 val b = bf(repr)
254 for (x <- this) if (pf.isDefinedAt(x)) b += pf(x)
255 b.result
256 }
257
258 /** Builds a new collection by applying an option-valued function to all
259 * elements of this $coll on which the function is defined.
260 *
261 * @param f the option-valued function which filters and maps the $coll.
262 * @tparam B the element type of the returned collection.
263 * @tparam That $thatinfo
264 * @param bf $bfinfo
265 * @return a new collection of type `That` resulting from applying the option-valued function
266 * `f` to each element and collecting all defined results.
267 * The order of the elements is preserved.
268 *
269 * @usecase def filterMap[B](f: A => Option[B]): $Coll[B]
270 *
271 * @param pf the partial function which filters and maps the $coll.
272 * @return a new $coll resulting from applying the given option-valued function
273 * `f` to each element and collecting all defined results.
274 * The order of the elements is preserved.
275 def filterMap[B, That](f: A => Option[B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
276 val b = bf(repr)
277 for (x <- this)
278 f(x) match {
279 case Some(y) => b += y
280 case _ =>
281 }
282 b.result
283 }
284 */
285
286 /** Partitions this $coll in two ${coll}s according to a predicate.
287 *
288 * @param p the predicate on which to partition.
289 * @return a pair of ${coll}s: the first $coll consists of all elements that
290 * satisfy the predicate `p` and the second $coll consists of all elements
291 * that don't. The relative order of the elements in the resulting ${coll}s
292 * is the same as in the original $coll.
293 */
294 def partition(p: A => Boolean): (Repr, Repr) = {
295 val l, r = newBuilder
296 for (x <- this) (if (p(x)) l else r) += x
297 (l.result, r.result)
298 }
299
300 def groupBy[K](f: A => K): immutable.Map[K, Repr] = {
301 val m = mutable.Map.empty[K, Builder[A, Repr]]
302 for (elem <- this) {
303 val key = f(elem)
304 val bldr = m.getOrElseUpdate(key, newBuilder)
305 bldr += elem
306 }
307 val b = immutable.Map.newBuilder[K, Repr]
308 for ((k, v) <- m)
309 b += ((k, v.result))
310
311 b.result
312 }
313
314 /** Tests whether a predicate holds for all elements of this $coll.
315 *
316 * $mayNotTerminateInf
317 *
318 * @param p the predicate used to test elements.
319 * @return `true` if the given predicate `p` holds for all elements
320 * of this $coll, otherwise `false`.
321 */
322 def forall(p: A => Boolean): Boolean = {
323 var result = true
324 breakable {
325 for (x <- this)
326 if (!p(x)) { result = false; break }
327 }
328 result
329 }
330
331 /** Tests whether a predicate holds for some of the elements of this $coll.
332 *
333 * $mayNotTerminateInf
334 *
335 * @param p the predicate used to test elements.
336 * @return `true` if the given predicate `p` holds for some of the
337 * elements of this $coll, otherwise `false`.
338 */
339 def exists(p: A => Boolean): Boolean = {
340 var result = false
341 breakable {
342 for (x <- this)
343 if (p(x)) { result = true; break }
344 }
345 result
346 }
347
348 /** Finds the first element of the $coll satisfying a predicate, if any.
349 *
350 * $mayNotTerminateInf
351 * $orderDependent
352 *
353 * @param p the predicate used to test elements.
354 * @return an option value containing the first element in the $coll
355 * that satisfies `p`, or `None` if none exists.
356 */
357 def find(p: A => Boolean): Option[A] = {
358 var result: Option[A] = None
359 breakable {
360 for (x <- this)
361 if (p(x)) { result = Some(x); break }
362 }
363 result
364 }
365
366 def scan[B >: A, That](z: B)(op: (B, B) => B)(implicit cbf: CanBuildFrom[Repr, B, That]): That = scanLeft(z)(op)
367
368 def scanLeft[B, That](z: B)(op: (B, A) => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
369 val b = bf(repr)
370 b.sizeHint(this, 1)
371 var acc = z
372 b += acc
373 for (x <- this) { acc = op(acc, x); b += acc }
374 b.result
375 }
376
377 @migration(2, 9,
378 "This scanRight definition has changed in 2.9.\n" +
379 "The previous behavior can be reproduced with scanRight.reverse."
380 )
381 def scanRight[B, That](z: B)(op: (A, B) => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
382 var scanned = List(z)
383 var acc = z
384 for (x <- reversed) {
385 acc = op(x, acc)
386 scanned ::= acc
387 }
388 val b = bf(repr)
389 for (elem <- scanned) b += elem
390 b.result
391 }
392
393 /** Selects the first element of this $coll.
394 * $orderDependent
395 * @return the first element of this $coll.
396 * @throws `NoSuchElementException` if the $coll is empty.
397 */
398 def head: A = {
399 var result: () => A = () => throw new NoSuchElementException
400 breakable {
401 for (x <- this) {
402 result = () => x
403 break
404 }
405 }
406 result()
407 }
408
409 /** Optionally selects the first element.
410 * $orderDependent
411 * @return the first element of this $coll if it is nonempty, `None` if it is empty.
412 */
413 def headOption: Option[A] = if (isEmpty) None else Some(head)
414
415 /** Selects all elements except the first.
416 * $orderDependent
417 * @return a $coll consisting of all elements of this $coll
418 * except the first one.
419 * @throws `UnsupportedOperationException` if the $coll is empty.
420 */
421 override def tail: Repr = {
422 if (isEmpty) throw new UnsupportedOperationException("empty.tail")
423 drop(1)
424 }
425
426 /** Selects the last element.
427 * $orderDependent
428 * @return The last element of this $coll.
429 * @throws NoSuchElementException If the $coll is empty.
430 */
431 def last: A = {
432 var lst = head
433 for (x <- this)
434 lst = x
435 lst
436 }
437
438 /** Optionally selects the last element.
439 * $orderDependent
440 * @return the last element of this $coll$ if it is nonempty, `None` if it is empty.
441 */
442 def lastOption: Option[A] = if (isEmpty) None else Some(last)
443
444 /** Selects all elements except the last.
445 * $orderDependent
446 * @return a $coll consisting of all elements of this $coll
447 * except the last one.
448 * @throws `UnsupportedOperationException` if the $coll is empty.
449 */
450 def init: Repr = {
451 if (isEmpty) throw new UnsupportedOperationException("empty.init")
452 var lst = head
453 var follow = false
454 val b = newBuilder
455 b.sizeHint(this, -1)
456 for (x <- this.seq) {
457 if (follow) b += lst
458 else follow = true
459 lst = x
460 }
461 b.result
462 }
463
464 def take(n: Int): Repr = slice(0, n)
465
466 def drop(n: Int): Repr =
467 if (n <= 0) {
468 val b = newBuilder
469 b.sizeHint(this)
470 b ++= thisCollection result
471 }
472 else sliceWithKnownDelta(n, Int.MaxValue, -n)
473
474 def slice(from: Int, until: Int): Repr = sliceWithKnownBound(math.max(from, 0), until)
475
476 // Precondition: from >= 0, until > 0, builder already configured for building.
477 private[this] def sliceInternal(from: Int, until: Int, b: Builder[A, Repr]): Repr = {
478 var i = 0
479 breakable {
480 for (x <- this.seq) {
481 if (i >= from) b += x
482 i += 1
483 if (i >= until) break
484 }
485 }
486 b.result
487 }
488 // Precondition: from >= 0
489 private[scala] def sliceWithKnownDelta(from: Int, until: Int, delta: Int): Repr = {
490 val b = newBuilder
491 if (until <= from) b.result
492 else {
493 b.sizeHint(this, delta)
494 sliceInternal(from, until, b)
495 }
496 }
497 // Precondition: from >= 0
498 private[scala] def sliceWithKnownBound(from: Int, until: Int): Repr = {
499 val b = newBuilder
500 if (until <= from) b.result
501 else {
502 b.sizeHintBounded(until - from, this)
503 sliceInternal(from, until, b)
504 }
505 }
506
507 def takeWhile(p: A => Boolean): Repr = {
508 val b = newBuilder
509 breakable {
510 for (x <- this) {
511 if (!p(x)) break
512 b += x
513 }
514 }
515 b.result
516 }
517
518 def dropWhile(p: A => Boolean): Repr = {
519 val b = newBuilder
520 var go = false
521 for (x <- this) {
522 if (!p(x)) go = true
523 if (go) b += x
524 }
525 b.result
526 }
527
528 def span(p: A => Boolean): (Repr, Repr) = {
529 val l, r = newBuilder
530 var toLeft = true
531 for (x <- this) {
532 toLeft = toLeft && p(x)
533 (if (toLeft) l else r) += x
534 }
535 (l.result, r.result)
536 }
537
538 def splitAt(n: Int): (Repr, Repr) = {
539 val l, r = newBuilder
540 l.sizeHintBounded(n, this)
541 if (n >= 0) r.sizeHint(this, -n)
542 var i = 0
543 for (x <- this) {
544 (if (i < n) l else r) += x
545 i += 1
546 }
547 (l.result, r.result)
548 }
549
550 /** Iterates over the tails of this $coll. The first value will be this
551 * $coll and the final one will be an empty $coll, with the intervening
552 * values the results of successive applications of `tail`.
553 *
554 * @return an iterator over all the tails of this $coll
555 * @example `List(1,2,3).tails = Iterator(List(1,2,3), List(2,3), List(3), Nil)`
556 */
557 def tails: Iterator[Repr] = iterateUntilEmpty(_.tail)
558
559 /** Iterates over the inits of this $coll. The first value will be this
560 * $coll and the final one will be an empty $coll, with the intervening
561 * values the results of successive applications of `init`.
562 *
563 * @return an iterator over all the inits of this $coll
564 * @example `List(1,2,3).inits = Iterator(List(1,2,3), List(1,2), List(1), Nil)`
565 */
566 def inits: Iterator[Repr] = iterateUntilEmpty(_.init)
567
568 /** Copies elements of this $coll to an array.
569 * Fills the given array `xs` with at most `len` elements of
570 * this $coll, starting at position `start`.
571 * Copying will stop once either the end of the current $coll is reached,
572 * or the end of the array is reached, or `len` elements have been copied.
573 *
574 * $willNotTerminateInf
575 *
576 * @param xs the array to fill.
577 * @param start the starting index.
578 * @param len the maximal number of elements to copy.
579 * @tparam B the type of the elements of the array.
580 *
581 *
582 * @usecase def copyToArray(xs: Array[A], start: Int, len: Int): Unit
583 */
584 def copyToArray[B >: A](xs: Array[B], start: Int, len: Int) {
585 var i = start
586 val end = (start + len) min xs.length
587 breakable {
588 for (x <- this) {
589 if (i >= end) break
590 xs(i) = x
591 i += 1
592 }
593 }
594 }
595
596 def toTraversable: Traversable[A] = thisCollection
597 def toIterator: Iterator[A] = toStream.iterator
598 def toStream: Stream[A] = toBuffer.toStream
599
600 /** Converts this $coll to a string.
601 *
602 * @return a string representation of this collection. By default this
603 * string consists of the `stringPrefix` of this $coll,
604 * followed by all elements separated by commas and enclosed in parentheses.
605 */
606 override def toString = mkString(stringPrefix + "(", ", ", ")")
607
608 /** Defines the prefix of this object's `toString` representation.
609 *
610 * @return a string representation which starts the result of `toString`
611 * applied to this $coll. By default the string prefix is the
612 * simple name of the collection class $coll.
613 */
614 def stringPrefix : String = {
615 var string = repr.asInstanceOf[AnyRef].getClass.getName
616 val idx1 = string.lastIndexOf('.' : Int)
617 if (idx1 != -1) string = string.substring(idx1 + 1)
618 val idx2 = string.indexOf('$')
619 if (idx2 != -1) string = string.substring(0, idx2)
620 string
621 }
622
623 /** Creates a non-strict view of this $coll.
624 *
625 * @return a non-strict view of this $coll.
626 */
627 def view = new TraversableView[A, Repr] {
628 protected lazy val underlying = self.repr
629 override def foreach[U](f: A => U) = self foreach f
630 }
631
632 /** Creates a non-strict view of a slice of this $coll.
633 *
634 * Note: the difference between `view` and `slice` is that `view` produces
635 * a view of the current $coll, whereas `slice` produces a new $coll.
636 *
637 * Note: `view(from, to)` is equivalent to `view.slice(from, to)`
638 * $orderDependent
639 *
640 * @param from the index of the first element of the view
641 * @param until the index of the element following the view
642 * @return a non-strict view of a slice of this $coll, starting at index `from`
643 * and extending up to (but not including) index `until`.
644 */
645 def view(from: Int, until: Int): TraversableView[A, Repr] = view.slice(from, until)
646
647 /** Creates a non-strict filter of this $coll.
648 *
649 * Note: the difference between `c filter p` and `c withFilter p` is that
650 * the former creates a new collection, whereas the latter only
651 * restricts the domain of subsequent `map`, `flatMap`, `foreach`,
652 * and `withFilter` operations.
653 * $orderDependent
654 *
655 * @param p the predicate used to test elements.
656 * @return an object of class `WithFilter`, which supports
657 * `map`, `flatMap`, `foreach`, and `withFilter` operations.
658 * All these operations apply to those elements of this $coll which
659 * satisfy the predicate `p`.
660 */
661 def withFilter(p: A => Boolean): FilterMonadic[A, Repr] = new WithFilter(p)
662
663 /** A class supporting filtered operations. Instances of this class are
664 * returned by method `withFilter`.
665 */
666 class WithFilter(p: A => Boolean) extends FilterMonadic[A, Repr] {
667
668 /** Builds a new collection by applying a function to all elements of the
669 * outer $coll containing this `WithFilter` instance that satisfy predicate `p`.
670 *
671 * @param f the function to apply to each element.
672 * @tparam B the element type of the returned collection.
673 * @tparam That $thatinfo
674 * @param bf $bfinfo
675 * @return a new collection of type `That` resulting from applying
676 * the given function `f` to each element of the outer $coll
677 * that satisfies predicate `p` and collecting the results.
678 *
679 * @usecase def map[B](f: A => B): $Coll[B]
680 *
681 * @return a new $coll resulting from applying the given function
682 * `f` to each element of the outer $coll that satisfies
683 * predicate `p` and collecting the results.
684 */
685 def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
686 val b = bf(repr)
687 for (x <- self)
688 if (p(x)) b += f(x)
689 b.result
690 }
691
692 /** Builds a new collection by applying a function to all elements of the
693 * outer $coll containing this `WithFilter` instance that satisfy
694 * predicate `p` and concatenating the results.
695 *
696 * @param f the function to apply to each element.
697 * @tparam B the element type of the returned collection.
698 * @tparam That $thatinfo
699 * @param bf $bfinfo
700 * @return a new collection of type `That` resulting from applying
701 * the given collection-valued function `f` to each element
702 * of the outer $coll that satisfies predicate `p` and
703 * concatenating the results.
704 *
705 * @usecase def flatMap[B](f: A => TraversableOnce[B]): $Coll[B]
706 *
707 * @return a new $coll resulting from applying the given collection-valued function
708 * `f` to each element of the outer $coll that satisfies predicate `p` and concatenating the results.
709 */
710 def flatMap[B, That](f: A => GenTraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
711 val b = bf(repr)
712 for (x <- self)
713 if (p(x)) b ++= f(x).seq
714 b.result
715 }
716
717 /** Applies a function `f` to all elements of the outer $coll containing
718 * this `WithFilter` instance that satisfy predicate `p`.
719 *
720 * @param f the function that is applied for its side-effect to every element.
721 * The result of function `f` is discarded.
722 *
723 * @tparam U the type parameter describing the result of function `f`.
724 * This result will always be ignored. Typically `U` is `Unit`,
725 * but this is not necessary.
726 *
727 * @usecase def foreach(f: A => Unit): Unit
728 */
729 def foreach[U](f: A => U): Unit =
730 for (x <- self)
731 if (p(x)) f(x)
732
733 /** Further refines the filter for this $coll.
734 *
735 * @param q the predicate used to test elements.
736 * @return an object of class `WithFilter`, which supports
737 * `map`, `flatMap`, `foreach`, and `withFilter` operations.
738 * All these operations apply to those elements of this $coll which
739 * satisfy the predicate `q` in addition to the predicate `p`.
740 */
741 def withFilter(q: A => Boolean): WithFilter =
742 new WithFilter(x => p(x) && q(x))
743 }
744
745 // A helper for tails and inits.
746 private def iterateUntilEmpty(f: Traversable[A @uV] => Traversable[A @uV]): Iterator[Repr] = {
747 val it = Iterator.iterate(thisCollection)(f) takeWhile (x => !x.isEmpty)
748 it ++ Iterator(Nil) map (newBuilder ++= _ result)
749 }
750 }
751
752
753 </textarea>
754 </form>
755
756 <script>
757 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
758 lineNumbers: true,
759 matchBrackets: true,
760 theme: "ambiance",
761 mode: "text/x-scala"
762 });
763 </script>
764 </body>
765 </html>
@@ -0,0 +1,207
1 /**
2 * Author: Hans Engel
3 * Branched from CodeMirror's Scheme mode (by Koh Zi Han, based on implementation by Koh Zi Chun)
4 */
5 CodeMirror.defineMode("clojure", function (config, mode) {
6 var BUILTIN = "builtin", COMMENT = "comment", STRING = "string", TAG = "tag",
7 ATOM = "atom", NUMBER = "number", BRACKET = "bracket", KEYWORD = "keyword";
8 var INDENT_WORD_SKIP = 2, KEYWORDS_SKIP = 1;
9
10 function makeKeywords(str) {
11 var obj = {}, words = str.split(" ");
12 for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
13 return obj;
14 }
15
16 var atoms = makeKeywords("true false nil");
17
18 var keywords = makeKeywords(
19 "defn defn- def def- defonce defmulti defmethod defmacro defstruct deftype defprotocol defrecord defproject deftest slice defalias defhinted defmacro- defn-memo defnk defnk defonce- defunbound defunbound- defvar defvar- let letfn do case cond condp for loop recur when when-not when-let when-first if if-let if-not . .. -> ->> doto and or dosync doseq dotimes dorun doall load import unimport ns in-ns refer try catch finally throw with-open with-local-vars binding gen-class gen-and-load-class gen-and-save-class handler-case handle");
20
21 var builtins = makeKeywords(
22 "* *1 *2 *3 *agent* *allow-unresolved-vars* *assert *clojure-version* *command-line-args* *compile-files* *compile-path* *e *err* *file* *flush-on-newline* *in* *macro-meta* *math-context* *ns* *out* *print-dup* *print-length* *print-level* *print-meta* *print-readably* *read-eval* *source-path* *use-context-classloader* *warn-on-reflection* + - / < <= = == > >= accessor aclone agent agent-errors aget alength alias all-ns alter alter-meta! alter-var-root amap ancestors and apply areduce array-map aset aset-boolean aset-byte aset-char aset-double aset-float aset-int aset-long aset-short assert assoc assoc! assoc-in associative? atom await await-for await1 bases bean bigdec bigint binding bit-and bit-and-not bit-clear bit-flip bit-not bit-or bit-set bit-shift-left bit-shift-right bit-test bit-xor boolean boolean-array booleans bound-fn bound-fn* butlast byte byte-array bytes case cast char char-array char-escape-string char-name-string char? chars chunk chunk-append chunk-buffer chunk-cons chunk-first chunk-next chunk-rest chunked-seq? class class? clear-agent-errors clojure-version coll? comment commute comp comparator compare compare-and-set! compile complement concat cond condp conj conj! cons constantly construct-proxy contains? count counted? create-ns create-struct cycle dec decimal? declare definline defmacro defmethod defmulti defn defn- defonce defstruct delay delay? deliver deref derive descendants destructure disj disj! dissoc dissoc! distinct distinct? doall doc dorun doseq dosync dotimes doto double double-array doubles drop drop-last drop-while empty empty? ensure enumeration-seq eval even? every? extend extend-protocol extend-type extends? extenders false? ffirst file-seq filter find find-doc find-ns find-var first float float-array float? floats flush fn fn? fnext for force format future future-call future-cancel future-cancelled? future-done? future? gen-class gen-interface gensym get get-in get-method get-proxy-class get-thread-bindings get-validator hash hash-map hash-set identical? identity if-let if-not ifn? import in-ns inc init-proxy instance? int int-array integer? interleave intern interpose into into-array ints io! isa? iterate iterator-seq juxt key keys keyword keyword? last lazy-cat lazy-seq let letfn line-seq list list* list? load load-file load-reader load-string loaded-libs locking long long-array longs loop macroexpand macroexpand-1 make-array make-hierarchy map map? mapcat max max-key memfn memoize merge merge-with meta method-sig methods min min-key mod name namespace neg? newline next nfirst nil? nnext not not-any? not-empty not-every? not= ns ns-aliases ns-imports ns-interns ns-map ns-name ns-publics ns-refers ns-resolve ns-unalias ns-unmap nth nthnext num number? odd? or parents partial partition pcalls peek persistent! pmap pop pop! pop-thread-bindings pos? pr pr-str prefer-method prefers primitives-classnames print print-ctor print-doc print-dup print-method print-namespace-doc print-simple print-special-doc print-str printf println println-str prn prn-str promise proxy proxy-call-with-super proxy-mappings proxy-name proxy-super push-thread-bindings pvalues quot rand rand-int range ratio? rational? rationalize re-find re-groups re-matcher re-matches re-pattern re-seq read read-line read-string reify reduce ref ref-history-count ref-max-history ref-min-history ref-set refer refer-clojure release-pending-sends rem remove remove-method remove-ns repeat repeatedly replace replicate require reset! reset-meta! resolve rest resultset-seq reverse reversible? rseq rsubseq satisfies? second select-keys send send-off seq seq? seque sequence sequential? set set-validator! set? short short-array shorts shutdown-agents slurp some sort sort-by sorted-map sorted-map-by sorted-set sorted-set-by sorted? special-form-anchor special-symbol? split-at split-with str stream? string? struct struct-map subs subseq subvec supers swap! symbol symbol? sync syntax-symbol-anchor take take-last take-nth take-while test the-ns time to-array to-array-2d trampoline transient tree-seq true? type unchecked-add unchecked-dec unchecked-divide unchecked-inc unchecked-multiply unchecked-negate unchecked-remainder unchecked-subtract underive unquote unquote-splicing update-in update-proxy use val vals var-get var-set var? vary-meta vec vector vector? when when-first when-let when-not while with-bindings with-bindings* with-in-str with-loading-context with-local-vars with-meta with-open with-out-str with-precision xml-seq");
23
24 var indentKeys = makeKeywords(
25 // Built-ins
26 "ns fn def defn defmethod bound-fn if if-not case condp when while when-not when-first do future comment doto locking proxy with-open with-precision reify deftype defrecord defprotocol extend extend-protocol extend-type try catch " +
27
28 // Binding forms
29 "let letfn binding loop for doseq dotimes when-let if-let " +
30
31 // Data structures
32 "defstruct struct-map assoc " +
33
34 // clojure.test
35 "testing deftest " +
36
37 // contrib
38 "handler-case handle dotrace deftrace");
39
40 var tests = {
41 digit: /\d/,
42 digit_or_colon: /[\d:]/,
43 hex: /[0-9a-fA-F]/,
44 sign: /[+-]/,
45 exponent: /[eE]/,
46 keyword_char: /[^\s\(\[\;\)\]]/,
47 basic: /[\w\$_\-]/,
48 lang_keyword: /[\w*+!\-_?:\/]/
49 };
50
51 function stateStack(indent, type, prev) { // represents a state stack object
52 this.indent = indent;
53 this.type = type;
54 this.prev = prev;
55 }
56
57 function pushStack(state, indent, type) {
58 state.indentStack = new stateStack(indent, type, state.indentStack);
59 }
60
61 function popStack(state) {
62 state.indentStack = state.indentStack.prev;
63 }
64
65 function isNumber(ch, stream){
66 // hex
67 if ( ch === '0' && 'x' == stream.peek().toLowerCase() ) {
68 stream.eat('x');
69 stream.eatWhile(tests.hex);
70 return true;
71 }
72
73 // leading sign
74 if ( ch == '+' || ch == '-' ) {
75 stream.eat(tests.sign);
76 ch = stream.next();
77 }
78
79 if ( tests.digit.test(ch) ) {
80 stream.eat(ch);
81 stream.eatWhile(tests.digit);
82
83 if ( '.' == stream.peek() ) {
84 stream.eat('.');
85 stream.eatWhile(tests.digit);
86 }
87
88 if ( 'e' == stream.peek().toLowerCase() ) {
89 stream.eat(tests.exponent);
90 stream.eat(tests.sign);
91 stream.eatWhile(tests.digit);
92 }
93
94 return true;
95 }
96
97 return false;
98 }
99
100 return {
101 startState: function () {
102 return {
103 indentStack: null,
104 indentation: 0,
105 mode: false
106 };
107 },
108
109 token: function (stream, state) {
110 if (state.indentStack == null && stream.sol()) {
111 // update indentation, but only if indentStack is empty
112 state.indentation = stream.indentation();
113 }
114
115 // skip spaces
116 if (stream.eatSpace()) {
117 return null;
118 }
119 var returnType = null;
120
121 switch(state.mode){
122 case "string": // multi-line string parsing mode
123 var next, escaped = false;
124 while ((next = stream.next()) != null) {
125 if (next == "\"" && !escaped) {
126
127 state.mode = false;
128 break;
129 }
130 escaped = !escaped && next == "\\";
131 }
132 returnType = STRING; // continue on in string mode
133 break;
134 default: // default parsing mode
135 var ch = stream.next();
136
137 if (ch == "\"") {
138 state.mode = "string";
139 returnType = STRING;
140 } else if (ch == "'" && !( tests.digit_or_colon.test(stream.peek()) )) {
141 returnType = ATOM;
142 } else if (ch == ";") { // comment
143 stream.skipToEnd(); // rest of the line is a comment
144 returnType = COMMENT;
145 } else if (isNumber(ch,stream)){
146 returnType = NUMBER;
147 } else if (ch == "(" || ch == "[") {
148 var keyWord = ''; var indentTemp = stream.column();
149 /**
150 Either
151 (indent-word ..
152 (non-indent-word ..
153 (;something else, bracket, etc.
154 */
155
156 if (ch == "(") while ((letter = stream.eat(tests.keyword_char)) != null) {
157 keyWord += letter;
158 }
159
160 if (keyWord.length > 0 && indentKeys.propertyIsEnumerable(keyWord)) { // indent-word
161 pushStack(state, indentTemp + INDENT_WORD_SKIP, ch);
162 } else { // non-indent word
163 // we continue eating the spaces
164 stream.eatSpace();
165 if (stream.eol() || stream.peek() == ";") {
166 // nothing significant after
167 // we restart indentation 1 space after
168 pushStack(state, indentTemp + 1, ch);
169 } else {
170 pushStack(state, indentTemp + stream.current().length, ch); // else we match
171 }
172 }
173 stream.backUp(stream.current().length - 1); // undo all the eating
174
175 returnType = BRACKET;
176 } else if (ch == ")" || ch == "]") {
177 returnType = BRACKET;
178 if (state.indentStack != null && state.indentStack.type == (ch == ")" ? "(" : "[")) {
179 popStack(state);
180 }
181 } else if ( ch == ":" ) {
182 stream.eatWhile(tests.lang_keyword);
183 return ATOM;
184 } else {
185 stream.eatWhile(tests.basic);
186
187 if (keywords && keywords.propertyIsEnumerable(stream.current())) {
188 returnType = KEYWORD;
189 } else if (builtins && builtins.propertyIsEnumerable(stream.current())) {
190 returnType = BUILTIN;
191 } else if (atoms && atoms.propertyIsEnumerable(stream.current())) {
192 returnType = ATOM;
193 } else returnType = null;
194 }
195 }
196
197 return returnType;
198 },
199
200 indent: function (state, textAfter) {
201 if (state.indentStack == null) return state.indentation;
202 return state.indentStack.indent;
203 }
204 };
205 });
206
207 CodeMirror.defineMIME("text/x-clojure", "clojure");
@@ -0,0 +1,66
1 <!doctype html>
2 <html>
3 <head>
4 <title>CodeMirror: Clojure mode</title>
5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 <script src="../../lib/codemirror.js"></script>
7 <script src="clojure.js"></script>
8 <style>.CodeMirror {background: #f8f8f8;}</style>
9 <link rel="stylesheet" href="../../doc/docs.css">
10 </head>
11 <body>
12 <h1>CodeMirror: Clojure mode</h1>
13 <form><textarea id="code" name="code">
14 ; Conway's Game of Life, based on the work of:
15 ;; Laurent Petit https://gist.github.com/1200343
16 ;; Christophe Grand http://clj-me.cgrand.net/2011/08/19/conways-game-of-life
17
18 (ns ^{:doc "Conway's Game of Life."}
19 game-of-life)
20
21 ;; Core game of life's algorithm functions
22
23 (defn neighbours
24 "Given a cell's coordinates, returns the coordinates of its neighbours."
25 [[x y]]
26 (for [dx [-1 0 1] dy (if (zero? dx) [-1 1] [-1 0 1])]
27 [(+ dx x) (+ dy y)]))
28
29 (defn step
30 "Given a set of living cells, computes the new set of living cells."
31 [cells]
32 (set (for [[cell n] (frequencies (mapcat neighbours cells))
33 :when (or (= n 3) (and (= n 2) (cells cell)))]
34 cell)))
35
36 ;; Utility methods for displaying game on a text terminal
37
38 (defn print-board
39 "Prints a board on *out*, representing a step in the game."
40 [board w h]
41 (doseq [x (range (inc w)) y (range (inc h))]
42 (if (= y 0) (print "\n"))
43 (print (if (board [x y]) "[X]" " . "))))
44
45 (defn display-grids
46 "Prints a squence of boards on *out*, representing several steps."
47 [grids w h]
48 (doseq [board grids]
49 (print-board board w h)
50 (print "\n")))
51
52 ;; Launches an example board
53
54 (def
55 ^{:doc "board represents the initial set of living cells"}
56 board #{[2 1] [2 2] [2 3]})
57
58 (display-grids (take 3 (iterate step board)) 5 5) </textarea></form>
59 <script>
60 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {});
61 </script>
62
63 <p><strong>MIME types defined:</strong> <code>text/x-clojure</code>.</p>
64
65 </body>
66 </html>
@@ -0,0 +1,22
1 The MIT License
2
3 Copyright (c) 2011 Jeff Pickhardt
4 Modified from the Python CodeMirror mode, Copyright (c) 2010 Timothy Farrell
5
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12
13 The above copyright notice and this permission notice shall be included in
14 all copies or substantial portions of the Software.
15
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 THE SOFTWARE. No newline at end of file
@@ -0,0 +1,346
1 /**
2 * Link to the project's GitHub page:
3 * https://github.com/pickhardt/coffeescript-codemirror-mode
4 */
5 CodeMirror.defineMode('coffeescript', function(conf) {
6 var ERRORCLASS = 'error';
7
8 function wordRegexp(words) {
9 return new RegExp("^((" + words.join(")|(") + "))\\b");
10 }
11
12 var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\?]");
13 var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\},:`=;\\.]');
14 var doubleOperators = new RegExp("^((\->)|(\=>)|(\\+\\+)|(\\+\\=)|(\\-\\-)|(\\-\\=)|(\\*\\*)|(\\*\\=)|(\\/\\/)|(\\/\\=)|(==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//))");
15 var doubleDelimiters = new RegExp("^((\\.\\.)|(\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
16 var tripleDelimiters = new RegExp("^((\\.\\.\\.)|(//=)|(>>=)|(<<=)|(\\*\\*=))");
17 var identifiers = new RegExp("^[_A-Za-z$][_A-Za-z$0-9]*");
18 var properties = new RegExp("^(@|this\.)[_A-Za-z$][_A-Za-z$0-9]*");
19
20 var wordOperators = wordRegexp(['and', 'or', 'not',
21 'is', 'isnt', 'in',
22 'instanceof', 'typeof']);
23 var indentKeywords = ['for', 'while', 'loop', 'if', 'unless', 'else',
24 'switch', 'try', 'catch', 'finally', 'class'];
25 var commonKeywords = ['break', 'by', 'continue', 'debugger', 'delete',
26 'do', 'in', 'of', 'new', 'return', 'then',
27 'this', 'throw', 'when', 'until'];
28
29 var keywords = wordRegexp(indentKeywords.concat(commonKeywords));
30
31 indentKeywords = wordRegexp(indentKeywords);
32
33
34 var stringPrefixes = new RegExp("^('{3}|\"{3}|['\"])");
35 var regexPrefixes = new RegExp("^(/{3}|/)");
36 var commonConstants = ['Infinity', 'NaN', 'undefined', 'null', 'true', 'false', 'on', 'off', 'yes', 'no'];
37 var constants = wordRegexp(commonConstants);
38
39 // Tokenizers
40 function tokenBase(stream, state) {
41 // Handle scope changes
42 if (stream.sol()) {
43 var scopeOffset = state.scopes[0].offset;
44 if (stream.eatSpace()) {
45 var lineOffset = stream.indentation();
46 if (lineOffset > scopeOffset) {
47 return 'indent';
48 } else if (lineOffset < scopeOffset) {
49 return 'dedent';
50 }
51 return null;
52 } else {
53 if (scopeOffset > 0) {
54 dedent(stream, state);
55 }
56 }
57 }
58 if (stream.eatSpace()) {
59 return null;
60 }
61
62 var ch = stream.peek();
63
64 // Handle docco title comment (single line)
65 if (stream.match("####")) {
66 stream.skipToEnd();
67 return 'comment';
68 }
69
70 // Handle multi line comments
71 if (stream.match("###")) {
72 state.tokenize = longComment;
73 return state.tokenize(stream, state);
74 }
75
76 // Single line comment
77 if (ch === '#') {
78 stream.skipToEnd();
79 return 'comment';
80 }
81
82 // Handle number literals
83 if (stream.match(/^-?[0-9\.]/, false)) {
84 var floatLiteral = false;
85 // Floats
86 if (stream.match(/^-?\d*\.\d+(e[\+\-]?\d+)?/i)) {
87 floatLiteral = true;
88 }
89 if (stream.match(/^-?\d+\.\d*/)) {
90 floatLiteral = true;
91 }
92 if (stream.match(/^-?\.\d+/)) {
93 floatLiteral = true;
94 }
95
96 if (floatLiteral) {
97 // prevent from getting extra . on 1..
98 if (stream.peek() == "."){
99 stream.backUp(1);
100 }
101 return 'number';
102 }
103 // Integers
104 var intLiteral = false;
105 // Hex
106 if (stream.match(/^-?0x[0-9a-f]+/i)) {
107 intLiteral = true;
108 }
109 // Decimal
110 if (stream.match(/^-?[1-9]\d*(e[\+\-]?\d+)?/)) {
111 intLiteral = true;
112 }
113 // Zero by itself with no other piece of number.
114 if (stream.match(/^-?0(?![\dx])/i)) {
115 intLiteral = true;
116 }
117 if (intLiteral) {
118 return 'number';
119 }
120 }
121
122 // Handle strings
123 if (stream.match(stringPrefixes)) {
124 state.tokenize = tokenFactory(stream.current(), 'string');
125 return state.tokenize(stream, state);
126 }
127 // Handle regex literals
128 if (stream.match(regexPrefixes)) {
129 if (stream.current() != '/' || stream.match(/^.*\//, false)) { // prevent highlight of division
130 state.tokenize = tokenFactory(stream.current(), 'string-2');
131 return state.tokenize(stream, state);
132 } else {
133 stream.backUp(1);
134 }
135 }
136
137 // Handle operators and delimiters
138 if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) {
139 return 'punctuation';
140 }
141 if (stream.match(doubleOperators)
142 || stream.match(singleOperators)
143 || stream.match(wordOperators)) {
144 return 'operator';
145 }
146 if (stream.match(singleDelimiters)) {
147 return 'punctuation';
148 }
149
150 if (stream.match(constants)) {
151 return 'atom';
152 }
153
154 if (stream.match(keywords)) {
155 return 'keyword';
156 }
157
158 if (stream.match(identifiers)) {
159 return 'variable';
160 }
161
162 if (stream.match(properties)) {
163 return 'property';
164 }
165
166 // Handle non-detected items
167 stream.next();
168 return ERRORCLASS;
169 }
170
171 function tokenFactory(delimiter, outclass) {
172 var singleline = delimiter.length == 1;
173 return function tokenString(stream, state) {
174 while (!stream.eol()) {
175 stream.eatWhile(/[^'"\/\\]/);
176 if (stream.eat('\\')) {
177 stream.next();
178 if (singleline && stream.eol()) {
179 return outclass;
180 }
181 } else if (stream.match(delimiter)) {
182 state.tokenize = tokenBase;
183 return outclass;
184 } else {
185 stream.eat(/['"\/]/);
186 }
187 }
188 if (singleline) {
189 if (conf.mode.singleLineStringErrors) {
190 outclass = ERRORCLASS
191 } else {
192 state.tokenize = tokenBase;
193 }
194 }
195 return outclass;
196 };
197 }
198
199 function longComment(stream, state) {
200 while (!stream.eol()) {
201 stream.eatWhile(/[^#]/);
202 if (stream.match("###")) {
203 state.tokenize = tokenBase;
204 break;
205 }
206 stream.eatWhile("#");
207 }
208 return "comment"
209 }
210
211 function indent(stream, state, type) {
212 type = type || 'coffee';
213 var indentUnit = 0;
214 if (type === 'coffee') {
215 for (var i = 0; i < state.scopes.length; i++) {
216 if (state.scopes[i].type === 'coffee') {
217 indentUnit = state.scopes[i].offset + conf.indentUnit;
218 break;
219 }
220 }
221 } else {
222 indentUnit = stream.column() + stream.current().length;
223 }
224 state.scopes.unshift({
225 offset: indentUnit,
226 type: type
227 });
228 }
229
230 function dedent(stream, state) {
231 if (state.scopes.length == 1) return;
232 if (state.scopes[0].type === 'coffee') {
233 var _indent = stream.indentation();
234 var _indent_index = -1;
235 for (var i = 0; i < state.scopes.length; ++i) {
236 if (_indent === state.scopes[i].offset) {
237 _indent_index = i;
238 break;
239 }
240 }
241 if (_indent_index === -1) {
242 return true;
243 }
244 while (state.scopes[0].offset !== _indent) {
245 state.scopes.shift();
246 }
247 return false
248 } else {
249 state.scopes.shift();
250 return false;
251 }
252 }
253
254 function tokenLexer(stream, state) {
255 var style = state.tokenize(stream, state);
256 var current = stream.current();
257
258 // Handle '.' connected identifiers
259 if (current === '.') {
260 style = state.tokenize(stream, state);
261 current = stream.current();
262 if (style === 'variable') {
263 return 'variable';
264 } else {
265 return ERRORCLASS;
266 }
267 }
268
269 // Handle scope changes.
270 if (current === 'return') {
271 state.dedent += 1;
272 }
273 if (((current === '->' || current === '=>') &&
274 !state.lambda &&
275 state.scopes[0].type == 'coffee' &&
276 stream.peek() === '')
277 || style === 'indent') {
278 indent(stream, state);
279 }
280 var delimiter_index = '[({'.indexOf(current);
281 if (delimiter_index !== -1) {
282 indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1));
283 }
284 if (indentKeywords.exec(current)){
285 indent(stream, state);
286 }
287 if (current == 'then'){
288 dedent(stream, state);
289 }
290
291
292 if (style === 'dedent') {
293 if (dedent(stream, state)) {
294 return ERRORCLASS;
295 }
296 }
297 delimiter_index = '])}'.indexOf(current);
298 if (delimiter_index !== -1) {
299 if (dedent(stream, state)) {
300 return ERRORCLASS;
301 }
302 }
303 if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'coffee') {
304 if (state.scopes.length > 1) state.scopes.shift();
305 state.dedent -= 1;
306 }
307
308 return style;
309 }
310
311 var external = {
312 startState: function(basecolumn) {
313 return {
314 tokenize: tokenBase,
315 scopes: [{offset:basecolumn || 0, type:'coffee'}],
316 lastToken: null,
317 lambda: false,
318 dedent: 0
319 };
320 },
321
322 token: function(stream, state) {
323 var style = tokenLexer(stream, state);
324
325 state.lastToken = {style:style, content: stream.current()};
326
327 if (stream.eol() && stream.lambda) {
328 state.lambda = false;
329 }
330
331 return style;
332 },
333
334 indent: function(state, textAfter) {
335 if (state.tokenize != tokenBase) {
336 return 0;
337 }
338
339 return state.scopes[0].offset;
340 }
341
342 };
343 return external;
344 });
345
346 CodeMirror.defineMIME('text/x-coffeescript', 'coffeescript');
This diff has been collapsed as it changes many lines, (727 lines changed) Show them Hide them
@@ -0,0 +1,727
1 <!doctype html>
2 <html>
3 <head>
4 <title>CodeMirror: CoffeeScript mode</title>
5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 <script src="../../lib/codemirror.js"></script>
7 <script src="coffeescript.js"></script>
8 <style>.CodeMirror {border-top: 1px solid silver; border-bottom: 1px solid silver;}</style>
9 <link rel="stylesheet" href="../../doc/docs.css">
10 </head>
11 <body>
12 <h1>CodeMirror: CoffeeScript mode</h1>
13 <form><textarea id="code" name="code">
14 # CoffeeScript mode for CodeMirror
15 # Copyright (c) 2011 Jeff Pickhardt, released under
16 # the MIT License.
17 #
18 # Modified from the Python CodeMirror mode, which also is
19 # under the MIT License Copyright (c) 2010 Timothy Farrell.
20 #
21 # The following script, Underscore.coffee, is used to
22 # demonstrate CoffeeScript mode for CodeMirror.
23 #
24 # To download CoffeeScript mode for CodeMirror, go to:
25 # https://github.com/pickhardt/coffeescript-codemirror-mode
26
27 # **Underscore.coffee
28 # (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.**
29 # Underscore is freely distributable under the terms of the
30 # [MIT license](http://en.wikipedia.org/wiki/MIT_License).
31 # Portions of Underscore are inspired by or borrowed from
32 # [Prototype.js](http://prototypejs.org/api), Oliver Steele's
33 # [Functional](http://osteele.com), and John Resig's
34 # [Micro-Templating](http://ejohn.org).
35 # For all details and documentation:
36 # http://documentcloud.github.com/underscore/
37
38
39 # Baseline setup
40 # --------------
41
42 # Establish the root object, `window` in the browser, or `global` on the server.
43 root = this
44
45
46 # Save the previous value of the `_` variable.
47 previousUnderscore = root._
48
49 ### Multiline
50 comment
51 ###
52
53 # Establish the object that gets thrown to break out of a loop iteration.
54 # `StopIteration` is SOP on Mozilla.
55 breaker = if typeof(StopIteration) is 'undefined' then '__break__' else StopIteration
56
57
58 #### Docco style single line comment (title)
59
60
61 # Helper function to escape **RegExp** contents, because JS doesn't have one.
62 escapeRegExp = (string) -> string.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1')
63
64
65 # Save bytes in the minified (but not gzipped) version:
66 ArrayProto = Array.prototype
67 ObjProto = Object.prototype
68
69
70 # Create quick reference variables for speed access to core prototypes.
71 slice = ArrayProto.slice
72 unshift = ArrayProto.unshift
73 toString = ObjProto.toString
74 hasOwnProperty = ObjProto.hasOwnProperty
75 propertyIsEnumerable = ObjProto.propertyIsEnumerable
76
77
78 # All **ECMA5** native implementations we hope to use are declared here.
79 nativeForEach = ArrayProto.forEach
80 nativeMap = ArrayProto.map
81 nativeReduce = ArrayProto.reduce
82 nativeReduceRight = ArrayProto.reduceRight
83 nativeFilter = ArrayProto.filter
84 nativeEvery = ArrayProto.every
85 nativeSome = ArrayProto.some
86 nativeIndexOf = ArrayProto.indexOf
87 nativeLastIndexOf = ArrayProto.lastIndexOf
88 nativeIsArray = Array.isArray
89 nativeKeys = Object.keys
90
91
92 # Create a safe reference to the Underscore object for use below.
93 _ = (obj) -> new wrapper(obj)
94
95
96 # Export the Underscore object for **CommonJS**.
97 if typeof(exports) != 'undefined' then exports._ = _
98
99
100 # Export Underscore to global scope.
101 root._ = _
102
103
104 # Current version.
105 _.VERSION = '1.1.0'
106
107
108 # Collection Functions
109 # --------------------
110
111 # The cornerstone, an **each** implementation.
112 # Handles objects implementing **forEach**, arrays, and raw objects.
113 _.each = (obj, iterator, context) ->
114 try
115 if nativeForEach and obj.forEach is nativeForEach
116 obj.forEach iterator, context
117 else if _.isNumber obj.length
118 iterator.call context, obj[i], i, obj for i in [0...obj.length]
119 else
120 iterator.call context, val, key, obj for own key, val of obj
121 catch e
122 throw e if e isnt breaker
123 obj
124
125
126 # Return the results of applying the iterator to each element. Use JavaScript
127 # 1.6's version of **map**, if possible.
128 _.map = (obj, iterator, context) ->
129 return obj.map(iterator, context) if nativeMap and obj.map is nativeMap
130 results = []
131 _.each obj, (value, index, list) ->
132 results.push iterator.call context, value, index, list
133 results
134
135
136 # **Reduce** builds up a single result from a list of values. Also known as
137 # **inject**, or **foldl**. Uses JavaScript 1.8's version of **reduce**, if possible.
138 _.reduce = (obj, iterator, memo, context) ->
139 if nativeReduce and obj.reduce is nativeReduce
140 iterator = _.bind iterator, context if context
141 return obj.reduce iterator, memo
142 _.each obj, (value, index, list) ->
143 memo = iterator.call context, memo, value, index, list
144 memo
145
146
147 # The right-associative version of **reduce**, also known as **foldr**. Uses
148 # JavaScript 1.8's version of **reduceRight**, if available.
149 _.reduceRight = (obj, iterator, memo, context) ->
150 if nativeReduceRight and obj.reduceRight is nativeReduceRight
151 iterator = _.bind iterator, context if context
152 return obj.reduceRight iterator, memo
153 reversed = _.clone(_.toArray(obj)).reverse()
154 _.reduce reversed, iterator, memo, context
155
156
157 # Return the first value which passes a truth test.
158 _.detect = (obj, iterator, context) ->
159 result = null
160 _.each obj, (value, index, list) ->
161 if iterator.call context, value, index, list
162 result = value
163 _.breakLoop()
164 result
165
166
167 # Return all the elements that pass a truth test. Use JavaScript 1.6's
168 # **filter**, if it exists.
169 _.filter = (obj, iterator, context) ->
170 return obj.filter iterator, context if nativeFilter and obj.filter is nativeFilter
171 results = []
172 _.each obj, (value, index, list) ->
173 results.push value if iterator.call context, value, index, list
174 results
175
176
177 # Return all the elements for which a truth test fails.
178 _.reject = (obj, iterator, context) ->
179 results = []
180 _.each obj, (value, index, list) ->
181 results.push value if not iterator.call context, value, index, list
182 results
183
184
185 # Determine whether all of the elements match a truth test. Delegate to
186 # JavaScript 1.6's **every**, if it is present.
187 _.every = (obj, iterator, context) ->
188 iterator ||= _.identity
189 return obj.every iterator, context if nativeEvery and obj.every is nativeEvery
190 result = true
191 _.each obj, (value, index, list) ->
192 _.breakLoop() unless (result = result and iterator.call(context, value, index, list))
193 result
194
195
196 # Determine if at least one element in the object matches a truth test. Use
197 # JavaScript 1.6's **some**, if it exists.
198 _.some = (obj, iterator, context) ->
199 iterator ||= _.identity
200 return obj.some iterator, context if nativeSome and obj.some is nativeSome
201 result = false
202 _.each obj, (value, index, list) ->
203 _.breakLoop() if (result = iterator.call(context, value, index, list))
204 result
205
206
207 # Determine if a given value is included in the array or object,
208 # based on `===`.
209 _.include = (obj, target) ->
210 return _.indexOf(obj, target) isnt -1 if nativeIndexOf and obj.indexOf is nativeIndexOf
211 return true for own key, val of obj when val is target
212 false
213
214
215 # Invoke a method with arguments on every item in a collection.
216 _.invoke = (obj, method) ->
217 args = _.rest arguments, 2
218 (if method then val[method] else val).apply(val, args) for val in obj
219
220
221 # Convenience version of a common use case of **map**: fetching a property.
222 _.pluck = (obj, key) ->
223 _.map(obj, (val) -> val[key])
224
225
226 # Return the maximum item or (item-based computation).
227 _.max = (obj, iterator, context) ->
228 return Math.max.apply(Math, obj) if not iterator and _.isArray(obj)
229 result = computed: -Infinity
230 _.each obj, (value, index, list) ->
231 computed = if iterator then iterator.call(context, value, index, list) else value
232 computed >= result.computed and (result = {value: value, computed: computed})
233 result.value
234
235
236 # Return the minimum element (or element-based computation).
237 _.min = (obj, iterator, context) ->
238 return Math.min.apply(Math, obj) if not iterator and _.isArray(obj)
239 result = computed: Infinity
240 _.each obj, (value, index, list) ->
241 computed = if iterator then iterator.call(context, value, index, list) else value
242 computed < result.computed and (result = {value: value, computed: computed})
243 result.value
244
245
246 # Sort the object's values by a criterion produced by an iterator.
247 _.sortBy = (obj, iterator, context) ->
248 _.pluck(((_.map obj, (value, index, list) ->
249 {value: value, criteria: iterator.call(context, value, index, list)}
250 ).sort((left, right) ->
251 a = left.criteria; b = right.criteria
252 if a < b then -1 else if a > b then 1 else 0
253 )), 'value')
254
255
256 # Use a comparator function to figure out at what index an object should
257 # be inserted so as to maintain order. Uses binary search.
258 _.sortedIndex = (array, obj, iterator) ->
259 iterator ||= _.identity
260 low = 0
261 high = array.length
262 while low < high
263 mid = (low + high) >> 1
264 if iterator(array[mid]) < iterator(obj) then low = mid + 1 else high = mid
265 low
266
267
268 # Convert anything iterable into a real, live array.
269 _.toArray = (iterable) ->
270 return [] if (!iterable)
271 return iterable.toArray() if (iterable.toArray)
272 return iterable if (_.isArray(iterable))
273 return slice.call(iterable) if (_.isArguments(iterable))
274 _.values(iterable)
275
276
277 # Return the number of elements in an object.
278 _.size = (obj) -> _.toArray(obj).length
279
280
281 # Array Functions
282 # ---------------
283
284 # Get the first element of an array. Passing `n` will return the first N
285 # values in the array. Aliased as **head**. The `guard` check allows it to work
286 # with **map**.
287 _.first = (array, n, guard) ->
288 if n and not guard then slice.call(array, 0, n) else array[0]
289
290
291 # Returns everything but the first entry of the array. Aliased as **tail**.
292 # Especially useful on the arguments object. Passing an `index` will return
293 # the rest of the values in the array from that index onward. The `guard`
294 # check allows it to work with **map**.
295 _.rest = (array, index, guard) ->
296 slice.call(array, if _.isUndefined(index) or guard then 1 else index)
297
298
299 # Get the last element of an array.
300 _.last = (array) -> array[array.length - 1]
301
302
303 # Trim out all falsy values from an array.
304 _.compact = (array) -> item for item in array when item
305
306
307 # Return a completely flattened version of an array.
308 _.flatten = (array) ->
309 _.reduce array, (memo, value) ->
310 return memo.concat(_.flatten(value)) if _.isArray value
311 memo.push value
312 memo
313 , []
314
315
316 # Return a version of the array that does not contain the specified value(s).
317 _.without = (array) ->
318 values = _.rest arguments
319 val for val in _.toArray(array) when not _.include values, val
320
321
322 # Produce a duplicate-free version of the array. If the array has already
323 # been sorted, you have the option of using a faster algorithm.
324 _.uniq = (array, isSorted) ->
325 memo = []
326 for el, i in _.toArray array
327 memo.push el if i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el))
328 memo
329
330
331 # Produce an array that contains every item shared between all the
332 # passed-in arrays.
333 _.intersect = (array) ->
334 rest = _.rest arguments
335 _.select _.uniq(array), (item) ->
336 _.all rest, (other) ->
337 _.indexOf(other, item) >= 0
338
339
340 # Zip together multiple lists into a single array -- elements that share
341 # an index go together.
342 _.zip = ->
343 length = _.max _.pluck arguments, 'length'
344 results = new Array length
345 for i in [0...length]
346 results[i] = _.pluck arguments, String i
347 results
348
349
350 # If the browser doesn't supply us with **indexOf** (I'm looking at you, MSIE),
351 # we need this function. Return the position of the first occurrence of an
352 # item in an array, or -1 if the item is not included in the array.
353 _.indexOf = (array, item) ->
354 return array.indexOf item if nativeIndexOf and array.indexOf is nativeIndexOf
355 i = 0; l = array.length
356 while l - i
357 if array[i] is item then return i else i++
358 -1
359
360
361 # Provide JavaScript 1.6's **lastIndexOf**, delegating to the native function,
362 # if possible.
363 _.lastIndexOf = (array, item) ->
364 return array.lastIndexOf(item) if nativeLastIndexOf and array.lastIndexOf is nativeLastIndexOf
365 i = array.length
366 while i
367 if array[i] is item then return i else i--
368 -1
369
370
371 # Generate an integer Array containing an arithmetic progression. A port of
372 # [the native Python **range** function](http://docs.python.org/library/functions.html#range).
373 _.range = (start, stop, step) ->
374 a = arguments
375 solo = a.length <= 1
376 i = start = if solo then 0 else a[0]
377 stop = if solo then a[0] else a[1]
378 step = a[2] or 1
379 len = Math.ceil((stop - start) / step)
380 return [] if len <= 0
381 range = new Array len
382 idx = 0
383 loop
384 return range if (if step > 0 then i - stop else stop - i) >= 0
385 range[idx] = i
386 idx++
387 i+= step
388
389
390 # Function Functions
391 # ------------------
392
393 # Create a function bound to a given object (assigning `this`, and arguments,
394 # optionally). Binding with arguments is also known as **curry**.
395 _.bind = (func, obj) ->
396 args = _.rest arguments, 2
397 -> func.apply obj or root, args.concat arguments
398
399
400 # Bind all of an object's methods to that object. Useful for ensuring that
401 # all callbacks defined on an object belong to it.
402 _.bindAll = (obj) ->
403 funcs = if arguments.length > 1 then _.rest(arguments) else _.functions(obj)
404 _.each funcs, (f) -> obj[f] = _.bind obj[f], obj
405 obj
406
407
408 # Delays a function for the given number of milliseconds, and then calls
409 # it with the arguments supplied.
410 _.delay = (func, wait) ->
411 args = _.rest arguments, 2
412 setTimeout((-> func.apply(func, args)), wait)
413
414
415 # Memoize an expensive function by storing its results.
416 _.memoize = (func, hasher) ->
417 memo = {}
418 hasher or= _.identity
419 ->
420 key = hasher.apply this, arguments
421 return memo[key] if key of memo
422 memo[key] = func.apply this, arguments
423
424
425 # Defers a function, scheduling it to run after the current call stack has
426 # cleared.
427 _.defer = (func) ->
428 _.delay.apply _, [func, 1].concat _.rest arguments
429
430
431 # Returns the first function passed as an argument to the second,
432 # allowing you to adjust arguments, run code before and after, and
433 # conditionally execute the original function.
434 _.wrap = (func, wrapper) ->
435 -> wrapper.apply wrapper, [func].concat arguments
436
437
438 # Returns a function that is the composition of a list of functions, each
439 # consuming the return value of the function that follows.
440 _.compose = ->
441 funcs = arguments
442 ->
443 args = arguments
444 for i in [funcs.length - 1..0] by -1
445 args = [funcs[i].apply(this, args)]
446 args[0]
447
448
449 # Object Functions
450 # ----------------
451
452 # Retrieve the names of an object's properties.
453 _.keys = nativeKeys or (obj) ->
454 return _.range 0, obj.length if _.isArray(obj)
455 key for key, val of obj
456
457
458 # Retrieve the values of an object's properties.
459 _.values = (obj) ->
460 _.map obj, _.identity
461
462
463 # Return a sorted list of the function names available in Underscore.
464 _.functions = (obj) ->
465 _.filter(_.keys(obj), (key) -> _.isFunction(obj[key])).sort()
466
467
468 # Extend a given object with all of the properties in a source object.
469 _.extend = (obj) ->
470 for source in _.rest(arguments)
471 obj[key] = val for key, val of source
472 obj
473
474
475 # Create a (shallow-cloned) duplicate of an object.
476 _.clone = (obj) ->
477 return obj.slice 0 if _.isArray obj
478 _.extend {}, obj
479
480
481 # Invokes interceptor with the obj, and then returns obj.
482 # The primary purpose of this method is to "tap into" a method chain,
483 # in order to perform operations on intermediate results within
484 the chain.
485 _.tap = (obj, interceptor) ->
486 interceptor obj
487 obj
488
489
490 # Perform a deep comparison to check if two objects are equal.
491 _.isEqual = (a, b) ->
492 # Check object identity.
493 return true if a is b
494 # Different types?
495 atype = typeof(a); btype = typeof(b)
496 return false if atype isnt btype
497 # Basic equality test (watch out for coercions).
498 return true if `a == b`
499 # One is falsy and the other truthy.
500 return false if (!a and b) or (a and !b)
501 # One of them implements an `isEqual()`?
502 return a.isEqual(b) if a.isEqual
503 # Check dates' integer values.
504 return a.getTime() is b.getTime() if _.isDate(a) and _.isDate(b)
505 # Both are NaN?
506 return false if _.isNaN(a) and _.isNaN(b)
507 # Compare regular expressions.
508 if _.isRegExp(a) and _.isRegExp(b)
509 return a.source is b.source and
510 a.global is b.global and
511 a.ignoreCase is b.ignoreCase and
512 a.multiline is b.multiline
513 # If a is not an object by this point, we can't handle it.
514 return false if atype isnt 'object'
515 # Check for different array lengths before comparing contents.
516 return false if a.length and (a.length isnt b.length)
517 # Nothing else worked, deep compare the contents.
518 aKeys = _.keys(a); bKeys = _.keys(b)
519 # Different object sizes?
520 return false if aKeys.length isnt bKeys.length
521 # Recursive comparison of contents.
522 return false for key, val of a when !(key of b) or !_.isEqual(val, b[key])
523 true
524
525
526 # Is a given array or object empty?
527 _.isEmpty = (obj) ->
528 return obj.length is 0 if _.isArray(obj) or _.isString(obj)
529 return false for own key of obj
530 true
531
532
533 # Is a given value a DOM element?
534 _.isElement = (obj) -> obj and obj.nodeType is 1
535
536
537 # Is a given value an array?
538 _.isArray = nativeIsArray or (obj) -> !!(obj and obj.concat and obj.unshift and not obj.callee)
539
540
541 # Is a given variable an arguments object?
542 _.isArguments = (obj) -> obj and obj.callee
543
544
545 # Is the given value a function?
546 _.isFunction = (obj) -> !!(obj and obj.constructor and obj.call and obj.apply)
547
548
549 # Is the given value a string?
550 _.isString = (obj) -> !!(obj is '' or (obj and obj.charCodeAt and obj.substr))
551
552
553 # Is a given value a number?
554 _.isNumber = (obj) -> (obj is +obj) or toString.call(obj) is '[object Number]'
555
556
557 # Is a given value a boolean?
558 _.isBoolean = (obj) -> obj is true or obj is false
559
560
561 # Is a given value a Date?
562 _.isDate = (obj) -> !!(obj and obj.getTimezoneOffset and obj.setUTCFullYear)
563
564
565 # Is the given value a regular expression?
566 _.isRegExp = (obj) -> !!(obj and obj.exec and (obj.ignoreCase or obj.ignoreCase is false))
567
568
569 # Is the given value NaN -- this one is interesting. `NaN != NaN`, and
570 # `isNaN(undefined) == true`, so we make sure it's a number first.
571 _.isNaN = (obj) -> _.isNumber(obj) and window.isNaN(obj)
572
573
574 # Is a given value equal to null?
575 _.isNull = (obj) -> obj is null
576
577
578 # Is a given variable undefined?
579 _.isUndefined = (obj) -> typeof obj is 'undefined'
580
581
582 # Utility Functions
583 # -----------------
584
585 # Run Underscore.js in noConflict mode, returning the `_` variable to its
586 # previous owner. Returns a reference to the Underscore object.
587 _.noConflict = ->
588 root._ = previousUnderscore
589 this
590
591
592 # Keep the identity function around for default iterators.
593 _.identity = (value) -> value
594
595
596 # Run a function `n` times.
597 _.times = (n, iterator, context) ->
598 iterator.call context, i for i in [0...n]
599
600
601 # Break out of the middle of an iteration.
602 _.breakLoop = -> throw breaker
603
604
605 # Add your own custom functions to the Underscore object, ensuring that
606 # they're correctly added to the OOP wrapper as well.
607 _.mixin = (obj) ->
608 for name in _.functions(obj)
609 addToWrapper name, _[name] = obj[name]
610
611
612 # Generate a unique integer id (unique within the entire client session).
613 # Useful for temporary DOM ids.
614 idCounter = 0
615 _.uniqueId = (prefix) ->
616 (prefix or '') + idCounter++
617
618
619 # By default, Underscore uses **ERB**-style template delimiters, change the
620 # following template settings to use alternative delimiters.
621 _.templateSettings = {
622 start: '<%'
623 end: '%>'
624 interpolate: /<%=(.+?)%>/g
625 }
626
627
628 # JavaScript templating a-la **ERB**, pilfered from John Resig's
629 # *Secrets of the JavaScript Ninja*, page 83.
630 # Single-quote fix from Rick Strahl.
631 # With alterations for arbitrary delimiters, and to preserve whitespace.
632 _.template = (str, data) ->
633 c = _.templateSettings
634 endMatch = new RegExp("'(?=[^"+c.end.substr(0, 1)+"]*"+escapeRegExp(c.end)+")","g")
635 fn = new Function 'obj',
636 'var p=[],print=function(){p.push.apply(p,arguments);};' +
637 'with(obj||{}){p.push(\'' +
638 str.replace(/\r/g, '\\r')
639 .replace(/\n/g, '\\n')
640 .replace(/\t/g, '\\t')
641 .replace(endMatch,"���")
642 .split("'").join("\\'")
643 .split("���").join("'")
644 .replace(c.interpolate, "',$1,'")
645 .split(c.start).join("');")
646 .split(c.end).join("p.push('") +
647 "');}return p.join('');"
648 if data then fn(data) else fn
649
650
651 # Aliases
652 # -------
653
654 _.forEach = _.each
655 _.foldl = _.inject = _.reduce
656 _.foldr = _.reduceRight
657 _.select = _.filter
658 _.all = _.every
659 _.any = _.some
660 _.contains = _.include
661 _.head = _.first
662 _.tail = _.rest
663 _.methods = _.functions
664
665
666 # Setup the OOP Wrapper
667 # ---------------------
668
669 # If Underscore is called as a function, it returns a wrapped object that
670 # can be used OO-style. This wrapper holds altered versions of all the
671 # underscore functions. Wrapped objects may be chained.
672 wrapper = (obj) ->
673 this._wrapped = obj
674 this
675
676
677 # Helper function to continue chaining intermediate results.
678 result = (obj, chain) ->
679 if chain then _(obj).chain() else obj
680
681
682 # A method to easily add functions to the OOP wrapper.
683 addToWrapper = (name, func) ->
684 wrapper.prototype[name] = ->
685 args = _.toArray arguments
686 unshift.call args, this._wrapped
687 result func.apply(_, args), this._chain
688
689
690 # Add all ofthe Underscore functions to the wrapper object.
691 _.mixin _
692
693
694 # Add all mutator Array functions to the wrapper.
695 _.each ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], (name) ->
696 method = Array.prototype[name]
697 wrapper.prototype[name] = ->
698 method.apply(this._wrapped, arguments)
699 result(this._wrapped, this._chain)
700
701
702 # Add all accessor Array functions to the wrapper.
703 _.each ['concat', 'join', 'slice'], (name) ->
704 method = Array.prototype[name]
705 wrapper.prototype[name] = ->
706 result(method.apply(this._wrapped, arguments), this._chain)
707
708
709 # Start chaining a wrapped Underscore object.
710 wrapper::chain = ->
711 this._chain = true
712 this
713
714
715 # Extracts the result from a wrapped and chained object.
716 wrapper::value = -> this._wrapped
717 </textarea></form>
718 <script>
719 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {});
720 </script>
721
722 <p><strong>MIME types defined:</strong> <code>text/x-coffeescript</code>.</p>
723
724 <p>The CoffeeScript mode was written by Jeff Pickhardt (<a href="LICENSE">license</a>).</p>
725
726 </body>
727 </html>
@@ -0,0 +1,32
1 CodeMirror.defineMode("diff", function() {
2
3 var TOKEN_NAMES = {
4 '+': 'tag',
5 '-': 'string',
6 '@': 'meta'
7 };
8
9 return {
10 token: function(stream) {
11 var tw_pos = stream.string.search(/[\t ]+?$/);
12
13 if (!stream.sol() || tw_pos === 0) {
14 stream.skipToEnd();
15 return ("error " + (
16 TOKEN_NAMES[stream.string.charAt(0)] || '')).replace(/ $/, '');
17 }
18
19 var token_name = TOKEN_NAMES[stream.peek()] || stream.skipToEnd();
20
21 if (tw_pos === -1) {
22 stream.skipToEnd();
23 } else {
24 stream.pos = tw_pos;
25 }
26
27 return token_name;
28 }
29 };
30 });
31
32 CodeMirror.defineMIME("text/x-diff", "diff");
@@ -0,0 +1,104
1 <!doctype html>
2 <html>
3 <head>
4 <title>CodeMirror: Diff mode</title>
5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 <script src="../../lib/codemirror.js"></script>
7 <script src="diff.js"></script>
8 <style>
9 .CodeMirror {border-top: 1px solid #ddd; border-bottom: 1px solid #ddd;}
10 span.cm-meta {color: #a0b !important;}
11 span.cm-error { background-color: black; opacity: 0.4;}
12 span.cm-error.cm-string { background-color: red; }
13 span.cm-error.cm-tag { background-color: #2b2; }
14 </style>
15 <link rel="stylesheet" href="../../doc/docs.css">
16 </head>
17 <body>
18 <h1>CodeMirror: Diff mode</h1>
19 <form><textarea id="code" name="code">
20 diff --git a/index.html b/index.html
21 index c1d9156..7764744 100644
22 --- a/index.html
23 +++ b/index.html
24 @@ -95,7 +95,8 @@ StringStream.prototype = {
25 <script>
26 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
27 lineNumbers: true,
28 - autoMatchBrackets: true
29 + autoMatchBrackets: true,
30 + onGutterClick: function(x){console.log(x);}
31 });
32 </script>
33 </body>
34 diff --git a/lib/codemirror.js b/lib/codemirror.js
35 index 04646a9..9a39cc7 100644
36 --- a/lib/codemirror.js
37 +++ b/lib/codemirror.js
38 @@ -399,10 +399,16 @@ var CodeMirror = (function() {
39 }
40
41 function onMouseDown(e) {
42 - var start = posFromMouse(e), last = start;
43 + var start = posFromMouse(e), last = start, target = e.target();
44 if (!start) return;
45 setCursor(start.line, start.ch, false);
46 if (e.button() != 1) return;
47 + if (target.parentNode == gutter) {
48 + if (options.onGutterClick)
49 + options.onGutterClick(indexOf(gutter.childNodes, target) + showingFrom);
50 + return;
51 + }
52 +
53 if (!focused) onFocus();
54
55 e.stop();
56 @@ -808,7 +814,7 @@ var CodeMirror = (function() {
57 for (var i = showingFrom; i < showingTo; ++i) {
58 var marker = lines[i].gutterMarker;
59 if (marker) html.push('<div class="' + marker.style + '">' + htmlEscape(marker.text) + '</div>');
60 - else html.push("<div>" + (options.lineNumbers ? i + 1 : "\u00a0") + "</div>");
61 + else html.push("<div>" + (options.lineNumbers ? i + options.firstLineNumber : "\u00a0") + "</div>");
62 }
63 gutter.style.display = "none"; // TODO test whether this actually helps
64 gutter.innerHTML = html.join("");
65 @@ -1371,10 +1377,8 @@ var CodeMirror = (function() {
66 if (option == "parser") setParser(value);
67 else if (option === "lineNumbers") setLineNumbers(value);
68 else if (option === "gutter") setGutter(value);
69 - else if (option === "readOnly") options.readOnly = value;
70 - else if (option === "indentUnit") {options.indentUnit = indentUnit = value; setParser(options.parser);}
71 - else if (/^(?:enterMode|tabMode|indentWithTabs|readOnly|autoMatchBrackets|undoDepth)$/.test(option)) options[option] = value;
72 - else throw new Error("Can't set option " + option);
73 + else if (option === "indentUnit") {options.indentUnit = value; setParser(options.parser);}
74 + else options[option] = value;
75 },
76 cursorCoords: cursorCoords,
77 undo: operation(undo),
78 @@ -1402,7 +1406,8 @@ var CodeMirror = (function() {
79 replaceRange: operation(replaceRange),
80
81 operation: function(f){return operation(f)();},
82 - refresh: function(){updateDisplay([{from: 0, to: lines.length}]);}
83 + refresh: function(){updateDisplay([{from: 0, to: lines.length}]);},
84 + getInputField: function(){return input;}
85 };
86 return instance;
87 }
88 @@ -1420,6 +1425,7 @@ var CodeMirror = (function() {
89 readOnly: false,
90 onChange: null,
91 onCursorActivity: null,
92 + onGutterClick: null,
93 autoMatchBrackets: false,
94 workTime: 200,
95 workDelay: 300,
96 </textarea></form>
97 <script>
98 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {});
99 </script>
100
101 <p><strong>MIME types defined:</strong> <code>text/x-diff</code>.</p>
102
103 </body>
104 </html>
@@ -0,0 +1,203
1 CodeMirror.defineMode("ecl", function(config) {
2
3 function words(str) {
4 var obj = {}, words = str.split(" ");
5 for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
6 return obj;
7 }
8
9 function metaHook(stream, state) {
10 if (!state.startOfLine) return false;
11 stream.skipToEnd();
12 return "meta";
13 }
14
15 function tokenAtString(stream, state) {
16 var next;
17 while ((next = stream.next()) != null) {
18 if (next == '"' && !stream.eat('"')) {
19 state.tokenize = null;
20 break;
21 }
22 }
23 return "string";
24 }
25
26 var indentUnit = config.indentUnit;
27 var keyword = words("abs acos allnodes ascii asin asstring atan atan2 ave case choose choosen choosesets clustersize combine correlation cos cosh count covariance cron dataset dedup define denormalize distribute distributed distribution ebcdic enth error evaluate event eventextra eventname exists exp failcode failmessage fetch fromunicode getisvalid global graph group hash hash32 hash64 hashcrc hashmd5 having if index intformat isvalid iterate join keyunicode length library limit ln local log loop map matched matchlength matchposition matchtext matchunicode max merge mergejoin min nolocal nonempty normalize parse pipe power preload process project pull random range rank ranked realformat recordof regexfind regexreplace regroup rejected rollup round roundup row rowdiff sample set sin sinh sizeof soapcall sort sorted sqrt stepped stored sum table tan tanh thisnode topn tounicode transfer trim truncate typeof ungroup unicodeorder variance which workunit xmldecode xmlencode xmltext xmlunicode");
28 var variable = words("apply assert build buildindex evaluate fail keydiff keypatch loadxml nothor notify output parallel sequential soapcall wait");
29 var variable_2 = words("__compressed__ all and any as atmost before beginc++ best between case const counter csv descend encrypt end endc++ endmacro except exclusive expire export extend false few first flat from full function group header heading hole ifblock import in interface joined keep keyed last left limit load local locale lookup macro many maxcount maxlength min skew module named nocase noroot noscan nosort not of only opt or outer overwrite packed partition penalty physicallength pipe quote record relationship repeat return right scan self separator service shared skew skip sql store terminator thor threshold token transform trim true type unicodeorder unsorted validate virtual whole wild within xml xpath");
30 var variable_3 = words("ascii big_endian boolean data decimal ebcdic integer pattern qstring real record rule set of string token udecimal unicode unsigned varstring varunicode");
31 var builtin = words("checkpoint deprecated failcode failmessage failure global independent onwarning persist priority recovery stored success wait when");
32 var blockKeywords = words("catch class do else finally for if switch try while");
33 var atoms = words("true false null");
34 var hooks = {"#": metaHook};
35 var multiLineStrings;
36 var isOperatorChar = /[+\-*&%=<>!?|\/]/;
37
38 var curPunc;
39
40 function tokenBase(stream, state) {
41 var ch = stream.next();
42 if (hooks[ch]) {
43 var result = hooks[ch](stream, state);
44 if (result !== false) return result;
45 }
46 if (ch == '"' || ch == "'") {
47 state.tokenize = tokenString(ch);
48 return state.tokenize(stream, state);
49 }
50 if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
51 curPunc = ch;
52 return null
53 }
54 if (/\d/.test(ch)) {
55 stream.eatWhile(/[\w\.]/);
56 return "number";
57 }
58 if (ch == "/") {
59 if (stream.eat("*")) {
60 state.tokenize = tokenComment;
61 return tokenComment(stream, state);
62 }
63 if (stream.eat("/")) {
64 stream.skipToEnd();
65 return "comment";
66 }
67 }
68 if (isOperatorChar.test(ch)) {
69 stream.eatWhile(isOperatorChar);
70 return "operator";
71 }
72 stream.eatWhile(/[\w\$_]/);
73 var cur = stream.current().toLowerCase();
74 if (keyword.propertyIsEnumerable(cur)) {
75 if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
76 return "keyword";
77 } else if (variable.propertyIsEnumerable(cur)) {
78 if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
79 return "variable";
80 } else if (variable_2.propertyIsEnumerable(cur)) {
81 if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
82 return "variable-2";
83 } else if (variable_3.propertyIsEnumerable(cur)) {
84 if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
85 return "variable-3";
86 } else if (builtin.propertyIsEnumerable(cur)) {
87 if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
88 return "builtin";
89 } else { //Data types are of from KEYWORD##
90 var i = cur.length - 1;
91 while(i >= 0 && (!isNaN(cur[i]) || cur[i] == '_'))
92 --i;
93
94 if (i > 0) {
95 var cur2 = cur.substr(0, i + 1);
96 if (variable_3.propertyIsEnumerable(cur2)) {
97 if (blockKeywords.propertyIsEnumerable(cur2)) curPunc = "newstatement";
98 return "variable-3";
99 }
100 }
101 }
102 if (atoms.propertyIsEnumerable(cur)) return "atom";
103 return null;
104 }
105
106 function tokenString(quote) {
107 return function(stream, state) {
108 var escaped = false, next, end = false;
109 while ((next = stream.next()) != null) {
110 if (next == quote && !escaped) {end = true; break;}
111 escaped = !escaped && next == "\\";
112 }
113 if (end || !(escaped || multiLineStrings))
114 state.tokenize = tokenBase;
115 return "string";
116 };
117 }
118
119 function tokenComment(stream, state) {
120 var maybeEnd = false, ch;
121 while (ch = stream.next()) {
122 if (ch == "/" && maybeEnd) {
123 state.tokenize = tokenBase;
124 break;
125 }
126 maybeEnd = (ch == "*");
127 }
128 return "comment";
129 }
130
131 function Context(indented, column, type, align, prev) {
132 this.indented = indented;
133 this.column = column;
134 this.type = type;
135 this.align = align;
136 this.prev = prev;
137 }
138 function pushContext(state, col, type) {
139 return state.context = new Context(state.indented, col, type, null, state.context);
140 }
141 function popContext(state) {
142 var t = state.context.type;
143 if (t == ")" || t == "]" || t == "}")
144 state.indented = state.context.indented;
145 return state.context = state.context.prev;
146 }
147
148 // Interface
149
150 return {
151 startState: function(basecolumn) {
152 return {
153 tokenize: null,
154 context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
155 indented: 0,
156 startOfLine: true
157 };
158 },
159
160 token: function(stream, state) {
161 var ctx = state.context;
162 if (stream.sol()) {
163 if (ctx.align == null) ctx.align = false;
164 state.indented = stream.indentation();
165 state.startOfLine = true;
166 }
167 if (stream.eatSpace()) return null;
168 curPunc = null;
169 var style = (state.tokenize || tokenBase)(stream, state);
170 if (style == "comment" || style == "meta") return style;
171 if (ctx.align == null) ctx.align = true;
172
173 if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state);
174 else if (curPunc == "{") pushContext(state, stream.column(), "}");
175 else if (curPunc == "[") pushContext(state, stream.column(), "]");
176 else if (curPunc == "(") pushContext(state, stream.column(), ")");
177 else if (curPunc == "}") {
178 while (ctx.type == "statement") ctx = popContext(state);
179 if (ctx.type == "}") ctx = popContext(state);
180 while (ctx.type == "statement") ctx = popContext(state);
181 }
182 else if (curPunc == ctx.type) popContext(state);
183 else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement"))
184 pushContext(state, stream.column(), "statement");
185 state.startOfLine = false;
186 return style;
187 },
188
189 indent: function(state, textAfter) {
190 if (state.tokenize != tokenBase && state.tokenize != null) return 0;
191 var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
192 if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
193 var closing = firstChar == ctx.type;
194 if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit);
195 else if (ctx.align) return ctx.column + (closing ? 0 : 1);
196 else return ctx.indented + (closing ? 0 : indentUnit);
197 },
198
199 electricChars: "{}"
200 };
201 });
202
203 CodeMirror.defineMIME("text/x-ecl", "ecl");
@@ -0,0 +1,42
1 <!doctype html>
2 <html>
3 <head>
4 <title>CodeMirror: ECL mode</title>
5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 <script src="../../lib/codemirror.js"></script>
7 <script src="ecl.js"></script>
8 <link rel="stylesheet" href="../../doc/docs.css">
9 <style>.CodeMirror {border: 1px solid black;}</style>
10 </head>
11 <body>
12 <h1>CodeMirror: ECL mode</h1>
13 <form><textarea id="code" name="code">
14 /*
15 sample useless code to demonstrate ecl syntax highlighting
16 this is a multiline comment!
17 */
18
19 // this is a singleline comment!
20
21 import ut;
22 r :=
23 record
24 string22 s1 := '123';
25 integer4 i1 := 123;
26 end;
27 #option('tmp', true);
28 d := dataset('tmp::qb', r, thor);
29 output(d);
30 </textarea></form>
31 <script>
32 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
33 tabMode: "indent",
34 matchBrackets: true,
35 });
36 </script>
37
38 <p>Based on CodeMirror's clike mode. For more information see <a href="http://hpccsystems.com">HPCC Systems</a> web site.</p>
39 <p><strong>MIME types defined:</strong> <code>text/x-ecl</code>.</p>
40
41 </body>
42 </html>
@@ -0,0 +1,463
1 // block; "begin", "case", "fun", "if", "receive", "try": closed by "end"
2 // block internal; "after", "catch", "of"
3 // guard; "when", closed by "->"
4 // "->" opens a clause, closed by ";" or "."
5 // "<<" opens a binary, closed by ">>"
6 // "," appears in arglists, lists, tuples and terminates lines of code
7 // "." resets indentation to 0
8 // obsolete; "cond", "let", "query"
9
10 CodeMirror.defineMIME("text/x-erlang", "erlang");
11
12 CodeMirror.defineMode("erlang", function(cmCfg, modeCfg) {
13
14 function rval(state,stream,type) {
15 // distinguish between "." as terminator and record field operator
16 if (type == "record") {
17 state.context = "record";
18 }else{
19 state.context = false;
20 }
21
22 // remember last significant bit on last line for indenting
23 if (type != "whitespace" && type != "comment") {
24 state.lastToken = stream.current();
25 }
26 // erlang -> CodeMirror tag
27 switch (type) {
28 case "atom": return "atom";
29 case "attribute": return "attribute";
30 case "builtin": return "builtin";
31 case "comment": return "comment";
32 case "fun": return "meta";
33 case "function": return "tag";
34 case "guard": return "property";
35 case "keyword": return "keyword";
36 case "macro": return "variable-2";
37 case "number": return "number";
38 case "operator": return "operator";
39 case "record": return "bracket";
40 case "string": return "string";
41 case "type": return "def";
42 case "variable": return "variable";
43 case "error": return "error";
44 case "separator": return null;
45 case "open_paren": return null;
46 case "close_paren": return null;
47 default: return null;
48 }
49 }
50
51 var typeWords = [
52 "-type", "-spec", "-export_type", "-opaque"];
53
54 var keywordWords = [
55 "after","begin","catch","case","cond","end","fun","if",
56 "let","of","query","receive","try","when"];
57
58 var separatorWords = [
59 "->",";",":",".",","];
60
61 var operatorWords = [
62 "and","andalso","band","bnot","bor","bsl","bsr","bxor",
63 "div","not","or","orelse","rem","xor"];
64
65 var symbolWords = [
66 "+","-","*","/",">",">=","<","=<","=:=","==","=/=","/=","||","<-"];
67
68 var openParenWords = [
69 "<<","(","[","{"];
70
71 var closeParenWords = [
72 "}","]",")",">>"];
73
74 var guardWords = [
75 "is_atom","is_binary","is_bitstring","is_boolean","is_float",
76 "is_function","is_integer","is_list","is_number","is_pid",
77 "is_port","is_record","is_reference","is_tuple",
78 "atom","binary","bitstring","boolean","function","integer","list",
79 "number","pid","port","record","reference","tuple"];
80
81 var bifWords = [
82 "abs","adler32","adler32_combine","alive","apply","atom_to_binary",
83 "atom_to_list","binary_to_atom","binary_to_existing_atom",
84 "binary_to_list","binary_to_term","bit_size","bitstring_to_list",
85 "byte_size","check_process_code","contact_binary","crc32",
86 "crc32_combine","date","decode_packet","delete_module",
87 "disconnect_node","element","erase","exit","float","float_to_list",
88 "garbage_collect","get","get_keys","group_leader","halt","hd",
89 "integer_to_list","internal_bif","iolist_size","iolist_to_binary",
90 "is_alive","is_atom","is_binary","is_bitstring","is_boolean",
91 "is_float","is_function","is_integer","is_list","is_number","is_pid",
92 "is_port","is_process_alive","is_record","is_reference","is_tuple",
93 "length","link","list_to_atom","list_to_binary","list_to_bitstring",
94 "list_to_existing_atom","list_to_float","list_to_integer",
95 "list_to_pid","list_to_tuple","load_module","make_ref","module_loaded",
96 "monitor_node","node","node_link","node_unlink","nodes","notalive",
97 "now","open_port","pid_to_list","port_close","port_command",
98 "port_connect","port_control","pre_loaded","process_flag",
99 "process_info","processes","purge_module","put","register",
100 "registered","round","self","setelement","size","spawn","spawn_link",
101 "spawn_monitor","spawn_opt","split_binary","statistics",
102 "term_to_binary","time","throw","tl","trunc","tuple_size",
103 "tuple_to_list","unlink","unregister","whereis"];
104
105 // ignored for indenting purposes
106 var ignoreWords = [
107 ",", ":", "catch", "after", "of", "cond", "let", "query"];
108
109
110 var smallRE = /[a-z_]/;
111 var largeRE = /[A-Z_]/;
112 var digitRE = /[0-9]/;
113 var octitRE = /[0-7]/;
114 var anumRE = /[a-z_A-Z0-9]/;
115 var symbolRE = /[\+\-\*\/<>=\|:]/;
116 var openParenRE = /[<\(\[\{]/;
117 var closeParenRE = /[>\)\]\}]/;
118 var sepRE = /[\->\.,:;]/;
119
120 function isMember(element,list) {
121 return (-1 < list.indexOf(element));
122 }
123
124 function isPrev(stream,string) {
125 var start = stream.start;
126 var len = string.length;
127 if (len <= start) {
128 var word = stream.string.slice(start-len,start);
129 return word == string;
130 }else{
131 return false;
132 }
133 }
134
135 function tokenize(stream, state) {
136 if (stream.eatSpace()) {
137 return rval(state,stream,"whitespace");
138 }
139
140 // attributes and type specs
141 if ((peekToken(state).token == "" || peekToken(state).token == ".") &&
142 stream.peek() == '-') {
143 stream.next();
144 if (stream.eat(smallRE) && stream.eatWhile(anumRE)) {
145 if (isMember(stream.current(),typeWords)) {
146 return rval(state,stream,"type");
147 }else{
148 return rval(state,stream,"attribute");
149 }
150 }
151 stream.backUp(1);
152 }
153
154 var ch = stream.next();
155
156 // comment
157 if (ch == '%') {
158 stream.skipToEnd();
159 return rval(state,stream,"comment");
160 }
161
162 // macro
163 if (ch == '?') {
164 stream.eatWhile(anumRE);
165 return rval(state,stream,"macro");
166 }
167
168 // record
169 if ( ch == "#") {
170 stream.eatWhile(anumRE);
171 return rval(state,stream,"record");
172 }
173
174 // char
175 if ( ch == "$") {
176 if (stream.next() == "\\") {
177 if (!stream.eatWhile(octitRE)) {
178 stream.next();
179 }
180 }
181 return rval(state,stream,"string");
182 }
183
184 // quoted atom
185 if (ch == '\'') {
186 if (singleQuote(stream)) {
187 return rval(state,stream,"atom");
188 }else{
189 return rval(state,stream,"error");
190 }
191 }
192
193 // string
194 if (ch == '"') {
195 if (doubleQuote(stream)) {
196 return rval(state,stream,"string");
197 }else{
198 return rval(state,stream,"error");
199 }
200 }
201
202 // variable
203 if (largeRE.test(ch)) {
204 stream.eatWhile(anumRE);
205 return rval(state,stream,"variable");
206 }
207
208 // atom/keyword/BIF/function
209 if (smallRE.test(ch)) {
210 stream.eatWhile(anumRE);
211
212 if (stream.peek() == "/") {
213 stream.next();
214 if (stream.eatWhile(digitRE)) {
215 return rval(state,stream,"fun"); // f/0 style fun
216 }else{
217 stream.backUp(1);
218 return rval(state,stream,"atom");
219 }
220 }
221
222 var w = stream.current();
223
224 if (isMember(w,keywordWords)) {
225 pushToken(state,stream);
226 return rval(state,stream,"keyword");
227 }
228 if (stream.peek() == "(") {
229 // 'put' and 'erlang:put' are bifs, 'foo:put' is not
230 if (isMember(w,bifWords) &&
231 (!isPrev(stream,":") || isPrev(stream,"erlang:"))) {
232 return rval(state,stream,"builtin");
233 }else{
234 return rval(state,stream,"function");
235 }
236 }
237 if (isMember(w,guardWords)) {
238 return rval(state,stream,"guard");
239 }
240 if (isMember(w,operatorWords)) {
241 return rval(state,stream,"operator");
242 }
243 if (stream.peek() == ":") {
244 if (w == "erlang") {
245 return rval(state,stream,"builtin");
246 } else {
247 return rval(state,stream,"function");
248 }
249 }
250 return rval(state,stream,"atom");
251 }
252
253 // number
254 if (digitRE.test(ch)) {
255 stream.eatWhile(digitRE);
256 if (stream.eat('#')) {
257 stream.eatWhile(digitRE); // 16#10 style integer
258 } else {
259 if (stream.eat('.')) { // float
260 stream.eatWhile(digitRE);
261 }
262 if (stream.eat(/[eE]/)) {
263 stream.eat(/[-+]/); // float with exponent
264 stream.eatWhile(digitRE);
265 }
266 }
267 return rval(state,stream,"number"); // normal integer
268 }
269
270 // open parens
271 if (nongreedy(stream,openParenRE,openParenWords)) {
272 pushToken(state,stream);
273 return rval(state,stream,"open_paren");
274 }
275
276 // close parens
277 if (nongreedy(stream,closeParenRE,closeParenWords)) {
278 pushToken(state,stream);
279 return rval(state,stream,"close_paren");
280 }
281
282 // separators
283 if (greedy(stream,sepRE,separatorWords)) {
284 // distinguish between "." as terminator and record field operator
285 if (state.context == false) {
286 pushToken(state,stream);
287 }
288 return rval(state,stream,"separator");
289 }
290
291 // operators
292 if (greedy(stream,symbolRE,symbolWords)) {
293 return rval(state,stream,"operator");
294 }
295
296 return rval(state,stream,null);
297 }
298
299 function nongreedy(stream,re,words) {
300 if (stream.current().length == 1 && re.test(stream.current())) {
301 stream.backUp(1);
302 while (re.test(stream.peek())) {
303 stream.next();
304 if (isMember(stream.current(),words)) {
305 return true;
306 }
307 }
308 stream.backUp(stream.current().length-1);
309 }
310 return false;
311 }
312
313 function greedy(stream,re,words) {
314 if (stream.current().length == 1 && re.test(stream.current())) {
315 while (re.test(stream.peek())) {
316 stream.next();
317 }
318 while (0 < stream.current().length) {
319 if (isMember(stream.current(),words)) {
320 return true;
321 }else{
322 stream.backUp(1);
323 }
324 }
325 stream.next();
326 }
327 return false;
328 }
329
330 function doubleQuote(stream) {
331 return quote(stream, '"', '\\');
332 }
333
334 function singleQuote(stream) {
335 return quote(stream,'\'','\\');
336 }
337
338 function quote(stream,quoteChar,escapeChar) {
339 while (!stream.eol()) {
340 var ch = stream.next();
341 if (ch == quoteChar) {
342 return true;
343 }else if (ch == escapeChar) {
344 stream.next();
345 }
346 }
347 return false;
348 }
349
350 function Token(stream) {
351 this.token = stream ? stream.current() : "";
352 this.column = stream ? stream.column() : 0;
353 this.indent = stream ? stream.indentation() : 0;
354 }
355
356 function myIndent(state,textAfter) {
357 var indent = cmCfg.indentUnit;
358 var outdentWords = ["after","catch"];
359 var token = (peekToken(state)).token;
360 var wordAfter = takewhile(textAfter,/[^a-z]/);
361
362 if (isMember(token,openParenWords)) {
363 return (peekToken(state)).column+token.length;
364 }else if (token == "." || token == ""){
365 return 0;
366 }else if (token == "->") {
367 if (wordAfter == "end") {
368 return peekToken(state,2).column;
369 }else if (peekToken(state,2).token == "fun") {
370 return peekToken(state,2).column+indent;
371 }else{
372 return (peekToken(state)).indent+indent;
373 }
374 }else if (isMember(wordAfter,outdentWords)) {
375 return (peekToken(state)).indent;
376 }else{
377 return (peekToken(state)).column+indent;
378 }
379 }
380
381 function takewhile(str,re) {
382 var m = str.match(re);
383 return m ? str.slice(0,m.index) : str;
384 }
385
386 function popToken(state) {
387 return state.tokenStack.pop();
388 }
389
390 function peekToken(state,depth) {
391 var len = state.tokenStack.length;
392 var dep = (depth ? depth : 1);
393 if (len < dep) {
394 return new Token;
395 }else{
396 return state.tokenStack[len-dep];
397 }
398 }
399
400 function pushToken(state,stream) {
401 var token = stream.current();
402 var prev_token = peekToken(state).token;
403 if (isMember(token,ignoreWords)) {
404 return false;
405 }else if (drop_both(prev_token,token)) {
406 popToken(state);
407 return false;
408 }else if (drop_first(prev_token,token)) {
409 popToken(state);
410 return pushToken(state,stream);
411 }else{
412 state.tokenStack.push(new Token(stream));
413 return true;
414 }
415 }
416
417 function drop_first(open, close) {
418 switch (open+" "+close) {
419 case "when ->": return true;
420 case "-> end": return true;
421 case "-> .": return true;
422 case ". .": return true;
423 default: return false;
424 }
425 }
426
427 function drop_both(open, close) {
428 switch (open+" "+close) {
429 case "( )": return true;
430 case "[ ]": return true;
431 case "{ }": return true;
432 case "<< >>": return true;
433 case "begin end": return true;
434 case "case end": return true;
435 case "fun end": return true;
436 case "if end": return true;
437 case "receive end": return true;
438 case "try end": return true;
439 case "-> ;": return true;
440 default: return false;
441 }
442 }
443
444 return {
445 startState:
446 function() {
447 return {tokenStack: [],
448 context: false,
449 lastToken: null};
450 },
451
452 token:
453 function(stream, state) {
454 return tokenize(stream, state);
455 },
456
457 indent:
458 function(state, textAfter) {
459 // console.log(state.tokenStack);
460 return myIndent(state,textAfter);
461 }
462 };
463 });
@@ -0,0 +1,62
1 <!doctype html>
2 <html>
3 <head>
4 <title>CodeMirror: Erlang mode</title>
5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 <script src="../../lib/codemirror.js"></script>
7 <script src="erlang.js"></script>
8 <link rel="stylesheet" href="../../theme/erlang-dark.css">
9 <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
10 <link rel="stylesheet" href="../../doc/docs.css">
11 </head>
12 <body>
13 <h1>CodeMirror: Erlang mode</h1>
14
15 <form><textarea id="code" name="code">
16 %% -*- mode: erlang; erlang-indent-level: 2 -*-
17 %%% Created : 7 May 2012 by mats cronqvist <masse@klarna.com>
18
19 %% @doc
20 %% Demonstrates how to print a record.
21 %% @end
22
23 -module('ex').
24 -author('mats cronqvist').
25 -export([demo/0,
26 rec_info/1]).
27
28 -record(demo,{a="One",b="Two",c="Three",d="Four"}).
29
30 rec_info(demo) -> record_info(fields,demo).
31
32 demo() -> expand_recs(?MODULE,#demo{a="A",b="BB"}).
33
34 expand_recs(M,List) when is_list(List) ->
35 [expand_recs(M,L)||L<-List];
36 expand_recs(M,Tup) when is_tuple(Tup) ->
37 case tuple_size(Tup) of
38 L when L < 1 -> Tup;
39 L ->
40 try Fields = M:rec_info(element(1,Tup)),
41 L = length(Fields)+1,
42 lists:zip(Fields,expand_recs(M,tl(tuple_to_list(Tup))))
43 catch _:_ ->
44 list_to_tuple(expand_recs(M,tuple_to_list(Tup)))
45 end
46 end;
47 expand_recs(_,Term) ->
48 Term.
49 </textarea></form>
50
51 <script>
52 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
53 lineNumbers: true,
54 matchBrackets: true,
55 extraKeys: {"Tab": "indentAuto"},
56 theme: "erlang-dark"
57 });
58 </script>
59
60 <p><strong>MIME types defined:</strong> <code>text/x-erlang</code>.</p>
61 </body>
62 </html>
@@ -0,0 +1,144
1 CodeMirror.defineMode("gfm", function(config, parserConfig) {
2 var mdMode = CodeMirror.getMode(config, "markdown");
3 var aliases = {
4 html: "htmlmixed",
5 js: "javascript",
6 json: "application/json",
7 c: "text/x-csrc",
8 "c++": "text/x-c++src",
9 java: "text/x-java",
10 csharp: "text/x-csharp",
11 "c#": "text/x-csharp"
12 };
13
14 // make this lazy so that we don't need to load GFM last
15 var getMode = (function () {
16 var i, modes = {}, mimes = {}, mime;
17
18 var list = CodeMirror.listModes();
19 for (i = 0; i < list.length; i++) {
20 modes[list[i]] = list[i];
21 }
22 var mimesList = CodeMirror.listMIMEs();
23 for (i = 0; i < mimesList.length; i++) {
24 mime = mimesList[i].mime;
25 mimes[mime] = mimesList[i].mime;
26 }
27
28 for (var a in aliases) {
29 if (aliases[a] in modes || aliases[a] in mimes)
30 modes[a] = aliases[a];
31 }
32
33 return function (lang) {
34 return modes[lang] ? CodeMirror.getMode(config, modes[lang]) : null;
35 }
36 }());
37
38 function markdown(stream, state) {
39 // intercept fenced code blocks
40 if (stream.sol() && stream.match(/^```([\w+#]*)/)) {
41 // try switching mode
42 state.localMode = getMode(RegExp.$1)
43 if (state.localMode)
44 state.localState = state.localMode.startState();
45
46 state.token = local;
47 return 'code';
48 }
49
50 return mdMode.token(stream, state.mdState);
51 }
52
53 function local(stream, state) {
54 if (stream.sol() && stream.match(/^```/)) {
55 state.localMode = state.localState = null;
56 state.token = markdown;
57 return 'code';
58 }
59 else if (state.localMode) {
60 return state.localMode.token(stream, state.localState);
61 } else {
62 stream.skipToEnd();
63 return 'code';
64 }
65 }
66
67 // custom handleText to prevent emphasis in the middle of a word
68 // and add autolinking
69 function handleText(stream, mdState) {
70 var match;
71 if (stream.match(/^\w+:\/\/\S+/)) {
72 return 'link';
73 }
74 if (stream.match(/^[^\[*\\<>` _][^\[*\\<>` ]*[^\[*\\<>` _]/)) {
75 return mdMode.getType(mdState);
76 }
77 if (match = stream.match(/^[^\[*\\<>` ]+/)) {
78 var word = match[0];
79 if (word[0] === '_' && word[word.length-1] === '_') {
80 stream.backUp(word.length);
81 return undefined;
82 }
83 return mdMode.getType(mdState);
84 }
85 if (stream.eatSpace()) {
86 return null;
87 }
88 }
89
90 return {
91 startState: function() {
92 var mdState = mdMode.startState();
93 mdState.text = handleText;
94 return {token: markdown, mode: "markdown", mdState: mdState,
95 localMode: null, localState: null};
96 },
97
98 copyState: function(state) {
99 return {token: state.token, mode: state.mode, mdState: CodeMirror.copyState(mdMode, state.mdState),
100 localMode: state.localMode,
101 localState: state.localMode ? CodeMirror.copyState(state.localMode, state.localState) : null};
102 },
103
104 token: function(stream, state) {
105 /* Parse GFM double bracket links */
106 if ((ch = stream.peek()) != undefined && ch == '[') {
107 stream.next(); // Advance the stream
108
109 /* Only handle double bracket links */
110 if ((ch = stream.peek()) == undefined || ch != '[') {
111 stream.backUp(1);
112 return state.token(stream, state);
113 }
114
115 while ((ch = stream.next()) != undefined && ch != ']') {}
116
117 if (ch == ']' && (ch = stream.next()) != undefined && ch == ']')
118 return 'link';
119
120 /* If we did not find the second ']' */
121 stream.backUp(1);
122 }
123
124 /* Match GFM latex formulas, as well as latex formulas within '$' */
125 if (stream.match(/^\$[^\$]+\$/)) {
126 return "string";
127 }
128
129 if (stream.match(/^\\\((.*?)\\\)/)) {
130 return "string";
131 }
132
133 if (stream.match(/^\$\$[^\$]+\$\$/)) {
134 return "string";
135 }
136
137 if (stream.match(/^\\\[(.*?)\\\]/)) {
138 return "string";
139 }
140
141 return state.token(stream, state);
142 }
143 }
144 }, "markdown");
@@ -0,0 +1,47
1 <!doctype html>
2 <html>
3 <head>
4 <title>CodeMirror: GFM mode</title>
5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 <script src="../../lib/codemirror.js"></script>
7 <script src="../xml/xml.js"></script>
8 <script src="../markdown/markdown.js"></script>
9 <script src="gfm.js"></script>
10 <script src="../javascript/javascript.js"></script>
11 <link rel="stylesheet" href="../markdown/markdown.css">
12 <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
13 <link rel="stylesheet" href="../../doc/docs.css">
14 </head>
15 <body>
16 <h1>CodeMirror: GFM mode</h1>
17
18 <!-- source: http://daringfireball.net/projects/markdown/basics.text -->
19 <form><textarea id="code" name="code">
20 Github Flavored Markdown
21 ========================
22
23 Everything from markdown plus GFM features:
24
25 ## Fenced code blocks
26
27 ```javascript
28 for (var i = 0; i &lt; items.length; i++) {
29 console.log(items[i], i); // log them
30 }
31 ```
32
33 See http://github.github.com/github-flavored-markdown/
34
35 </textarea></form>
36
37 <script>
38 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
39 mode: 'gfm',
40 lineNumbers: true,
41 matchBrackets: true,
42 theme: "default"
43 });
44 </script>
45
46 </body>
47 </html>
@@ -0,0 +1,170
1 CodeMirror.defineMode("go", function(config, parserConfig) {
2 var indentUnit = config.indentUnit;
3
4 var keywords = {
5 "break":true, "case":true, "chan":true, "const":true, "continue":true,
6 "default":true, "defer":true, "else":true, "fallthrough":true, "for":true,
7 "func":true, "go":true, "goto":true, "if":true, "import":true,
8 "interface":true, "map":true, "package":true, "range":true, "return":true,
9 "select":true, "struct":true, "switch":true, "type":true, "var":true,
10 "bool":true, "byte":true, "complex64":true, "complex128":true,
11 "float32":true, "float64":true, "int8":true, "int16":true, "int32":true,
12 "int64":true, "string":true, "uint8":true, "uint16":true, "uint32":true,
13 "uint64":true, "int":true, "uint":true, "uintptr":true
14 };
15
16 var atoms = {
17 "true":true, "false":true, "iota":true, "nil":true, "append":true,
18 "cap":true, "close":true, "complex":true, "copy":true, "imag":true,
19 "len":true, "make":true, "new":true, "panic":true, "print":true,
20 "println":true, "real":true, "recover":true
21 };
22
23 var blockKeywords = {
24 "else":true, "for":true, "func":true, "if":true, "interface":true,
25 "select":true, "struct":true, "switch":true
26 };
27
28 var isOperatorChar = /[+\-*&^%:=<>!|\/]/;
29
30 var curPunc;
31
32 function tokenBase(stream, state) {
33 var ch = stream.next();
34 if (ch == '"' || ch == "'" || ch == "`") {
35 state.tokenize = tokenString(ch);
36 return state.tokenize(stream, state);
37 }
38 if (/[\d\.]/.test(ch)) {
39 if (ch == ".") {
40 stream.match(/^[0-9]+([eE][\-+]?[0-9]+)?/);
41 } else if (ch == "0") {
42 stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/);
43 } else {
44 stream.match(/^[0-9]*\.?[0-9]*([eE][\-+]?[0-9]+)?/);
45 }
46 return "number";
47 }
48 if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
49 curPunc = ch;
50 return null
51 }
52 if (ch == "/") {
53 if (stream.eat("*")) {
54 state.tokenize = tokenComment;
55 return tokenComment(stream, state);
56 }
57 if (stream.eat("/")) {
58 stream.skipToEnd();
59 return "comment";
60 }
61 }
62 if (isOperatorChar.test(ch)) {
63 stream.eatWhile(isOperatorChar);
64 return "operator";
65 }
66 stream.eatWhile(/[\w\$_]/);
67 var cur = stream.current();
68 if (keywords.propertyIsEnumerable(cur)) {
69 if (cur == "case" || cur == "default") curPunc = "case";
70 return "keyword";
71 }
72 if (atoms.propertyIsEnumerable(cur)) return "atom";
73 return "variable";
74 }
75
76 function tokenString(quote) {
77 return function(stream, state) {
78 var escaped = false, next, end = false;
79 while ((next = stream.next()) != null) {
80 if (next == quote && !escaped) {end = true; break;}
81 escaped = !escaped && next == "\\";
82 }
83 if (end || !(escaped || quote == "`"))
84 state.tokenize = tokenBase;
85 return "string";
86 };
87 }
88
89 function tokenComment(stream, state) {
90 var maybeEnd = false, ch;
91 while (ch = stream.next()) {
92 if (ch == "/" && maybeEnd) {
93 state.tokenize = tokenBase;
94 break;
95 }
96 maybeEnd = (ch == "*");
97 }
98 return "comment";
99 }
100
101 function Context(indented, column, type, align, prev) {
102 this.indented = indented;
103 this.column = column;
104 this.type = type;
105 this.align = align;
106 this.prev = prev;
107 }
108 function pushContext(state, col, type) {
109 return state.context = new Context(state.indented, col, type, null, state.context);
110 }
111 function popContext(state) {
112 var t = state.context.type;
113 if (t == ")" || t == "]" || t == "}")
114 state.indented = state.context.indented;
115 return state.context = state.context.prev;
116 }
117
118 // Interface
119
120 return {
121 startState: function(basecolumn) {
122 return {
123 tokenize: null,
124 context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
125 indented: 0,
126 startOfLine: true
127 };
128 },
129
130 token: function(stream, state) {
131 var ctx = state.context;
132 if (stream.sol()) {
133 if (ctx.align == null) ctx.align = false;
134 state.indented = stream.indentation();
135 state.startOfLine = true;
136 if (ctx.type == "case") ctx.type = "}";
137 }
138 if (stream.eatSpace()) return null;
139 curPunc = null;
140 var style = (state.tokenize || tokenBase)(stream, state);
141 if (style == "comment") return style;
142 if (ctx.align == null) ctx.align = true;
143
144 if (curPunc == "{") pushContext(state, stream.column(), "}");
145 else if (curPunc == "[") pushContext(state, stream.column(), "]");
146 else if (curPunc == "(") pushContext(state, stream.column(), ")");
147 else if (curPunc == "case") ctx.type = "case"
148 else if (curPunc == "}" && ctx.type == "}") ctx = popContext(state);
149 else if (curPunc == ctx.type) popContext(state);
150 state.startOfLine = false;
151 return style;
152 },
153
154 indent: function(state, textAfter) {
155 if (state.tokenize != tokenBase && state.tokenize != null) return 0;
156 var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
157 if (ctx.type == "case" && /^(?:case|default)\b/.test(textAfter)) {
158 state.context.type = "}";
159 return ctx.indented;
160 }
161 var closing = firstChar == ctx.type;
162 if (ctx.align) return ctx.column + (closing ? 0 : 1);
163 else return ctx.indented + (closing ? 0 : indentUnit);
164 },
165
166 electricChars: "{}:"
167 };
168 });
169
170 CodeMirror.defineMIME("text/x-go", "go");
@@ -0,0 +1,72
1 <!doctype html>
2 <html>
3 <head>
4 <title>CodeMirror: Go mode</title>
5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 <link rel="stylesheet" href="../../theme/elegant.css">
7 <script src="../../lib/codemirror.js"></script>
8 <script src="go.js"></script>
9 <link rel="stylesheet" href="../../doc/docs.css">
10 <style>.CodeMirror {border:1px solid #999; background:#ffc}</style>
11 </head>
12 <body>
13 <h1>CodeMirror: Go mode</h1>
14
15 <form><textarea id="code" name="code">
16 // Prime Sieve in Go.
17 // Taken from the Go specification.
18 // Copyright © The Go Authors.
19
20 package main
21
22 import "fmt"
23
24 // Send the sequence 2, 3, 4, ... to channel 'ch'.
25 func generate(ch chan&lt;- int) {
26 for i := 2; ; i++ {
27 ch &lt;- i // Send 'i' to channel 'ch'
28 }
29 }
30
31 // Copy the values from channel 'src' to channel 'dst',
32 // removing those divisible by 'prime'.
33 func filter(src &lt;-chan int, dst chan&lt;- int, prime int) {
34 for i := range src { // Loop over values received from 'src'.
35 if i%prime != 0 {
36 dst &lt;- i // Send 'i' to channel 'dst'.
37 }
38 }
39 }
40
41 // The prime sieve: Daisy-chain filter processes together.
42 func sieve() {
43 ch := make(chan int) // Create a new channel.
44 go generate(ch) // Start generate() as a subprocess.
45 for {
46 prime := &lt;-ch
47 fmt.Print(prime, "\n")
48 ch1 := make(chan int)
49 go filter(ch, ch1, prime)
50 ch = ch1
51 }
52 }
53
54 func main() {
55 sieve()
56 }
57 </textarea></form>
58
59 <script>
60 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
61 theme: "elegant",
62 matchBrackets: true,
63 indentUnit: 8,
64 tabSize: 8,
65 indentWithTabs: true,
66 mode: "text/x-go"
67 });
68 </script>
69
70 <p><strong>MIME type:</strong> <code>text/x-go</code></p>
71 </body>
72 </html>
@@ -0,0 +1,210
1 CodeMirror.defineMode("groovy", function(config, parserConfig) {
2 function words(str) {
3 var obj = {}, words = str.split(" ");
4 for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
5 return obj;
6 }
7 var keywords = words(
8 "abstract as assert boolean break byte case catch char class const continue def default " +
9 "do double else enum extends final finally float for goto if implements import in " +
10 "instanceof int interface long native new package private protected public return " +
11 "short static strictfp super switch synchronized threadsafe throw throws transient " +
12 "try void volatile while");
13 var blockKeywords = words("catch class do else finally for if switch try while enum interface def");
14 var atoms = words("null true false this");
15
16 var curPunc;
17 function tokenBase(stream, state) {
18 var ch = stream.next();
19 if (ch == '"' || ch == "'") {
20 return startString(ch, stream, state);
21 }
22 if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
23 curPunc = ch;
24 return null
25 }
26 if (/\d/.test(ch)) {
27 stream.eatWhile(/[\w\.]/);
28 if (stream.eat(/eE/)) { stream.eat(/\+\-/); stream.eatWhile(/\d/); }
29 return "number";
30 }
31 if (ch == "/") {
32 if (stream.eat("*")) {
33 state.tokenize.push(tokenComment);
34 return tokenComment(stream, state);
35 }
36 if (stream.eat("/")) {
37 stream.skipToEnd();
38 return "comment";
39 }
40 if (expectExpression(state.lastToken)) {
41 return startString(ch, stream, state);
42 }
43 }
44 if (ch == "-" && stream.eat(">")) {
45 curPunc = "->";
46 return null;
47 }
48 if (/[+\-*&%=<>!?|\/~]/.test(ch)) {
49 stream.eatWhile(/[+\-*&%=<>|~]/);
50 return "operator";
51 }
52 stream.eatWhile(/[\w\$_]/);
53 if (ch == "@") { stream.eatWhile(/[\w\$_\.]/); return "meta"; }
54 if (state.lastToken == ".") return "property";
55 if (stream.eat(":")) { curPunc = "proplabel"; return "property"; }
56 var cur = stream.current();
57 if (atoms.propertyIsEnumerable(cur)) { return "atom"; }
58 if (keywords.propertyIsEnumerable(cur)) {
59 if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
60 return "keyword";
61 }
62 return "variable";
63 }
64 tokenBase.isBase = true;
65
66 function startString(quote, stream, state) {
67 var tripleQuoted = false;
68 if (quote != "/" && stream.eat(quote)) {
69 if (stream.eat(quote)) tripleQuoted = true;
70 else return "string";
71 }
72 function t(stream, state) {
73 var escaped = false, next, end = !tripleQuoted;
74 while ((next = stream.next()) != null) {
75 if (next == quote && !escaped) {
76 if (!tripleQuoted) { break; }
77 if (stream.match(quote + quote)) { end = true; break; }
78 }
79 if (quote == '"' && next == "$" && !escaped && stream.eat("{")) {
80 state.tokenize.push(tokenBaseUntilBrace());
81 return "string";
82 }
83 escaped = !escaped && next == "\\";
84 }
85 if (end) state.tokenize.pop();
86 return "string";
87 }
88 state.tokenize.push(t);
89 return t(stream, state);
90 }
91
92 function tokenBaseUntilBrace() {
93 var depth = 1;
94 function t(stream, state) {
95 if (stream.peek() == "}") {
96 depth--;
97 if (depth == 0) {
98 state.tokenize.pop();
99 return state.tokenize[state.tokenize.length-1](stream, state);
100 }
101 } else if (stream.peek() == "{") {
102 depth++;
103 }
104 return tokenBase(stream, state);
105 }
106 t.isBase = true;
107 return t;
108 }
109
110 function tokenComment(stream, state) {
111 var maybeEnd = false, ch;
112 while (ch = stream.next()) {
113 if (ch == "/" && maybeEnd) {
114 state.tokenize.pop();
115 break;
116 }
117 maybeEnd = (ch == "*");
118 }
119 return "comment";
120 }
121
122 function expectExpression(last) {
123 return !last || last == "operator" || last == "->" || /[\.\[\{\(,;:]/.test(last) ||
124 last == "newstatement" || last == "keyword" || last == "proplabel";
125 }
126
127 function Context(indented, column, type, align, prev) {
128 this.indented = indented;
129 this.column = column;
130 this.type = type;
131 this.align = align;
132 this.prev = prev;
133 }
134 function pushContext(state, col, type) {
135 return state.context = new Context(state.indented, col, type, null, state.context);
136 }
137 function popContext(state) {
138 var t = state.context.type;
139 if (t == ")" || t == "]" || t == "}")
140 state.indented = state.context.indented;
141 return state.context = state.context.prev;
142 }
143
144 // Interface
145
146 return {
147 startState: function(basecolumn) {
148 return {
149 tokenize: [tokenBase],
150 context: new Context((basecolumn || 0) - config.indentUnit, 0, "top", false),
151 indented: 0,
152 startOfLine: true,
153 lastToken: null
154 };
155 },
156
157 token: function(stream, state) {
158 var ctx = state.context;
159 if (stream.sol()) {
160 if (ctx.align == null) ctx.align = false;
161 state.indented = stream.indentation();
162 state.startOfLine = true;
163 // Automatic semicolon insertion
164 if (ctx.type == "statement" && !expectExpression(state.lastToken)) {
165 popContext(state); ctx = state.context;
166 }
167 }
168 if (stream.eatSpace()) return null;
169 curPunc = null;
170 var style = state.tokenize[state.tokenize.length-1](stream, state);
171 if (style == "comment") return style;
172 if (ctx.align == null) ctx.align = true;
173
174 if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state);
175 // Handle indentation for {x -> \n ... }
176 else if (curPunc == "->" && ctx.type == "statement" && ctx.prev.type == "}") {
177 popContext(state);
178 state.context.align = false;
179 }
180 else if (curPunc == "{") pushContext(state, stream.column(), "}");
181 else if (curPunc == "[") pushContext(state, stream.column(), "]");
182 else if (curPunc == "(") pushContext(state, stream.column(), ")");
183 else if (curPunc == "}") {
184 while (ctx.type == "statement") ctx = popContext(state);
185 if (ctx.type == "}") ctx = popContext(state);
186 while (ctx.type == "statement") ctx = popContext(state);
187 }
188 else if (curPunc == ctx.type) popContext(state);
189 else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement"))
190 pushContext(state, stream.column(), "statement");
191 state.startOfLine = false;
192 state.lastToken = curPunc || style;
193 return style;
194 },
195
196 indent: function(state, textAfter) {
197 if (!state.tokenize[state.tokenize.length-1].isBase) return 0;
198 var firstChar = textAfter && textAfter.charAt(0), ctx = state.context;
199 if (ctx.type == "statement" && !expectExpression(state.lastToken)) ctx = ctx.prev;
200 var closing = firstChar == ctx.type;
201 if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : config.indentUnit);
202 else if (ctx.align) return ctx.column + (closing ? 0 : 1);
203 else return ctx.indented + (closing ? 0 : config.indentUnit);
204 },
205
206 electricChars: "{}"
207 };
208 });
209
210 CodeMirror.defineMIME("text/x-groovy", "groovy");
@@ -0,0 +1,71
1 <!doctype html>
2 <html>
3 <head>
4 <title>CodeMirror: Groovy mode</title>
5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 <script src="../../lib/codemirror.js"></script>
7 <script src="groovy.js"></script>
8 <link rel="stylesheet" href="../../doc/docs.css">
9 <style>.CodeMirror {border-top: 1px solid #500; border-bottom: 1px solid #500;}</style>
10 </head>
11 <body>
12 <h1>CodeMirror: Groovy mode</h1>
13
14 <form><textarea id="code" name="code">
15 //Pattern for groovy script
16 def p = ~/.*\.groovy/
17 new File( 'd:\\scripts' ).eachFileMatch(p) {f ->
18 // imports list
19 def imports = []
20 f.eachLine {
21 // condition to detect an import instruction
22 ln -> if ( ln =~ '^import .*' ) {
23 imports << "${ln - 'import '}"
24 }
25 }
26 // print thmen
27 if ( ! imports.empty ) {
28 println f
29 imports.each{ println " $it" }
30 }
31 }
32
33 /* Coin changer demo code from http://groovy.codehaus.org */
34
35 enum UsCoin {
36 quarter(25), dime(10), nickel(5), penny(1)
37 UsCoin(v) { value = v }
38 final value
39 }
40
41 enum OzzieCoin {
42 fifty(50), twenty(20), ten(10), five(5)
43 OzzieCoin(v) { value = v }
44 final value
45 }
46
47 def plural(word, count) {
48 if (count == 1) return word
49 word[-1] == 'y' ? word[0..-2] + "ies" : word + "s"
50 }
51
52 def change(currency, amount) {
53 currency.values().inject([]){ list, coin ->
54 int count = amount / coin.value
55 amount = amount % coin.value
56 list += "$count ${plural(coin.toString(), count)}"
57 }
58 }
59 </textarea></form>
60
61 <script>
62 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
63 lineNumbers: true,
64 matchBrackets: true,
65 mode: "text/x-groovy"
66 });
67 </script>
68
69 <p><strong>MIME types defined:</strong> <code>text/x-groovy</code></p>
70 </body>
71 </html>
@@ -0,0 +1,242
1 CodeMirror.defineMode("haskell", function(cmCfg, modeCfg) {
2
3 function switchState(source, setState, f) {
4 setState(f);
5 return f(source, setState);
6 }
7
8 // These should all be Unicode extended, as per the Haskell 2010 report
9 var smallRE = /[a-z_]/;
10 var largeRE = /[A-Z]/;
11 var digitRE = /[0-9]/;
12 var hexitRE = /[0-9A-Fa-f]/;
13 var octitRE = /[0-7]/;
14 var idRE = /[a-z_A-Z0-9']/;
15 var symbolRE = /[-!#$%&*+.\/<=>?@\\^|~:]/;
16 var specialRE = /[(),;[\]`{}]/;
17 var whiteCharRE = /[ \t\v\f]/; // newlines are handled in tokenizer
18
19 function normal(source, setState) {
20 if (source.eatWhile(whiteCharRE)) {
21 return null;
22 }
23
24 var ch = source.next();
25 if (specialRE.test(ch)) {
26 if (ch == '{' && source.eat('-')) {
27 var t = "comment";
28 if (source.eat('#')) {
29 t = "meta";
30 }
31 return switchState(source, setState, ncomment(t, 1));
32 }
33 return null;
34 }
35
36 if (ch == '\'') {
37 if (source.eat('\\')) {
38 source.next(); // should handle other escapes here
39 }
40 else {
41 source.next();
42 }
43 if (source.eat('\'')) {
44 return "string";
45 }
46 return "error";
47 }
48
49 if (ch == '"') {
50 return switchState(source, setState, stringLiteral);
51 }
52
53 if (largeRE.test(ch)) {
54 source.eatWhile(idRE);
55 if (source.eat('.')) {
56 return "qualifier";
57 }
58 return "variable-2";
59 }
60
61 if (smallRE.test(ch)) {
62 source.eatWhile(idRE);
63 return "variable";
64 }
65
66 if (digitRE.test(ch)) {
67 if (ch == '0') {
68 if (source.eat(/[xX]/)) {
69 source.eatWhile(hexitRE); // should require at least 1
70 return "integer";
71 }
72 if (source.eat(/[oO]/)) {
73 source.eatWhile(octitRE); // should require at least 1
74 return "number";
75 }
76 }
77 source.eatWhile(digitRE);
78 var t = "number";
79 if (source.eat('.')) {
80 t = "number";
81 source.eatWhile(digitRE); // should require at least 1
82 }
83 if (source.eat(/[eE]/)) {
84 t = "number";
85 source.eat(/[-+]/);
86 source.eatWhile(digitRE); // should require at least 1
87 }
88 return t;
89 }
90
91 if (symbolRE.test(ch)) {
92 if (ch == '-' && source.eat(/-/)) {
93 source.eatWhile(/-/);
94 if (!source.eat(symbolRE)) {
95 source.skipToEnd();
96 return "comment";
97 }
98 }
99 var t = "variable";
100 if (ch == ':') {
101 t = "variable-2";
102 }
103 source.eatWhile(symbolRE);
104 return t;
105 }
106
107 return "error";
108 }
109
110 function ncomment(type, nest) {
111 if (nest == 0) {
112 return normal;
113 }
114 return function(source, setState) {
115 var currNest = nest;
116 while (!source.eol()) {
117 var ch = source.next();
118 if (ch == '{' && source.eat('-')) {
119 ++currNest;
120 }
121 else if (ch == '-' && source.eat('}')) {
122 --currNest;
123 if (currNest == 0) {
124 setState(normal);
125 return type;
126 }
127 }
128 }
129 setState(ncomment(type, currNest));
130 return type;
131 }
132 }
133
134 function stringLiteral(source, setState) {
135 while (!source.eol()) {
136 var ch = source.next();
137 if (ch == '"') {
138 setState(normal);
139 return "string";
140 }
141 if (ch == '\\') {
142 if (source.eol() || source.eat(whiteCharRE)) {
143 setState(stringGap);
144 return "string";
145 }
146 if (source.eat('&')) {
147 }
148 else {
149 source.next(); // should handle other escapes here
150 }
151 }
152 }
153 setState(normal);
154 return "error";
155 }
156
157 function stringGap(source, setState) {
158 if (source.eat('\\')) {
159 return switchState(source, setState, stringLiteral);
160 }
161 source.next();
162 setState(normal);
163 return "error";
164 }
165
166
167 var wellKnownWords = (function() {
168 var wkw = {};
169 function setType(t) {
170 return function () {
171 for (var i = 0; i < arguments.length; i++)
172 wkw[arguments[i]] = t;
173 }
174 }
175
176 setType("keyword")(
177 "case", "class", "data", "default", "deriving", "do", "else", "foreign",
178 "if", "import", "in", "infix", "infixl", "infixr", "instance", "let",
179 "module", "newtype", "of", "then", "type", "where", "_");
180
181 setType("keyword")(
182 "\.\.", ":", "::", "=", "\\", "\"", "<-", "->", "@", "~", "=>");
183
184 setType("builtin")(
185 "!!", "$!", "$", "&&", "+", "++", "-", ".", "/", "/=", "<", "<=", "=<<",
186 "==", ">", ">=", ">>", ">>=", "^", "^^", "||", "*", "**");
187
188 setType("builtin")(
189 "Bool", "Bounded", "Char", "Double", "EQ", "Either", "Enum", "Eq",
190 "False", "FilePath", "Float", "Floating", "Fractional", "Functor", "GT",
191 "IO", "IOError", "Int", "Integer", "Integral", "Just", "LT", "Left",
192 "Maybe", "Monad", "Nothing", "Num", "Ord", "Ordering", "Rational", "Read",
193 "ReadS", "Real", "RealFloat", "RealFrac", "Right", "Show", "ShowS",
194 "String", "True");
195
196 setType("builtin")(
197 "abs", "acos", "acosh", "all", "and", "any", "appendFile", "asTypeOf",
198 "asin", "asinh", "atan", "atan2", "atanh", "break", "catch", "ceiling",
199 "compare", "concat", "concatMap", "const", "cos", "cosh", "curry",
200 "cycle", "decodeFloat", "div", "divMod", "drop", "dropWhile", "either",
201 "elem", "encodeFloat", "enumFrom", "enumFromThen", "enumFromThenTo",
202 "enumFromTo", "error", "even", "exp", "exponent", "fail", "filter",
203 "flip", "floatDigits", "floatRadix", "floatRange", "floor", "fmap",
204 "foldl", "foldl1", "foldr", "foldr1", "fromEnum", "fromInteger",
205 "fromIntegral", "fromRational", "fst", "gcd", "getChar", "getContents",
206 "getLine", "head", "id", "init", "interact", "ioError", "isDenormalized",
207 "isIEEE", "isInfinite", "isNaN", "isNegativeZero", "iterate", "last",
208 "lcm", "length", "lex", "lines", "log", "logBase", "lookup", "map",
209 "mapM", "mapM_", "max", "maxBound", "maximum", "maybe", "min", "minBound",
210 "minimum", "mod", "negate", "not", "notElem", "null", "odd", "or",
211 "otherwise", "pi", "pred", "print", "product", "properFraction",
212 "putChar", "putStr", "putStrLn", "quot", "quotRem", "read", "readFile",
213 "readIO", "readList", "readLn", "readParen", "reads", "readsPrec",
214 "realToFrac", "recip", "rem", "repeat", "replicate", "return", "reverse",
215 "round", "scaleFloat", "scanl", "scanl1", "scanr", "scanr1", "seq",
216 "sequence", "sequence_", "show", "showChar", "showList", "showParen",
217 "showString", "shows", "showsPrec", "significand", "signum", "sin",
218 "sinh", "snd", "span", "splitAt", "sqrt", "subtract", "succ", "sum",
219 "tail", "take", "takeWhile", "tan", "tanh", "toEnum", "toInteger",
220 "toRational", "truncate", "uncurry", "undefined", "unlines", "until",
221 "unwords", "unzip", "unzip3", "userError", "words", "writeFile", "zip",
222 "zip3", "zipWith", "zipWith3");
223
224 return wkw;
225 })();
226
227
228
229 return {
230 startState: function () { return { f: normal }; },
231 copyState: function (s) { return { f: s.f }; },
232
233 token: function(stream, state) {
234 var t = state.f(stream, function(s) { state.f = s; });
235 var w = stream.current();
236 return (w in wellKnownWords) ? wellKnownWords[w] : t;
237 }
238 };
239
240 });
241
242 CodeMirror.defineMIME("text/x-haskell", "haskell");
@@ -0,0 +1,60
1 <!doctype html>
2 <html>
3 <head>
4 <title>CodeMirror: Haskell mode</title>
5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 <script src="../../lib/codemirror.js"></script>
7 <script src="haskell.js"></script>
8 <link rel="stylesheet" href="../../theme/elegant.css">
9 <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
10 <link rel="stylesheet" href="../../doc/docs.css">
11 </head>
12 <body>
13 <h1>CodeMirror: Haskell mode</h1>
14
15 <form><textarea id="code" name="code">
16 module UniquePerms (
17 uniquePerms
18 )
19 where
20
21 -- | Find all unique permutations of a list where there might be duplicates.
22 uniquePerms :: (Eq a) => [a] -> [[a]]
23 uniquePerms = permBag . makeBag
24
25 -- | An unordered collection where duplicate values are allowed,
26 -- but represented with a single value and a count.
27 type Bag a = [(a, Int)]
28
29 makeBag :: (Eq a) => [a] -> Bag a
30 makeBag [] = []
31 makeBag (a:as) = mix a $ makeBag as
32 where
33 mix a [] = [(a,1)]
34 mix a (bn@(b,n):bs) | a == b = (b,n+1):bs
35 | otherwise = bn : mix a bs
36
37 permBag :: Bag a -> [[a]]
38 permBag [] = [[]]
39 permBag bs = concatMap (\(f,cs) -> map (f:) $ permBag cs) . oneOfEach $ bs
40 where
41 oneOfEach [] = []
42 oneOfEach (an@(a,n):bs) =
43 let bs' = if n == 1 then bs else (a,n-1):bs
44 in (a,bs') : mapSnd (an:) (oneOfEach bs)
45
46 apSnd f (a,b) = (a, f b)
47 mapSnd = map . apSnd
48 </textarea></form>
49
50 <script>
51 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
52 lineNumbers: true,
53 matchBrackets: true,
54 theme: "elegant"
55 });
56 </script>
57
58 <p><strong>MIME types defined:</strong> <code>text/x-haskell</code>.</p>
59 </body>
60 </html>
@@ -0,0 +1,432
1 CodeMirror.defineMode("haxe", function(config, parserConfig) {
2 var indentUnit = config.indentUnit;
3
4 // Tokenizer
5
6 var keywords = function(){
7 function kw(type) {return {type: type, style: "keyword"};}
8 var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
9 var operator = kw("operator"), atom = {type: "atom", style: "atom"}, attribute = {type:"attribute", style: "attribute"}
10 var type = kw("typedef");
11 return {
12 "if": A, "while": A, "else": B, "do": B, "try": B,
13 "return": C, "break": C, "continue": C, "new": C, "throw": C,
14 "var": kw("var"), "inline":attribute, "static": attribute, "using":kw("import"),
15 "public": attribute, "private": attribute, "cast": kw("cast"), "import": kw("import"), "macro": kw("macro"),
16 "function": kw("function"), "catch": kw("catch"), "untyped": kw("untyped"), "callback": kw("cb"),
17 "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
18 "in": operator, "never": kw("property_access"), "trace":kw("trace"),
19 "class": type, "enum":type, "interface":type, "typedef":type, "extends":type, "implements":type, "dynamic":type,
20 "true": atom, "false": atom, "null": atom
21 };
22 }();
23
24 var isOperatorChar = /[+\-*&%=<>!?|]/;
25
26 function chain(stream, state, f) {
27 state.tokenize = f;
28 return f(stream, state);
29 }
30
31 function nextUntilUnescaped(stream, end) {
32 var escaped = false, next;
33 while ((next = stream.next()) != null) {
34 if (next == end && !escaped)
35 return false;
36 escaped = !escaped && next == "\\";
37 }
38 return escaped;
39 }
40
41 // Used as scratch variables to communicate multiple values without
42 // consing up tons of objects.
43 var type, content;
44 function ret(tp, style, cont) {
45 type = tp; content = cont;
46 return style;
47 }
48
49 function haxeTokenBase(stream, state) {
50 var ch = stream.next();
51 if (ch == '"' || ch == "'")
52 return chain(stream, state, haxeTokenString(ch));
53 else if (/[\[\]{}\(\),;\:\.]/.test(ch))
54 return ret(ch);
55 else if (ch == "0" && stream.eat(/x/i)) {
56 stream.eatWhile(/[\da-f]/i);
57 return ret("number", "number");
58 }
59 else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) {
60 stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
61 return ret("number", "number");
62 }
63 else if (state.reAllowed && (ch == "~" && stream.eat(/\//))) {
64 nextUntilUnescaped(stream, "/");
65 stream.eatWhile(/[gimsu]/);
66 return ret("regexp", "string-2");
67 }
68 else if (ch == "/") {
69 if (stream.eat("*")) {
70 return chain(stream, state, haxeTokenComment);
71 }
72 else if (stream.eat("/")) {
73 stream.skipToEnd();
74 return ret("comment", "comment");
75 }
76 else {
77 stream.eatWhile(isOperatorChar);
78 return ret("operator", null, stream.current());
79 }
80 }
81 else if (ch == "#") {
82 stream.skipToEnd();
83 return ret("conditional", "meta");
84 }
85 else if (ch == "@") {
86 stream.eat(/:/);
87 stream.eatWhile(/[\w_]/);
88 return ret ("metadata", "meta");
89 }
90 else if (isOperatorChar.test(ch)) {
91 stream.eatWhile(isOperatorChar);
92 return ret("operator", null, stream.current());
93 }
94 else {
95 var word;
96 if(/[A-Z]/.test(ch))
97 {
98 stream.eatWhile(/[\w_<>]/);
99 word = stream.current();
100 return ret("type", "variable-3", word);
101 }
102 else
103 {
104 stream.eatWhile(/[\w_]/);
105 var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
106 return (known && state.kwAllowed) ? ret(known.type, known.style, word) :
107 ret("variable", "variable", word);
108 }
109 }
110 }
111
112 function haxeTokenString(quote) {
113 return function(stream, state) {
114 if (!nextUntilUnescaped(stream, quote))
115 state.tokenize = haxeTokenBase;
116 return ret("string", "string");
117 };
118 }
119
120 function haxeTokenComment(stream, state) {
121 var maybeEnd = false, ch;
122 while (ch = stream.next()) {
123 if (ch == "/" && maybeEnd) {
124 state.tokenize = haxeTokenBase;
125 break;
126 }
127 maybeEnd = (ch == "*");
128 }
129 return ret("comment", "comment");
130 }
131
132 // Parser
133
134 var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
135
136 function HaxeLexical(indented, column, type, align, prev, info) {
137 this.indented = indented;
138 this.column = column;
139 this.type = type;
140 this.prev = prev;
141 this.info = info;
142 if (align != null) this.align = align;
143 }
144
145 function inScope(state, varname) {
146 for (var v = state.localVars; v; v = v.next)
147 if (v.name == varname) return true;
148 }
149
150 function parseHaxe(state, style, type, content, stream) {
151 var cc = state.cc;
152 // Communicate our context to the combinators.
153 // (Less wasteful than consing up a hundred closures on every call.)
154 cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
155
156 if (!state.lexical.hasOwnProperty("align"))
157 state.lexical.align = true;
158
159 while(true) {
160 var combinator = cc.length ? cc.pop() : statement;
161 if (combinator(type, content)) {
162 while(cc.length && cc[cc.length - 1].lex)
163 cc.pop()();
164 if (cx.marked) return cx.marked;
165 if (type == "variable" && inScope(state, content)) return "variable-2";
166 if (type == "variable" && imported(state, content)) return "variable-3";
167 return style;
168 }
169 }
170 }
171
172 function imported(state, typename)
173 {
174 if (/[a-z]/.test(typename.charAt(0)))
175 return false;
176 var len = state.importedtypes.length;
177 for (var i = 0; i<len; i++)
178 if(state.importedtypes[i]==typename) return true;
179 }
180
181
182 function registerimport(importname) {
183 var state = cx.state;
184 for (var t = state.importedtypes; t; t = t.next)
185 if(t.name == importname) return;
186 state.importedtypes = { name: importname, next: state.importedtypes };
187 }
188 // Combinator utils
189
190 var cx = {state: null, column: null, marked: null, cc: null};
191 function pass() {
192 for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
193 }
194 function cont() {
195 pass.apply(null, arguments);
196 return true;
197 }
198 function register(varname) {
199 var state = cx.state;
200 if (state.context) {
201 cx.marked = "def";
202 for (var v = state.localVars; v; v = v.next)
203 if (v.name == varname) return;
204 state.localVars = {name: varname, next: state.localVars};
205 }
206 }
207
208 // Combinators
209
210 var defaultVars = {name: "this", next: null};
211 function pushcontext() {
212 if (!cx.state.context) cx.state.localVars = defaultVars;
213 cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
214 }
215 function popcontext() {
216 cx.state.localVars = cx.state.context.vars;
217 cx.state.context = cx.state.context.prev;
218 }
219 function pushlex(type, info) {
220 var result = function() {
221 var state = cx.state;
222 state.lexical = new HaxeLexical(state.indented, cx.stream.column(), type, null, state.lexical, info)
223 };
224 result.lex = true;
225 return result;
226 }
227 function poplex() {
228 var state = cx.state;
229 if (state.lexical.prev) {
230 if (state.lexical.type == ")")
231 state.indented = state.lexical.indented;
232 state.lexical = state.lexical.prev;
233 }
234 }
235 poplex.lex = true;
236
237 function expect(wanted) {
238 return function expecting(type) {
239 if (type == wanted) return cont();
240 else if (wanted == ";") return pass();
241 else return cont(arguments.callee);
242 };
243 }
244
245 function statement(type) {
246 if (type == "@") return cont(metadef)
247 if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
248 if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
249 if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
250 if (type == "{") return cont(pushlex("}"), pushcontext, block, poplex, popcontext);
251 if (type == ";") return cont();
252 if (type == "attribute") return cont(maybeattribute);
253 if (type == "function") return cont(functiondef);
254 if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
255 poplex, statement, poplex);
256 if (type == "variable") return cont(pushlex("stat"), maybelabel);
257 if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
258 block, poplex, poplex);
259 if (type == "case") return cont(expression, expect(":"));
260 if (type == "default") return cont(expect(":"));
261 if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
262 statement, poplex, popcontext);
263 if (type == "import") return cont(importdef, expect(";"));
264 if (type == "typedef") return cont(typedef);
265 return pass(pushlex("stat"), expression, expect(";"), poplex);
266 }
267 function expression(type) {
268 if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
269 if (type == "function") return cont(functiondef);
270 if (type == "keyword c") return cont(maybeexpression);
271 if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
272 if (type == "operator") return cont(expression);
273 if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
274 if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
275 return cont();
276 }
277 function maybeexpression(type) {
278 if (type.match(/[;\}\)\],]/)) return pass();
279 return pass(expression);
280 }
281
282 function maybeoperator(type, value) {
283 if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
284 if (type == "operator" || type == ":") return cont(expression);
285 if (type == ";") return;
286 if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
287 if (type == ".") return cont(property, maybeoperator);
288 if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
289 }
290
291 function maybeattribute(type, value) {
292 if (type == "attribute") return cont(maybeattribute);
293 if (type == "function") return cont(functiondef);
294 if (type == "var") return cont(vardef1);
295 }
296
297 function metadef(type, value) {
298 if(type == ":") return cont(metadef);
299 if(type == "variable") return cont(metadef);
300 if(type == "(") return cont(pushlex(")"), comasep(metaargs, ")"), poplex, statement)
301 }
302 function metaargs(type, value) {
303 if(typ == "variable") return cont();
304 }
305
306 function importdef (type, value) {
307 if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); }
308 else if(type == "variable" || type == "property" || type == ".") return cont(importdef);
309 }
310
311 function typedef (type, value)
312 {
313 if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); }
314 }
315
316 function maybelabel(type) {
317 if (type == ":") return cont(poplex, statement);
318 return pass(maybeoperator, expect(";"), poplex);
319 }
320 function property(type) {
321 if (type == "variable") {cx.marked = "property"; return cont();}
322 }
323 function objprop(type) {
324 if (type == "variable") cx.marked = "property";
325 if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
326 }
327 function commasep(what, end) {
328 function proceed(type) {
329 if (type == ",") return cont(what, proceed);
330 if (type == end) return cont();
331 return cont(expect(end));
332 }
333 return function commaSeparated(type) {
334 if (type == end) return cont();
335 else return pass(what, proceed);
336 };
337 }
338 function block(type) {
339 if (type == "}") return cont();
340 return pass(statement, block);
341 }
342 function vardef1(type, value) {
343 if (type == "variable"){register(value); return cont(typeuse, vardef2);}
344 return cont();
345 }
346 function vardef2(type, value) {
347 if (value == "=") return cont(expression, vardef2);
348 if (type == ",") return cont(vardef1);
349 }
350 function forspec1(type, value) {
351 if (type == "variable") {
352 register(value);
353 }
354 return cont(pushlex(")"), pushcontext, forin, expression, poplex, statement, popcontext);
355 }
356 function forin(type, value) {
357 if (value == "in") return cont();
358 }
359 function functiondef(type, value) {
360 if (type == "variable") {register(value); return cont(functiondef);}
361 if (value == "new") return cont(functiondef);
362 if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, typeuse, statement, popcontext);
363 }
364 function typeuse(type, value) {
365 if(type == ":") return cont(typestring);
366 }
367 function typestring(type, value) {
368 if(type == "type") return cont();
369 if(type == "variable") return cont();
370 if(type == "{") return cont(pushlex("}"), commasep(typeprop, "}"), poplex);
371 }
372 function typeprop(type, value) {
373 if(type == "variable") return cont(typeuse);
374 }
375 function funarg(type, value) {
376 if (type == "variable") {register(value); return cont(typeuse);}
377 }
378
379 // Interface
380
381 return {
382 startState: function(basecolumn) {
383 var defaulttypes = ["Int", "Float", "String", "Void", "Std", "Bool", "Dynamic", "Array"];
384 return {
385 tokenize: haxeTokenBase,
386 reAllowed: true,
387 kwAllowed: true,
388 cc: [],
389 lexical: new HaxeLexical((basecolumn || 0) - indentUnit, 0, "block", false),
390 localVars: parserConfig.localVars,
391 importedtypes: defaulttypes,
392 context: parserConfig.localVars && {vars: parserConfig.localVars},
393 indented: 0
394 };
395 },
396
397 token: function(stream, state) {
398 if (stream.sol()) {
399 if (!state.lexical.hasOwnProperty("align"))
400 state.lexical.align = false;
401 state.indented = stream.indentation();
402 }
403 if (stream.eatSpace()) return null;
404 var style = state.tokenize(stream, state);
405 if (type == "comment") return style;
406 state.reAllowed = !!(type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/));
407 state.kwAllowed = type != '.';
408 return parseHaxe(state, style, type, content, stream);
409 },
410
411 indent: function(state, textAfter) {
412 if (state.tokenize != haxeTokenBase) return 0;
413 var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
414 if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
415 var type = lexical.type, closing = firstChar == type;
416 if (type == "vardef") return lexical.indented + 4;
417 else if (type == "form" && firstChar == "{") return lexical.indented;
418 else if (type == "stat" || type == "form") return lexical.indented + indentUnit;
419 else if (lexical.info == "switch" && !closing)
420 return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
421 else if (lexical.align) return lexical.column + (closing ? 0 : 1);
422 else return lexical.indented + (closing ? 0 : indentUnit);
423 },
424 compareStates: function(state1, state2) {
425 return (state1.localVars == state2.localVars) && (state1.context == state2.context);
426 },
427
428 electricChars: "{}"
429 };
430 });
431
432 CodeMirror.defineMIME("text/x-haxe", "haxe");
@@ -0,0 +1,90
1 <!doctype html>
2 <html>
3 <head>
4 <title>CodeMirror: Haxe mode</title>
5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 <script src="../../lib/codemirror.js"></script>
7 <script src="haxe.js"></script>
8 <link rel="stylesheet" href="../../doc/docs.css">
9 <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
10 </head>
11 <body>
12 <h1>CodeMirror: Haxe mode</h1>
13
14 <div><textarea id="code" name="code">
15 import one.two.Three;
16
17 @attr("test")
18 class Foo&lt;T&gt; extends Three
19 {
20 public function new()
21 {
22 noFoo = 12;
23 }
24
25 public static inline function doFoo(obj:{k:Int, l:Float}):Int
26 {
27 for(i in 0...10)
28 {
29 obj.k++;
30 trace(i);
31 var var1 = new Array();
32 if(var1.length > 1)
33 throw "Error";
34 }
35 // The following line should not be colored, the variable is scoped out
36 var1;
37 /* Multi line
38 * Comment test
39 */
40 return obj.k;
41 }
42 private function bar():Void
43 {
44 #if flash
45 var t1:String = "1.21";
46 #end
47 try {
48 doFoo({k:3, l:1.2});
49 }
50 catch (e : String) {
51 trace(e);
52 }
53 var t2:Float = cast(3.2);
54 var t3:haxe.Timer = new haxe.Timer();
55 var t4 = {k:Std.int(t2), l:Std.parseFloat(t1)};
56 var t5 = ~/123+.*$/i;
57 doFoo(t4);
58 untyped t1 = 4;
59 bob = new Foo&lt;Int&gt;
60 }
61 public var okFoo(default, never):Float;
62 var noFoo(getFoo, null):Int;
63 function getFoo():Int {
64 return noFoo;
65 }
66
67 public var three:Int;
68 }
69 enum Color
70 {
71 red;
72 green;
73 blue;
74 grey( v : Int );
75 rgb (r:Int,g:Int,b:Int);
76 }
77 </textarea></div>
78
79 <script>
80 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
81 lineNumbers: true,
82 matchBrackets: true,
83 indentUnit: 4,
84 indentWithTabs: true
85 });
86 </script>
87
88 <p><strong>MIME types defined:</strong> <code>text/x-haxe</code>.</p>
89 </body>
90 </html>
@@ -0,0 +1,68
1 CodeMirror.defineMode("htmlembedded", function(config, parserConfig) {
2
3 //config settings
4 var scriptStartRegex = parserConfig.scriptStartRegex || /^<%/i,
5 scriptEndRegex = parserConfig.scriptEndRegex || /^%>/i;
6
7 //inner modes
8 var scriptingMode, htmlMixedMode;
9
10 //tokenizer when in html mode
11 function htmlDispatch(stream, state) {
12 if (stream.match(scriptStartRegex, false)) {
13 state.token=scriptingDispatch;
14 return scriptingMode.token(stream, state.scriptState);
15 }
16 else
17 return htmlMixedMode.token(stream, state.htmlState);
18 }
19
20 //tokenizer when in scripting mode
21 function scriptingDispatch(stream, state) {
22 if (stream.match(scriptEndRegex, false)) {
23 state.token=htmlDispatch;
24 return htmlMixedMode.token(stream, state.htmlState);
25 }
26 else
27 return scriptingMode.token(stream, state.scriptState);
28 }
29
30
31 return {
32 startState: function() {
33 scriptingMode = scriptingMode || CodeMirror.getMode(config, parserConfig.scriptingModeSpec);
34 htmlMixedMode = htmlMixedMode || CodeMirror.getMode(config, "htmlmixed");
35 return {
36 token : parserConfig.startOpen ? scriptingDispatch : htmlDispatch,
37 htmlState : htmlMixedMode.startState(),
38 scriptState : scriptingMode.startState()
39 }
40 },
41
42 token: function(stream, state) {
43 return state.token(stream, state);
44 },
45
46 indent: function(state, textAfter) {
47 if (state.token == htmlDispatch)
48 return htmlMixedMode.indent(state.htmlState, textAfter);
49 else
50 return scriptingMode.indent(state.scriptState, textAfter);
51 },
52
53 copyState: function(state) {
54 return {
55 token : state.token,
56 htmlState : CodeMirror.copyState(htmlMixedMode, state.htmlState),
57 scriptState : CodeMirror.copyState(scriptingMode, state.scriptState)
58 }
59 },
60
61
62 electricChars: "/{}:"
63 }
64 }, "htmlmixed");
65
66 CodeMirror.defineMIME("application/x-ejs", { name: "htmlembedded", scriptingModeSpec:"javascript"});
67 CodeMirror.defineMIME("application/x-aspx", { name: "htmlembedded", scriptingModeSpec:"text/x-csharp"});
68 CodeMirror.defineMIME("application/x-jsp", { name: "htmlembedded", scriptingModeSpec:"text/x-java"});
@@ -0,0 +1,49
1 <!doctype html>
2 <html>
3 <head>
4 <title>CodeMirror: Html Embedded Scripts mode</title>
5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 <script src="../../lib/codemirror.js"></script>
7 <script src="../xml/xml.js"></script>
8 <script src="../javascript/javascript.js"></script>
9 <script src="../css/css.js"></script>
10 <script src="../htmlmixed/htmlmixed.js"></script>
11 <script src="htmlembedded.js"></script>
12 <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
13 <link rel="stylesheet" href="../../doc/docs.css">
14 </head>
15 <body>
16 <h1>CodeMirror: Html Embedded Scripts mode</h1>
17
18 <form><textarea id="code" name="code">
19 <%
20 function hello(who) {
21 return "Hello " + who;
22 }
23 %>
24 This is an example of EJS (embedded javascript)
25 <p>The program says <%= hello("world") %>.</p>
26 <script>
27 alert("And here is some normal JS code"); // also colored
28 </script>
29 </textarea></form>
30
31 <script>
32 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
33 lineNumbers: true,
34 matchBrackets: true,
35 mode: "application/x-ejs",
36 indentUnit: 4,
37 indentWithTabs: true,
38 enterMode: "keep",
39 tabMode: "shift"
40 });
41 </script>
42
43 <p>Mode for html embedded scripts like JSP and ASP.NET. Depends on HtmlMixed which in turn depends on
44 JavaScript, CSS and XML.<br />Other dependancies include those of the scriping language chosen.</p>
45
46 <p><strong>MIME types defined:</strong> <code>application/x-aspx</code> (ASP.NET),
47 <code>application/x-ejs</code> (Embedded Javascript), <code>application/x-jsp</code> (JavaServer Pages)</p>
48 </body>
49 </html>
@@ -0,0 +1,37
1 <!doctype html>
2 <html>
3 <head>
4 <title>CodeMirror: Jinja2 mode</title>
5 <link rel="stylesheet" href="../../lib/codemirror.css">
6 <script src="../../lib/codemirror.js"></script>
7 <script src="jinja2.js"></script>
8 <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
9 <link rel="stylesheet" href="../../doc/docs.css">
10 </head>
11 <body>
12 <h1>CodeMirror: Jinja2 mode</h1>
13 <form><textarea id="code" name="code">
14 &lt;html style="color: green"&gt;
15 &lt;!-- this is a comment --&gt;
16 &lt;head&gt;
17 &lt;title&gt;Jinja2 Example&lt;/title&gt;
18 &lt;/head&gt;
19 &lt;body&gt;
20 &lt;ul&gt;
21 {# this is a comment #}
22 {%- for item in li -%}
23 &lt;li&gt;
24 {{ item.label }}
25 &lt;/li&gt;
26 {% endfor -%}
27 &lt;/ul&gt;
28 &lt;/body&gt;
29 &lt;/html&gt;
30 </textarea></form>
31 <script>
32 var editor =
33 CodeMirror.fromTextArea(document.getElementById("code"), {mode:
34 {name: "jinja2", htmlMode: true}});
35 </script>
36 </body>
37 </html>
@@ -0,0 +1,42
1 CodeMirror.defineMode("jinja2", function(config, parserConf) {
2 var keywords = ["block", "endblock", "for", "endfor", "in", "true", "false",
3 "loop", "none", "self", "super", "if", "as", "not", "and",
4 "else", "import", "with", "without", "context"];
5 keywords = new RegExp("^((" + keywords.join(")|(") + "))\\b");
6
7 function tokenBase (stream, state) {
8 var ch = stream.next();
9 if (ch == "{") {
10 if (ch = stream.eat(/\{|%|#/)) {
11 stream.eat("-");
12 state.tokenize = inTag(ch);
13 return "tag";
14 }
15 }
16 }
17 function inTag (close) {
18 if (close == "{") {
19 close = "}";
20 }
21 return function (stream, state) {
22 var ch = stream.next();
23 if ((ch == close || (ch == "-" && stream.eat(close)))
24 && stream.eat("}")) {
25 state.tokenize = tokenBase;
26 return "tag";
27 }
28 if (stream.match(keywords)) {
29 return "keyword";
30 }
31 return close == "#" ? "comment" : "string";
32 };
33 }
34 return {
35 startState: function () {
36 return {tokenize: tokenBase};
37 },
38 token: function (stream, state) {
39 return state.tokenize(stream, state);
40 }
41 };
42 });
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,23 +1,23
1 Copyright (C) 2011 by Marijn Haverbeke <marijnh@gmail.com>
1 Copyright (C) 2012 by Marijn Haverbeke <marijnh@gmail.com>
2 2
3 3 Permission is hereby granted, free of charge, to any person obtaining a copy
4 4 of this software and associated documentation files (the "Software"), to deal
5 5 in the Software without restriction, including without limitation the rights
6 6 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 7 copies of the Software, and to permit persons to whom the Software is
8 8 furnished to do so, subject to the following conditions:
9 9
10 10 The above copyright notice and this permission notice shall be included in
11 11 all copies or substantial portions of the Software.
12 12
13 13 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 14 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 15 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 16 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 17 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 18 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 19 THE SOFTWARE.
20 20
21 21 Please note that some subdirectories of the CodeMirror distribution
22 22 include their own LICENSE files, and are released under different
23 23 licences.
@@ -1,6 +1,8
1 1 # CodeMirror 2
2 2
3 CodeMirror 2 is a rewrite of [CodeMirror
4 1](http://github.com/marijnh/CodeMirror). The docs live
5 [here](http://codemirror.net/doc/manual.html), and the project page is
6 [http://codemirror.net/](http://codemirror.net/).
3 CodeMirror is a JavaScript component that provides a code editor in
4 the browser. When a mode is available for the language you are coding
5 in, it will color your code, and optionally help with indentation.
6
7 The project page is http://codemirror.net
8 The manual is at http://codemirror.net/doc/manual.html
@@ -1,107 +1,169
1 1 .CodeMirror {
2 2 line-height: 1em;
3 3 font-family: monospace;
4
5 /* Necessary so the scrollbar can be absolutely positioned within the wrapper on Lion. */
6 position: relative;
7 /* This prevents unwanted scrollbars from showing up on the body and wrapper in IE. */
8 overflow: hidden;
4 9 }
5 10
6 11 .CodeMirror-scroll {
7 overflow: auto;
12 overflow-x: auto;
13 overflow-y: hidden;
8 14 height: 300px;
9 15 /* This is needed to prevent an IE[67] bug where the scrolled content
10 16 is visible outside of the scrolling box. */
11 17 position: relative;
18 outline: none;
19 }
20
21 /* Vertical scrollbar */
22 .CodeMirror-scrollbar {
23 float: right;
24 overflow-x: hidden;
25 overflow-y: scroll;
26
27 /* This corrects for the 1px gap introduced to the left of the scrollbar
28 by the rule for .CodeMirror-scrollbar-inner. */
29 margin-left: -1px;
30 }
31 .CodeMirror-scrollbar-inner {
32 /* This needs to have a nonzero width in order for the scrollbar to appear
33 in Firefox and IE9. */
34 width: 1px;
35 }
36 .CodeMirror-scrollbar.cm-sb-overlap {
37 /* Ensure that the scrollbar appears in Lion, and that it overlaps the content
38 rather than sitting to the right of it. */
39 position: absolute;
40 z-index: 1;
41 float: none;
42 right: 0;
43 min-width: 12px;
44 }
45 .CodeMirror-scrollbar.cm-sb-nonoverlap {
46 min-width: 12px;
47 }
48 .CodeMirror-scrollbar.cm-sb-ie7 {
49 min-width: 18px;
12 50 }
13 51
14 52 .CodeMirror-gutter {
15 53 position: absolute; left: 0; top: 0;
16 54 z-index: 10;
17 55 background-color: #f7f7f7;
18 56 border-right: 1px solid #eee;
19 57 min-width: 2em;
20 58 height: 100%;
21 59 }
22 60 .CodeMirror-gutter-text {
23 61 color: #aaa;
24 62 text-align: right;
25 63 padding: .4em .2em .4em .4em;
26 64 white-space: pre !important;
65 cursor: default;
27 66 }
28 67 .CodeMirror-lines {
29 68 padding: .4em;
69 white-space: pre;
70 cursor: text;
71 }
72 .CodeMirror-lines * {
73 /* Necessary for throw-scrolling to decelerate properly on Safari. */
74 pointer-events: none;
30 75 }
31 76
32 77 .CodeMirror pre {
33 78 -moz-border-radius: 0;
34 79 -webkit-border-radius: 0;
35 80 -o-border-radius: 0;
36 81 border-radius: 0;
37 82 border-width: 0; margin: 0; padding: 0; background: transparent;
38 83 font-family: inherit;
39 84 font-size: inherit;
40 85 padding: 0; margin: 0;
41 86 white-space: pre;
42 87 word-wrap: normal;
88 line-height: inherit;
89 color: inherit;
43 90 }
44 91
45 92 .CodeMirror-wrap pre {
46 93 word-wrap: break-word;
47 94 white-space: pre-wrap;
95 word-break: normal;
48 96 }
49 97 .CodeMirror-wrap .CodeMirror-scroll {
50 98 overflow-x: hidden;
51 99 }
52 100
53 101 .CodeMirror textarea {
54 102 outline: none !important;
55 103 }
56 104
57 105 .CodeMirror pre.CodeMirror-cursor {
58 106 z-index: 10;
59 107 position: absolute;
60 108 visibility: hidden;
61 109 border-left: 1px solid black;
110 border-right: none;
111 width: 0;
112 }
113 .cm-keymap-fat-cursor pre.CodeMirror-cursor {
114 width: auto;
115 border: 0;
116 background: transparent;
117 background: rgba(0, 200, 0, .4);
118 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800);
119 }
120 /* Kludge to turn off filter in ie9+, which also accepts rgba */
121 .cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) {
122 filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
62 123 }
124 .CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {}
63 125 .CodeMirror-focused pre.CodeMirror-cursor {
64 126 visibility: visible;
65 127 }
66 128
67 129 div.CodeMirror-selected { background: #d9d9d9; }
68 130 .CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; }
69 131
70 132 .CodeMirror-searching {
71 133 background: #ffa;
72 134 background: rgba(255, 255, 0, .4);
73 135 }
74 136
75 137 /* Default theme */
76 138
77 139 .cm-s-default span.cm-keyword {color: #708;}
78 140 .cm-s-default span.cm-atom {color: #219;}
79 141 .cm-s-default span.cm-number {color: #164;}
80 142 .cm-s-default span.cm-def {color: #00f;}
81 143 .cm-s-default span.cm-variable {color: black;}
82 144 .cm-s-default span.cm-variable-2 {color: #05a;}
83 145 .cm-s-default span.cm-variable-3 {color: #085;}
84 146 .cm-s-default span.cm-property {color: black;}
85 147 .cm-s-default span.cm-operator {color: black;}
86 148 .cm-s-default span.cm-comment {color: #a50;}
87 149 .cm-s-default span.cm-string {color: #a11;}
88 150 .cm-s-default span.cm-string-2 {color: #f50;}
89 151 .cm-s-default span.cm-meta {color: #555;}
90 152 .cm-s-default span.cm-error {color: #f00;}
91 153 .cm-s-default span.cm-qualifier {color: #555;}
92 154 .cm-s-default span.cm-builtin {color: #30a;}
93 155 .cm-s-default span.cm-bracket {color: #cc7;}
94 156 .cm-s-default span.cm-tag {color: #170;}
95 157 .cm-s-default span.cm-attribute {color: #00c;}
96 .cm-s-default span.cm-header {color: #a0a;}
158 .cm-s-default span.cm-header {color: blue;}
97 159 .cm-s-default span.cm-quote {color: #090;}
98 160 .cm-s-default span.cm-hr {color: #999;}
99 161 .cm-s-default span.cm-link {color: #00c;}
100 162
101 163 span.cm-header, span.cm-strong {font-weight: bold;}
102 164 span.cm-em {font-style: italic;}
103 165 span.cm-emstrong {font-style: italic; font-weight: bold;}
104 166 span.cm-link {text-decoration: underline;}
105 167
106 168 div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
107 169 div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
This diff has been collapsed as it changes many lines, (1180 lines changed) Show them Hide them
@@ -1,2817 +1,3229
1 1 // All functions that need access to the editor's state live inside
2 2 // the CodeMirror function. Below that, at the bottom of the file,
3 3 // some utilities are defined.
4 4
5 5 // CodeMirror is the only global var we claim
6 6 var CodeMirror = (function() {
7 // This is the function that produces an editor instance. It's
7 // This is the function that produces an editor instance. Its
8 8 // closure is used to store the editor state.
9 9 function CodeMirror(place, givenOptions) {
10 10 // Determine effective options based on given values and defaults.
11 11 var options = {}, defaults = CodeMirror.defaults;
12 12 for (var opt in defaults)
13 13 if (defaults.hasOwnProperty(opt))
14 14 options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt];
15 15
16 var targetDocument = options["document"];
17 16 // The element in which the editor lives.
18 var wrapper = targetDocument.createElement("div");
17 var wrapper = document.createElement("div");
19 18 wrapper.className = "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : "");
20 19 // This mess creates the base DOM structure for the editor.
21 20 wrapper.innerHTML =
22 21 '<div style="overflow: hidden; position: relative; width: 3px; height: 0px;">' + // Wraps and hides input textarea
23 22 '<textarea style="position: absolute; padding: 0; width: 1px; height: 1em" wrap="off" ' +
24 23 'autocorrect="off" autocapitalize="off"></textarea></div>' +
24 '<div class="CodeMirror-scrollbar">' + // The vertical scrollbar. Horizontal scrolling is handled by the scroller itself.
25 '<div class="CodeMirror-scrollbar-inner">' + // The empty scrollbar content, used solely for managing the scrollbar thumb.
26 '</div></div>' + // This must be before the scroll area because it's float-right.
25 27 '<div class="CodeMirror-scroll" tabindex="-1">' +
26 28 '<div style="position: relative">' + // Set to the height of the text, causes scrolling
27 29 '<div style="position: relative">' + // Moved around its parent to cover visible view
28 30 '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
29 31 // Provides positioning relative to (visible) text origin
30 32 '<div class="CodeMirror-lines"><div style="position: relative; z-index: 0">' +
31 '<div style="position: absolute; width: 100%; height: 0; overflow: hidden; visibility: hidden; outline: 5px auto none"></div>' +
33 // Used to measure text size
34 '<div style="position: absolute; width: 100%; height: 0px; overflow: hidden; visibility: hidden;"></div>' +
32 35 '<pre class="CodeMirror-cursor">&#160;</pre>' + // Absolutely positioned blinky cursor
36 '<pre class="CodeMirror-cursor" style="visibility: hidden">&#160;</pre>' + // Used to force a width
33 37 '<div style="position: relative; z-index: -1"></div><div></div>' + // DIVs containing the selection and the actual code
34 38 '</div></div></div></div></div>';
35 39 if (place.appendChild) place.appendChild(wrapper); else place(wrapper);
36 40 // I've never seen more elegant code in my life.
37 41 var inputDiv = wrapper.firstChild, input = inputDiv.firstChild,
38 42 scroller = wrapper.lastChild, code = scroller.firstChild,
39 43 mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild,
40 44 lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild,
41 cursor = measure.nextSibling, selectionDiv = cursor.nextSibling,
42 lineDiv = selectionDiv.nextSibling;
43 themeChanged();
45 cursor = measure.nextSibling, widthForcer = cursor.nextSibling,
46 selectionDiv = widthForcer.nextSibling, lineDiv = selectionDiv.nextSibling,
47 scrollbar = inputDiv.nextSibling, scrollbarInner = scrollbar.firstChild;
48 themeChanged(); keyMapChanged();
44 49 // Needed to hide big blue blinking cursor on Mobile Safari
45 50 if (ios) input.style.width = "0px";
46 if (!webkit) lineSpace.draggable = true;
51 if (!webkit) scroller.draggable = true;
47 52 lineSpace.style.outline = "none";
48 53 if (options.tabindex != null) input.tabIndex = options.tabindex;
54 if (options.autofocus) focusInput();
49 55 if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
56 // Needed to handle Tab key in KHTML
57 if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute";
58
59 // Check for OS X >= 10.7. If so, we need to force a width on the scrollbar, and
60 // make it overlap the content. (But we only do this if the scrollbar doesn't already
61 // have a natural width. If the mouse is plugged in or the user sets the system pref
62 // to always show scrollbars, the scrollbar shouldn't overlap.)
63 if (mac_geLion) {
64 scrollbar.className += (overlapScrollbars() ? " cm-sb-overlap" : " cm-sb-nonoverlap");
65 } else if (ie_lt8) {
66 // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
67 scrollbar.className += " cm-sb-ie7";
68 }
50 69
51 70 // Check for problem with IE innerHTML not working when we have a
52 71 // P (or similar) parent node.
53 72 try { stringWidth("x"); }
54 73 catch (e) {
55 74 if (e.message.match(/runtime/i))
56 75 e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)");
57 76 throw e;
58 77 }
59 78
60 79 // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.
61 80 var poll = new Delayed(), highlight = new Delayed(), blinker;
62 81
63 82 // mode holds a mode API object. doc is the tree of Line objects,
64 83 // work an array of lines that should be parsed, and history the
65 84 // undo history (instance of History constructor).
66 85 var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused;
67 86 loadMode();
68 87 // The selection. These are always maintained to point at valid
69 88 // positions. Inverted is used to remember that the user is
70 89 // selecting bottom-to-top.
71 90 var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false};
72 91 // Selection-related flags. shiftSelecting obviously tracks
73 92 // whether the user is holding shift.
74 var shiftSelecting, lastClick, lastDoubleClick, lastScrollPos = 0, draggingText,
93 var shiftSelecting, lastClick, lastDoubleClick, lastScrollTop = 0, lastScrollLeft = 0, draggingText,
75 94 overwrite = false, suppressEdits = false;
76 95 // Variables used by startOperation/endOperation to track what
77 96 // happened during the operation.
78 97 var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone,
79 98 gutterDirty, callbacks;
80 99 // Current visible range (may be bigger than the view window).
81 100 var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0;
82 // bracketHighlighted is used to remember that a backet has been
101 // bracketHighlighted is used to remember that a bracket has been
83 102 // marked.
84 103 var bracketHighlighted;
85 104 // Tracks the maximum line length so that the horizontal scrollbar
86 105 // can be kept static when scrolling.
87 var maxLine = "", maxWidth, tabText = computeTabText();
106 var maxLine = "", updateMaxLine = false, maxLineChanged = true;
107 var tabCache = {};
88 108
89 109 // Initialize the content.
90 110 operation(function(){setValue(options.value || ""); updateInput = false;})();
91 111 var history = new History();
92 112
93 113 // Register our event handlers.
94 114 connect(scroller, "mousedown", operation(onMouseDown));
95 115 connect(scroller, "dblclick", operation(onDoubleClick));
96 connect(lineSpace, "dragstart", onDragStart);
97 116 connect(lineSpace, "selectstart", e_preventDefault);
98 117 // Gecko browsers fire contextmenu *after* opening the menu, at
99 118 // which point we can't mess with it anymore. Context menu is
100 119 // handled in onMouseDown for Gecko.
101 120 if (!gecko) connect(scroller, "contextmenu", onContextMenu);
102 connect(scroller, "scroll", function() {
103 lastScrollPos = scroller.scrollTop;
104 updateDisplay([]);
105 if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px";
106 if (options.onScroll) options.onScroll(instance);
107 });
121 connect(scroller, "scroll", onScroll);
122 connect(scrollbar, "scroll", onScroll);
123 connect(scrollbar, "mousedown", function() {if (focused) setTimeout(focusInput, 0);});
124 connect(scroller, "mousewheel", onMouseWheel);
125 connect(scroller, "DOMMouseScroll", onMouseWheel);
108 126 connect(window, "resize", function() {updateDisplay(true);});
109 127 connect(input, "keyup", operation(onKeyUp));
110 128 connect(input, "input", fastPoll);
111 129 connect(input, "keydown", operation(onKeyDown));
112 130 connect(input, "keypress", operation(onKeyPress));
113 131 connect(input, "focus", onFocus);
114 132 connect(input, "blur", onBlur);
115 133
116 connect(scroller, "dragenter", e_stop);
117 connect(scroller, "dragover", e_stop);
118 connect(scroller, "drop", operation(onDrop));
134 if (options.dragDrop) {
135 connect(scroller, "dragstart", onDragStart);
136 function drag_(e) {
137 if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return;
138 e_stop(e);
139 }
140 connect(scroller, "dragenter", drag_);
141 connect(scroller, "dragover", drag_);
142 connect(scroller, "drop", operation(onDrop));
143 }
119 144 connect(scroller, "paste", function(){focusInput(); fastPoll();});
120 145 connect(input, "paste", fastPoll);
121 146 connect(input, "cut", operation(function(){
122 147 if (!options.readOnly) replaceSelection("");
123 148 }));
124 149
150 // Needed to handle Tab key in KHTML
151 if (khtml) connect(code, "mouseup", function() {
152 if (document.activeElement == input) input.blur();
153 focusInput();
154 });
155
125 156 // IE throws unspecified error in certain cases, when
126 157 // trying to access activeElement before onload
127 var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { }
128 if (hasFocus) setTimeout(onFocus, 20);
158 var hasFocus; try { hasFocus = (document.activeElement == input); } catch(e) { }
159 if (hasFocus || options.autofocus) setTimeout(onFocus, 20);
129 160 else onBlur();
130 161
131 162 function isLine(l) {return l >= 0 && l < doc.size;}
132 163 // The instance object that we'll return. Mostly calls out to
133 164 // local functions in the CodeMirror function. Some do some extra
134 165 // range checking and/or clipping. operation is used to wrap the
135 166 // call so that changes it makes are tracked, and the display is
136 167 // updated afterwards.
137 168 var instance = wrapper.CodeMirror = {
138 169 getValue: getValue,
139 170 setValue: operation(setValue),
140 171 getSelection: getSelection,
141 172 replaceSelection: operation(replaceSelection),
142 focus: function(){focusInput(); onFocus(); fastPoll();},
173 focus: function(){window.focus(); focusInput(); onFocus(); fastPoll();},
143 174 setOption: function(option, value) {
144 175 var oldVal = options[option];
145 176 options[option] = value;
146 177 if (option == "mode" || option == "indentUnit") loadMode();
147 178 else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();}
148 179 else if (option == "readOnly" && !value) {resetInput(true);}
149 180 else if (option == "theme") themeChanged();
150 181 else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)();
151 else if (option == "tabSize") operation(tabsChanged)();
182 else if (option == "tabSize") updateDisplay(true);
183 else if (option == "keyMap") keyMapChanged();
152 184 if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme") {
153 185 gutterChanged();
154 186 updateDisplay(true);
155 187 }
156 188 },
157 189 getOption: function(option) {return options[option];},
158 190 undo: operation(undo),
159 191 redo: operation(redo),
160 192 indentLine: operation(function(n, dir) {
161 193 if (typeof dir != "string") {
162 194 if (dir == null) dir = options.smartIndent ? "smart" : "prev";
163 195 else dir = dir ? "add" : "subtract";
164 196 }
165 197 if (isLine(n)) indentLine(n, dir);
166 198 }),
167 199 indentSelection: operation(indentSelected),
168 200 historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
169 201 clearHistory: function() {history = new History();},
202 setHistory: function(histData) {
203 history = new History();
204 history.done = histData.done;
205 history.undone = histData.undone;
206 },
207 getHistory: function() {
208 history.time = 0;
209 return {done: history.done.concat([]), undone: history.undone.concat([])};
210 },
170 211 matchBrackets: operation(function(){matchBrackets(true);}),
171 212 getTokenAt: operation(function(pos) {
172 213 pos = clipPos(pos);
173 214 return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), pos.ch);
174 215 }),
175 216 getStateAfter: function(line) {
176 217 line = clipLine(line == null ? doc.size - 1: line);
177 218 return getStateBefore(line + 1);
178 219 },
179 cursorCoords: function(start){
220 cursorCoords: function(start, mode) {
180 221 if (start == null) start = sel.inverted;
181 return pageCoords(start ? sel.from : sel.to);
222 return this.charCoords(start ? sel.from : sel.to, mode);
223 },
224 charCoords: function(pos, mode) {
225 pos = clipPos(pos);
226 if (mode == "local") return localCoords(pos, false);
227 if (mode == "div") return localCoords(pos, true);
228 return pageCoords(pos);
182 229 },
183 charCoords: function(pos){return pageCoords(clipPos(pos));},
184 230 coordsChar: function(coords) {
185 231 var off = eltOffset(lineSpace);
186 232 return coordsChar(coords.x - off.left, coords.y - off.top);
187 233 },
188 234 markText: operation(markText),
189 235 setBookmark: setBookmark,
236 findMarksAt: findMarksAt,
190 237 setMarker: operation(addGutterMarker),
191 238 clearMarker: operation(removeGutterMarker),
192 239 setLineClass: operation(setLineClass),
193 240 hideLine: operation(function(h) {return setLineHidden(h, true);}),
194 241 showLine: operation(function(h) {return setLineHidden(h, false);}),
195 242 onDeleteLine: function(line, f) {
196 243 if (typeof line == "number") {
197 244 if (!isLine(line)) return null;
198 245 line = getLine(line);
199 246 }
200 247 (line.handlers || (line.handlers = [])).push(f);
201 248 return line;
202 249 },
203 250 lineInfo: lineInfo,
204 251 addWidget: function(pos, node, scroll, vert, horiz) {
205 252 pos = localCoords(clipPos(pos));
206 253 var top = pos.yBot, left = pos.x;
207 254 node.style.position = "absolute";
208 255 code.appendChild(node);
209 256 if (vert == "over") top = pos.y;
210 257 else if (vert == "near") {
211 258 var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()),
212 259 hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft();
213 260 if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight)
214 261 top = pos.y - node.offsetHeight;
215 262 if (left + node.offsetWidth > hspace)
216 263 left = hspace - node.offsetWidth;
217 264 }
218 265 node.style.top = (top + paddingTop()) + "px";
219 266 node.style.left = node.style.right = "";
220 267 if (horiz == "right") {
221 268 left = code.clientWidth - node.offsetWidth;
222 269 node.style.right = "0px";
223 270 } else {
224 271 if (horiz == "left") left = 0;
225 272 else if (horiz == "middle") left = (code.clientWidth - node.offsetWidth) / 2;
226 273 node.style.left = (left + paddingLeft()) + "px";
227 274 }
228 275 if (scroll)
229 276 scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);
230 277 },
231 278
232 279 lineCount: function() {return doc.size;},
233 280 clipPos: clipPos,
234 281 getCursor: function(start) {
235 282 if (start == null) start = sel.inverted;
236 283 return copyPos(start ? sel.from : sel.to);
237 284 },
238 285 somethingSelected: function() {return !posEq(sel.from, sel.to);},
239 286 setCursor: operation(function(line, ch, user) {
240 287 if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user);
241 288 else setCursor(line, ch, user);
242 289 }),
243 290 setSelection: operation(function(from, to, user) {
244 291 (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from));
245 292 }),
246 293 getLine: function(line) {if (isLine(line)) return getLine(line).text;},
247 294 getLineHandle: function(line) {if (isLine(line)) return getLine(line);},
248 295 setLine: operation(function(line, text) {
249 296 if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
250 297 }),
251 298 removeLine: operation(function(line) {
252 299 if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0}));
253 300 }),
254 301 replaceRange: operation(replaceRange),
255 getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));},
302 getRange: function(from, to, lineSep) {return getRange(clipPos(from), clipPos(to), lineSep);},
256 303
304 triggerOnKeyDown: operation(onKeyDown),
257 305 execCommand: function(cmd) {return commands[cmd](instance);},
258 306 // Stuff used by commands, probably not much use to outside code.
259 307 moveH: operation(moveH),
260 308 deleteH: operation(deleteH),
261 309 moveV: operation(moveV),
262 toggleOverwrite: function() {overwrite = !overwrite;},
310 toggleOverwrite: function() {
311 if(overwrite){
312 overwrite = false;
313 cursor.className = cursor.className.replace(" CodeMirror-overwrite", "");
314 } else {
315 overwrite = true;
316 cursor.className += " CodeMirror-overwrite";
317 }
318 },
263 319
264 320 posFromIndex: function(off) {
265 321 var lineNo = 0, ch;
266 322 doc.iter(0, doc.size, function(line) {
267 323 var sz = line.text.length + 1;
268 324 if (sz > off) { ch = off; return true; }
269 325 off -= sz;
270 326 ++lineNo;
271 327 });
272 328 return clipPos({line: lineNo, ch: ch});
273 329 },
274 330 indexFromPos: function (coords) {
275 331 if (coords.line < 0 || coords.ch < 0) return 0;
276 332 var index = coords.ch;
277 333 doc.iter(0, coords.line, function (line) {
278 334 index += line.text.length + 1;
279 335 });
280 336 return index;
281 337 },
282 338 scrollTo: function(x, y) {
283 339 if (x != null) scroller.scrollLeft = x;
284 if (y != null) scroller.scrollTop = y;
340 if (y != null) scrollbar.scrollTop = y;
285 341 updateDisplay([]);
286 342 },
343 getScrollInfo: function() {
344 return {x: scroller.scrollLeft, y: scrollbar.scrollTop,
345 height: scrollbar.scrollHeight, width: scroller.scrollWidth};
346 },
347 setSize: function(width, height) {
348 function interpret(val) {
349 val = String(val);
350 return /^\d+$/.test(val) ? val + "px" : val;
351 }
352 if (width != null) wrapper.style.width = interpret(width);
353 if (height != null) scroller.style.height = interpret(height);
354 },
287 355
288 356 operation: function(f){return operation(f)();},
357 compoundChange: function(f){return compoundChange(f);},
289 358 refresh: function(){
290 updateDisplay(true);
291 if (scroller.scrollHeight > lastScrollPos)
292 scroller.scrollTop = lastScrollPos;
359 updateDisplay(true, null, lastScrollTop);
360 if (scrollbar.scrollHeight > lastScrollTop)
361 scrollbar.scrollTop = lastScrollTop;
293 362 },
294 363 getInputField: function(){return input;},
295 364 getWrapperElement: function(){return wrapper;},
296 365 getScrollerElement: function(){return scroller;},
297 366 getGutterElement: function(){return gutter;}
298 367 };
299 368
300 369 function getLine(n) { return getLineAt(doc, n); }
301 370 function updateLineHeight(line, height) {
302 371 gutterDirty = true;
303 372 var diff = height - line.height;
304 373 for (var n = line; n; n = n.parent) n.height += diff;
305 374 }
306 375
307 376 function setValue(code) {
308 377 var top = {line: 0, ch: 0};
309 378 updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length},
310 379 splitLines(code), top, top);
311 380 updateInput = true;
312 381 }
313 function getValue(code) {
382 function getValue(lineSep) {
314 383 var text = [];
315 384 doc.iter(0, doc.size, function(line) { text.push(line.text); });
316 return text.join("\n");
385 return text.join(lineSep || "\n");
386 }
387
388 function onScroll(e) {
389 if (scroller.scrollTop) {
390 scrollbar.scrollTop += scroller.scrollTop;
391 scroller.scrollTop = 0;
392 }
393 if (lastScrollTop != scrollbar.scrollTop || lastScrollLeft != scroller.scrollLeft) {
394 lastScrollTop = scrollbar.scrollTop;
395 lastScrollLeft = scroller.scrollLeft;
396 updateDisplay([]);
397 if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px";
398 if (options.onScroll) options.onScroll(instance);
399 }
317 400 }
318 401
319 402 function onMouseDown(e) {
320 403 setShift(e_prop(e, "shiftKey"));
321 404 // Check whether this is a click in a widget
322 405 for (var n = e_target(e); n != wrapper; n = n.parentNode)
323 406 if (n.parentNode == code && n != mover) return;
324 407
325 408 // See if this is a click in the gutter
326 409 for (var n = e_target(e); n != wrapper; n = n.parentNode)
327 410 if (n.parentNode == gutterText) {
328 411 if (options.onGutterClick)
329 412 options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e);
330 413 return e_preventDefault(e);
331 414 }
332 415
333 416 var start = posFromMouse(e);
334 417
335 418 switch (e_button(e)) {
336 419 case 3:
337 if (gecko && !mac) onContextMenu(e);
420 if (gecko) onContextMenu(e);
338 421 return;
339 422 case 2:
340 423 if (start) setCursor(start.line, start.ch, true);
424 setTimeout(focusInput, 20);
425 e_preventDefault(e);
341 426 return;
342 427 }
343 428 // For button 1, if it was clicked inside the editor
344 429 // (posFromMouse returning non-null), we have to adjust the
345 430 // selection.
346 431 if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;}
347 432
348 433 if (!focused) onFocus();
349 434
350 var now = +new Date;
435 var now = +new Date, type = "single";
351 436 if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
437 type = "triple";
352 438 e_preventDefault(e);
353 439 setTimeout(focusInput, 20);
354 return selectLine(start.line);
440 selectLine(start.line);
355 441 } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
442 type = "double";
356 443 lastDoubleClick = {time: now, pos: start};
357 444 e_preventDefault(e);
358 return selectWordAt(start);
445 var word = findWordAt(start);
446 setSelectionUser(word.from, word.to);
359 447 } else { lastClick = {time: now, pos: start}; }
360 448
361 449 var last = start, going;
362 if (dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) &&
363 !posLess(start, sel.from) && !posLess(sel.to, start)) {
450 if (options.dragDrop && dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) &&
451 !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") {
364 452 // Let the drag handler handle this.
365 if (webkit) lineSpace.draggable = true;
366 var up = connect(targetDocument, "mouseup", operation(function(e2) {
367 if (webkit) lineSpace.draggable = false;
453 if (webkit) scroller.draggable = true;
454 function dragEnd(e2) {
455 if (webkit) scroller.draggable = false;
368 456 draggingText = false;
369 up();
457 up(); drop();
370 458 if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
371 459 e_preventDefault(e2);
372 460 setCursor(start.line, start.ch, true);
373 461 focusInput();
374 462 }
375 }), true);
463 }
464 var up = connect(document, "mouseup", operation(dragEnd), true);
465 var drop = connect(scroller, "drop", operation(dragEnd), true);
376 466 draggingText = true;
467 // IE's approach to draggable
468 if (scroller.dragDrop) scroller.dragDrop();
377 469 return;
378 470 }
379 471 e_preventDefault(e);
380 setCursor(start.line, start.ch, true);
472 if (type == "single") setCursor(start.line, start.ch, true);
473
474 var startstart = sel.from, startend = sel.to;
475
476 function doSelect(cur) {
477 if (type == "single") {
478 setSelectionUser(start, cur);
479 } else if (type == "double") {
480 var word = findWordAt(cur);
481 if (posLess(cur, startstart)) setSelectionUser(word.from, startend);
482 else setSelectionUser(startstart, word.to);
483 } else if (type == "triple") {
484 if (posLess(cur, startstart)) setSelectionUser(startend, clipPos({line: cur.line, ch: 0}));
485 else setSelectionUser(startstart, clipPos({line: cur.line + 1, ch: 0}));
486 }
487 }
381 488
382 489 function extend(e) {
383 490 var cur = posFromMouse(e, true);
384 491 if (cur && !posEq(cur, last)) {
385 492 if (!focused) onFocus();
386 493 last = cur;
387 setSelectionUser(start, cur);
494 doSelect(cur);
388 495 updateInput = false;
389 496 var visible = visibleLines();
390 497 if (cur.line >= visible.to || cur.line < visible.from)
391 498 going = setTimeout(operation(function(){extend(e);}), 150);
392 499 }
393 500 }
394 501
395 var move = connect(targetDocument, "mousemove", operation(function(e) {
396 clearTimeout(going);
397 e_preventDefault(e);
398 extend(e);
399 }), true);
400 var up = connect(targetDocument, "mouseup", operation(function(e) {
502 function done(e) {
401 503 clearTimeout(going);
402 504 var cur = posFromMouse(e);
403 if (cur) setSelectionUser(start, cur);
505 if (cur) doSelect(cur);
404 506 e_preventDefault(e);
405 507 focusInput();
406 508 updateInput = true;
407 509 move(); up();
510 }
511 var move = connect(document, "mousemove", operation(function(e) {
512 clearTimeout(going);
513 e_preventDefault(e);
514 if (!ie && !e_button(e)) done(e);
515 else extend(e);
408 516 }), true);
517 var up = connect(document, "mouseup", operation(done), true);
409 518 }
410 519 function onDoubleClick(e) {
411 520 for (var n = e_target(e); n != wrapper; n = n.parentNode)
412 521 if (n.parentNode == gutterText) return e_preventDefault(e);
413 var start = posFromMouse(e);
414 if (!start) return;
415 lastDoubleClick = {time: +new Date, pos: start};
416 522 e_preventDefault(e);
417 selectWordAt(start);
418 523 }
419 524 function onDrop(e) {
525 if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return;
420 526 e.preventDefault();
421 527 var pos = posFromMouse(e, true), files = e.dataTransfer.files;
422 528 if (!pos || options.readOnly) return;
423 529 if (files && files.length && window.FileReader && window.File) {
424 530 function loadFile(file, i) {
425 531 var reader = new FileReader;
426 532 reader.onload = function() {
427 533 text[i] = reader.result;
428 534 if (++read == n) {
429 pos = clipPos(pos);
430 operation(function() {
535 pos = clipPos(pos);
536 operation(function() {
431 537 var end = replaceRange(text.join(""), pos, pos);
432 538 setSelectionUser(pos, end);
433 539 })();
434 }
540 }
435 541 };
436 542 reader.readAsText(file);
437 543 }
438 544 var n = files.length, text = Array(n), read = 0;
439 545 for (var i = 0; i < n; ++i) loadFile(files[i], i);
440 }
441 else {
546 } else {
547 // Don't do a replace if the drop happened inside of the selected text.
548 if (draggingText && !(posLess(pos, sel.from) || posLess(sel.to, pos))) return;
442 549 try {
443 550 var text = e.dataTransfer.getData("Text");
444 551 if (text) {
445 var curFrom = sel.from, curTo = sel.to;
446 setSelectionUser(pos, pos);
447 if (draggingText) replaceRange("", curFrom, curTo);
448 replaceSelection(text);
449 focusInput();
450 }
552 compoundChange(function() {
553 var curFrom = sel.from, curTo = sel.to;
554 setSelectionUser(pos, pos);
555 if (draggingText) replaceRange("", curFrom, curTo);
556 replaceSelection(text);
557 focusInput();
558 });
559 }
451 560 }
452 561 catch(e){}
453 562 }
454 563 }
455 564 function onDragStart(e) {
456 565 var txt = getSelection();
457 // This will reset escapeElement
458 htmlEscape(txt);
459 e.dataTransfer.setDragImage(escapeElement, 0, 0);
460 566 e.dataTransfer.setData("Text", txt);
461 }
462 function handleKeyBinding(e) {
463 var name = keyNames[e_prop(e, "keyCode")], next = keyMap[options.keyMap].auto, bound, dropShift;
464 function handleNext() {
465 return next.call ? next.call(null, instance) : next;
466 }
467 if (name == null || e.altGraphKey) {
468 if (next) options.keyMap = handleNext();
469 return null;
470 }
471 if (e_prop(e, "altKey")) name = "Alt-" + name;
472 if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name;
473 if (e_prop(e, "metaKey")) name = "Cmd-" + name;
474 if (e_prop(e, "shiftKey") &&
475 (bound = lookupKey("Shift-" + name, options.extraKeys, options.keyMap))) {
476 dropShift = true;
477 } else {
478 bound = lookupKey(name, options.extraKeys, options.keyMap);
567
568 // Use dummy image instead of default browsers image.
569 if (gecko || chrome || opera) {
570 var img = document.createElement('img');
571 img.scr = 'data:image/gif;base64,R0lGODdhAgACAIAAAAAAAP///ywAAAAAAgACAAACAoRRADs='; //1x1 image
572 e.dataTransfer.setDragImage(img, 0, 0);
479 573 }
574 }
575
576 function doHandleBinding(bound, dropShift) {
480 577 if (typeof bound == "string") {
481 if (commands.propertyIsEnumerable(bound)) bound = commands[bound];
482 else bound = null;
578 bound = commands[bound];
579 if (!bound) return false;
483 580 }
484 if (next && (bound || !isModifierKey(e))) options.keyMap = handleNext();
485 if (!bound) return false;
486 581 var prevShift = shiftSelecting;
487 582 try {
488 583 if (options.readOnly) suppressEdits = true;
489 584 if (dropShift) shiftSelecting = null;
490 585 bound(instance);
586 } catch(e) {
587 if (e != Pass) throw e;
588 return false;
491 589 } finally {
492 590 shiftSelecting = prevShift;
493 591 suppressEdits = false;
494 592 }
495 e_preventDefault(e);
496 593 return true;
497 594 }
498 var lastStoppedKey = null;
595 function handleKeyBinding(e) {
596 // Handle auto keymap transitions
597 var startMap = getKeyMap(options.keyMap), next = startMap.auto;
598 clearTimeout(maybeTransition);
599 if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
600 if (getKeyMap(options.keyMap) == startMap) {
601 options.keyMap = (next.call ? next.call(null, instance) : next);
602 }
603 }, 50);
604
605 var name = keyNames[e_prop(e, "keyCode")], handled = false;
606 if (name == null || e.altGraphKey) return false;
607 if (e_prop(e, "altKey")) name = "Alt-" + name;
608 if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name;
609 if (e_prop(e, "metaKey")) name = "Cmd-" + name;
610
611 var stopped = false;
612 function stop() { stopped = true; }
613
614 if (e_prop(e, "shiftKey")) {
615 handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap,
616 function(b) {return doHandleBinding(b, true);}, stop)
617 || lookupKey(name, options.extraKeys, options.keyMap, function(b) {
618 if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(b);
619 }, stop);
620 } else {
621 handled = lookupKey(name, options.extraKeys, options.keyMap, doHandleBinding, stop);
622 }
623 if (stopped) handled = false;
624 if (handled) {
625 e_preventDefault(e);
626 restartBlink();
627 if (ie) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
628 }
629 return handled;
630 }
631 function handleCharBinding(e, ch) {
632 var handled = lookupKey("'" + ch + "'", options.extraKeys,
633 options.keyMap, function(b) { return doHandleBinding(b, true); });
634 if (handled) {
635 e_preventDefault(e);
636 restartBlink();
637 }
638 return handled;
639 }
640
641 var lastStoppedKey = null, maybeTransition;
499 642 function onKeyDown(e) {
500 643 if (!focused) onFocus();
501 644 if (ie && e.keyCode == 27) { e.returnValue = false; }
645 if (pollingFast) { if (readInput()) pollingFast = false; }
502 646 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
503 647 var code = e_prop(e, "keyCode");
504 648 // IE does strange things with escape.
505 649 setShift(code == 16 || e_prop(e, "shiftKey"));
506 650 // First give onKeyEvent option a chance to handle this.
507 651 var handled = handleKeyBinding(e);
508 if (window.opera) {
652 if (opera) {
509 653 lastStoppedKey = handled ? code : null;
510 654 // Opera has no cut event... we try to at least catch the key combo
511 655 if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey"))
512 656 replaceSelection("");
513 657 }
514 658 }
515 659 function onKeyPress(e) {
660 if (pollingFast) readInput();
516 661 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
517 662 var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode");
518 if (window.opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
519 if (window.opera && !e.which && handleKeyBinding(e)) return;
663 if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
664 if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(e)) return;
665 var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
520 666 if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) {
521 var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
522 667 if (mode.electricChars.indexOf(ch) > -1)
523 668 setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75);
524 669 }
670 if (handleCharBinding(e, ch)) return;
525 671 fastPoll();
526 672 }
527 673 function onKeyUp(e) {
528 674 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
529 675 if (e_prop(e, "keyCode") == 16) shiftSelecting = null;
530 676 }
531 677
532 678 function onFocus() {
533 679 if (options.readOnly == "nocursor") return;
534 680 if (!focused) {
535 681 if (options.onFocus) options.onFocus(instance);
536 682 focused = true;
537 if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
538 wrapper.className += " CodeMirror-focused";
683 if (scroller.className.search(/\bCodeMirror-focused\b/) == -1)
684 scroller.className += " CodeMirror-focused";
539 685 if (!leaveInputAlone) resetInput(true);
540 686 }
541 687 slowPoll();
542 688 restartBlink();
543 689 }
544 690 function onBlur() {
545 691 if (focused) {
546 692 if (options.onBlur) options.onBlur(instance);
547 693 focused = false;
548 694 if (bracketHighlighted)
549 695 operation(function(){
550 696 if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; }
551 697 })();
552 wrapper.className = wrapper.className.replace(" CodeMirror-focused", "");
698 scroller.className = scroller.className.replace(" CodeMirror-focused", "");
553 699 }
554 700 clearInterval(blinker);
555 701 setTimeout(function() {if (!focused) shiftSelecting = null;}, 150);
556 702 }
557 703
704 function chopDelta(delta) {
705 // Make sure we always scroll a little bit for any nonzero delta.
706 if (delta > 0.0 && delta < 1.0) return 1;
707 else if (delta > -1.0 && delta < 0.0) return -1;
708 else return Math.round(delta);
709 }
710
711 function onMouseWheel(e) {
712 var deltaX = 0, deltaY = 0;
713 if (e.type == "DOMMouseScroll") { // Firefox
714 var delta = -e.detail * 8.0;
715 if (e.axis == e.HORIZONTAL_AXIS) deltaX = delta;
716 else if (e.axis == e.VERTICAL_AXIS) deltaY = delta;
717 } else if (e.wheelDeltaX !== undefined && e.wheelDeltaY !== undefined) { // WebKit
718 deltaX = e.wheelDeltaX / 3.0;
719 deltaY = e.wheelDeltaY / 3.0;
720 } else if (e.wheelDelta !== undefined) { // IE or Opera
721 deltaY = e.wheelDelta / 3.0;
722 }
723
724 var scrolled = false;
725 deltaX = chopDelta(deltaX);
726 deltaY = chopDelta(deltaY);
727 if ((deltaX > 0 && scroller.scrollLeft > 0) ||
728 (deltaX < 0 && scroller.scrollLeft + scroller.clientWidth < scroller.scrollWidth)) {
729 scroller.scrollLeft -= deltaX;
730 scrolled = true;
731 }
732 if ((deltaY > 0 && scrollbar.scrollTop > 0) ||
733 (deltaY < 0 && scrollbar.scrollTop + scrollbar.clientHeight < scrollbar.scrollHeight)) {
734 scrollbar.scrollTop -= deltaY;
735 scrolled = true;
736 }
737 if (scrolled) e_stop(e);
738 }
739
558 740 // Replace the range from from to to by the strings in newText.
559 741 // Afterwards, set the selection to selFrom, selTo.
560 742 function updateLines(from, to, newText, selFrom, selTo) {
561 743 if (suppressEdits) return;
562 744 if (history) {
563 745 var old = [];
564 746 doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); });
565 747 history.addChange(from.line, newText.length, old);
566 748 while (history.done.length > options.undoDepth) history.done.shift();
567 749 }
568 750 updateLinesNoUndo(from, to, newText, selFrom, selTo);
569 751 }
570 function unredoHelper(from, to, dir) {
571 var set = from.pop(), len = set ? set.length : 0, out = [];
572 for (var i = dir > 0 ? 0 : len - 1, e = dir > 0 ? len : -1; i != e; i += dir) {
752 function unredoHelper(from, to) {
753 if (!from.length) return;
754 var set = from.pop(), out = [];
755 for (var i = set.length - 1; i >= 0; i -= 1) {
573 756 var change = set[i];
574 757 var replaced = [], end = change.start + change.added;
575 758 doc.iter(change.start, end, function(line) { replaced.push(line.text); });
576 759 out.push({start: change.start, added: change.old.length, old: replaced});
577 var pos = clipPos({line: change.start + change.old.length - 1,
578 ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])});
760 var pos = {line: change.start + change.old.length - 1,
761 ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])};
579 762 updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos);
580 763 }
581 764 updateInput = true;
582 765 to.push(out);
583 766 }
584 function undo() {unredoHelper(history.done, history.undone, -1);}
585 function redo() {unredoHelper(history.undone, history.done, 1);}
767 function undo() {unredoHelper(history.done, history.undone);}
768 function redo() {unredoHelper(history.undone, history.done);}
586 769
587 770 function updateLinesNoUndo(from, to, newText, selFrom, selTo) {
588 771 if (suppressEdits) return;
589 772 var recomputeMaxLength = false, maxLineLength = maxLine.length;
590 773 if (!options.lineWrapping)
591 doc.iter(from.line, to.line, function(line) {
592 if (line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}
774 doc.iter(from.line, to.line + 1, function(line) {
775 if (!line.hidden && line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}
593 776 });
594 777 if (from.line != to.line || newText.length > 1) gutterDirty = true;
595 778
596 779 var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);
597 780 // First adjust the line structure, taking some care to leave highlighting intact.
598 781 if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "") {
599 782 // This is a whole-line replace. Treated specially to make
600 783 // sure line objects move the way they are supposed to.
601 784 var added = [], prevLine = null;
602 785 if (from.line) {
603 786 prevLine = getLine(from.line - 1);
604 787 prevLine.fixMarkEnds(lastLine);
605 788 } else lastLine.fixMarkStarts();
606 789 for (var i = 0, e = newText.length - 1; i < e; ++i)
607 790 added.push(Line.inheritMarks(newText[i], prevLine));
608 791 if (nlines) doc.remove(from.line, nlines, callbacks);
609 792 if (added.length) doc.insert(from.line, added);
610 793 } else if (firstLine == lastLine) {
611 794 if (newText.length == 1)
612 795 firstLine.replace(from.ch, to.ch, newText[0]);
613 796 else {
614 797 lastLine = firstLine.split(to.ch, newText[newText.length-1]);
615 798 firstLine.replace(from.ch, null, newText[0]);
616 799 firstLine.fixMarkEnds(lastLine);
617 800 var added = [];
618 801 for (var i = 1, e = newText.length - 1; i < e; ++i)
619 802 added.push(Line.inheritMarks(newText[i], firstLine));
620 803 added.push(lastLine);
621 804 doc.insert(from.line + 1, added);
622 805 }
623 806 } else if (newText.length == 1) {
624 807 firstLine.replace(from.ch, null, newText[0]);
625 808 lastLine.replace(null, to.ch, "");
626 809 firstLine.append(lastLine);
627 810 doc.remove(from.line + 1, nlines, callbacks);
628 811 } else {
629 812 var added = [];
630 813 firstLine.replace(from.ch, null, newText[0]);
631 814 lastLine.replace(null, to.ch, newText[newText.length-1]);
632 815 firstLine.fixMarkEnds(lastLine);
633 816 for (var i = 1, e = newText.length - 1; i < e; ++i)
634 817 added.push(Line.inheritMarks(newText[i], firstLine));
635 818 if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks);
636 819 doc.insert(from.line + 1, added);
637 820 }
638 821 if (options.lineWrapping) {
639 var perLine = scroller.clientWidth / charWidth() - 3;
822 var perLine = Math.max(5, scroller.clientWidth / charWidth() - 3);
640 823 doc.iter(from.line, from.line + newText.length, function(line) {
641 824 if (line.hidden) return;
642 825 var guess = Math.ceil(line.text.length / perLine) || 1;
643 826 if (guess != line.height) updateLineHeight(line, guess);
644 827 });
645 828 } else {
646 doc.iter(from.line, i + newText.length, function(line) {
829 doc.iter(from.line, from.line + newText.length, function(line) {
647 830 var l = line.text;
648 if (l.length > maxLineLength) {
649 maxLine = l; maxLineLength = l.length; maxWidth = null;
831 if (!line.hidden && l.length > maxLineLength) {
832 maxLine = l; maxLineLength = l.length; maxLineChanged = true;
650 833 recomputeMaxLength = false;
651 834 }
652 835 });
653 if (recomputeMaxLength) {
654 maxLineLength = 0; maxLine = ""; maxWidth = null;
655 doc.iter(0, doc.size, function(line) {
656 var l = line.text;
657 if (l.length > maxLineLength) {
658 maxLineLength = l.length; maxLine = l;
659 }
660 });
661 }
836 if (recomputeMaxLength) updateMaxLine = true;
662 837 }
663 838
664 839 // Add these lines to the work array, so that they will be
665 840 // highlighted. Adjust work lines if lines were added/removed.
666 841 var newWork = [], lendiff = newText.length - nlines - 1;
667 842 for (var i = 0, l = work.length; i < l; ++i) {
668 843 var task = work[i];
669 844 if (task < from.line) newWork.push(task);
670 845 else if (task > to.line) newWork.push(task + lendiff);
671 846 }
672 847 var hlEnd = from.line + Math.min(newText.length, 500);
673 848 highlightLines(from.line, hlEnd);
674 849 newWork.push(hlEnd);
675 850 work = newWork;
676 851 startWorker(100);
677 852 // Remember that these lines changed, for updating the display
678 853 changes.push({from: from.line, to: to.line + 1, diff: lendiff});
679 854 var changeObj = {from: from, to: to, text: newText};
680 855 if (textChanged) {
681 856 for (var cur = textChanged; cur.next; cur = cur.next) {}
682 857 cur.next = changeObj;
683 858 } else textChanged = changeObj;
684 859
685 860 // Update the selection
686 861 function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;}
687 setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line));
862 setSelection(clipPos(selFrom), clipPos(selTo),
863 updateLine(sel.from.line), updateLine(sel.to.line));
864 }
865
866 function needsScrollbar() {
867 var realHeight = doc.height * textHeight() + 2 * paddingTop();
868 return realHeight - 1 > scroller.offsetHeight ? realHeight : false;
869 }
870
871 function updateVerticalScroll(scrollTop) {
872 var scrollHeight = needsScrollbar();
873 scrollbar.style.display = scrollHeight ? "block" : "none";
874 if (scrollHeight) {
875 scrollbarInner.style.height = scrollHeight + "px";
876 scrollbar.style.height = scroller.offsetHeight + "px";
877 if (scrollTop != null) scrollbar.scrollTop = scrollTop;
878 }
879 // Position the mover div to align with the current virtual scroll position
880 mover.style.top = (displayOffset * textHeight() - scrollbar.scrollTop) + "px";
881 }
882
883 // On Mac OS X Lion and up, detect whether the mouse is plugged in by measuring
884 // the width of a div with a scrollbar in it. If the width is <= 1, then
885 // the mouse isn't plugged in and scrollbars should overlap the content.
886 function overlapScrollbars() {
887 var tmpSb = document.createElement('div'),
888 tmpSbInner = document.createElement('div');
889 tmpSb.className = "CodeMirror-scrollbar";
890 tmpSb.style.cssText = "position: absolute; left: -9999px; height: 100px;";
891 tmpSbInner.className = "CodeMirror-scrollbar-inner";
892 tmpSbInner.style.height = "200px";
893 tmpSb.appendChild(tmpSbInner);
894
895 document.body.appendChild(tmpSb);
896 var result = (tmpSb.offsetWidth <= 1);
897 document.body.removeChild(tmpSb);
898 return result;
899 }
688 900
689 // Make sure the scroll-size div has the correct height.
690 if (scroller.clientHeight)
691 code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px";
901 function computeMaxLength() {
902 var maxLineLength = 0;
903 maxLine = ""; maxLineChanged = true;
904 doc.iter(0, doc.size, function(line) {
905 var l = line.text;
906 if (!line.hidden && l.length > maxLineLength) {
907 maxLineLength = l.length; maxLine = l;
908 }
909 });
910 updateMaxLine = false;
692 911 }
693 912
694 913 function replaceRange(code, from, to) {
695 914 from = clipPos(from);
696 915 if (!to) to = from; else to = clipPos(to);
697 916 code = splitLines(code);
698 917 function adjustPos(pos) {
699 918 if (posLess(pos, from)) return pos;
700 919 if (!posLess(to, pos)) return end;
701 920 var line = pos.line + code.length - (to.line - from.line) - 1;
702 921 var ch = pos.ch;
703 922 if (pos.line == to.line)
704 923 ch += code[code.length-1].length - (to.ch - (to.line == from.line ? from.ch : 0));
705 924 return {line: line, ch: ch};
706 925 }
707 926 var end;
708 927 replaceRange1(code, from, to, function(end1) {
709 928 end = end1;
710 929 return {from: adjustPos(sel.from), to: adjustPos(sel.to)};
711 930 });
712 931 return end;
713 932 }
714 933 function replaceSelection(code, collapse) {
715 934 replaceRange1(splitLines(code), sel.from, sel.to, function(end) {
716 935 if (collapse == "end") return {from: end, to: end};
717 936 else if (collapse == "start") return {from: sel.from, to: sel.from};
718 937 else return {from: sel.from, to: end};
719 938 });
720 939 }
721 940 function replaceRange1(code, from, to, computeSel) {
722 941 var endch = code.length == 1 ? code[0].length + from.ch : code[code.length-1].length;
723 942 var newSel = computeSel({line: from.line + code.length - 1, ch: endch});
724 943 updateLines(from, to, code, newSel.from, newSel.to);
725 944 }
726 945
727 function getRange(from, to) {
946 function getRange(from, to, lineSep) {
728 947 var l1 = from.line, l2 = to.line;
729 948 if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch);
730 949 var code = [getLine(l1).text.slice(from.ch)];
731 950 doc.iter(l1 + 1, l2, function(line) { code.push(line.text); });
732 951 code.push(getLine(l2).text.slice(0, to.ch));
733 return code.join("\n");
952 return code.join(lineSep || "\n");
734 953 }
735 function getSelection() {
736 return getRange(sel.from, sel.to);
954 function getSelection(lineSep) {
955 return getRange(sel.from, sel.to, lineSep);
737 956 }
738 957
739 958 var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll
740 959 function slowPoll() {
741 960 if (pollingFast) return;
742 961 poll.set(options.pollInterval, function() {
743 962 startOperation();
744 963 readInput();
745 964 if (focused) slowPoll();
746 965 endOperation();
747 966 });
748 967 }
749 968 function fastPoll() {
750 969 var missed = false;
751 970 pollingFast = true;
752 971 function p() {
753 972 startOperation();
754 973 var changed = readInput();
755 974 if (!changed && !missed) {missed = true; poll.set(60, p);}
756 975 else {pollingFast = false; slowPoll();}
757 976 endOperation();
758 977 }
759 978 poll.set(20, p);
760 979 }
761 980
762 981 // Previnput is a hack to work with IME. If we reset the textarea
763 982 // on every change, that breaks IME. So we look for changes
764 983 // compared to the previous content instead. (Modern browsers have
765 984 // events that indicate IME taking place, but these are not widely
766 985 // supported or compatible enough yet to rely on.)
767 986 var prevInput = "";
768 987 function readInput() {
769 988 if (leaveInputAlone || !focused || hasSelection(input) || options.readOnly) return false;
770 989 var text = input.value;
771 990 if (text == prevInput) return false;
772 991 shiftSelecting = null;
773 992 var same = 0, l = Math.min(prevInput.length, text.length);
774 993 while (same < l && prevInput[same] == text[same]) ++same;
775 994 if (same < prevInput.length)
776 995 sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)};
777 996 else if (overwrite && posEq(sel.from, sel.to))
778 997 sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))};
779 998 replaceSelection(text.slice(same), "end");
780 prevInput = text;
999 if (text.length > 1000) { input.value = prevInput = ""; }
1000 else prevInput = text;
781 1001 return true;
782 1002 }
783 1003 function resetInput(user) {
784 1004 if (!posEq(sel.from, sel.to)) {
785 1005 prevInput = "";
786 1006 input.value = getSelection();
787 1007 selectInput(input);
788 1008 } else if (user) prevInput = input.value = "";
789 1009 }
790 1010
791 1011 function focusInput() {
792 1012 if (options.readOnly != "nocursor") input.focus();
793 1013 }
794 1014
795 1015 function scrollEditorIntoView() {
796 if (!cursor.getBoundingClientRect) return;
797 1016 var rect = cursor.getBoundingClientRect();
798 1017 // IE returns bogus coordinates when the instance sits inside of an iframe and the cursor is hidden
799 1018 if (ie && rect.top == rect.bottom) return;
800 1019 var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
801 if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView();
1020 if (rect.top < 0 || rect.bottom > winH) scrollCursorIntoView();
802 1021 }
803 1022 function scrollCursorIntoView() {
1023 var coords = calculateCursorCoords();
1024 return scrollIntoView(coords.x, coords.y, coords.x, coords.yBot);
1025 }
1026 function calculateCursorCoords() {
804 1027 var cursor = localCoords(sel.inverted ? sel.from : sel.to);
805 1028 var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x;
806 return scrollIntoView(x, cursor.y, x, cursor.yBot);
1029 return {x: x, y: cursor.y, yBot: cursor.yBot};
807 1030 }
808 1031 function scrollIntoView(x1, y1, x2, y2) {
809 var pl = paddingLeft(), pt = paddingTop(), lh = textHeight();
1032 var scrollPos = calculateScrollPos(x1, y1, x2, y2), scrolled = false;
1033 if (scrollPos.scrollLeft != null) {scroller.scrollLeft = scrollPos.scrollLeft; scrolled = true;}
1034 if (scrollPos.scrollTop != null) {scrollbar.scrollTop = scrollPos.scrollTop; scrolled = true;}
1035 if (scrolled && options.onScroll) options.onScroll(instance);
1036 }
1037 function calculateScrollPos(x1, y1, x2, y2) {
1038 var pl = paddingLeft(), pt = paddingTop();
810 1039 y1 += pt; y2 += pt; x1 += pl; x2 += pl;
811 var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true;
812 if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;}
813 else if (y2 > screentop + screen) {scroller.scrollTop = y2 + lh - screen; scrolled = true;}
1040 var screen = scroller.clientHeight, screentop = scrollbar.scrollTop, result = {};
1041 var docBottom = scroller.scrollHeight;
1042 var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10;;
1043 if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1);
1044 else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen;
814 1045
815 1046 var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft;
816 1047 var gutterw = options.fixedGutter ? gutter.clientWidth : 0;
817 if (x1 < screenleft + gutterw) {
818 if (x1 < 50) x1 = 0;
819 scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw);
820 scrolled = true;
821 }
822 else if (x2 > screenw + screenleft - 3) {
823 scroller.scrollLeft = x2 + 10 - screenw;
824 scrolled = true;
825 if (x2 > code.clientWidth) result = false;
1048 var atLeft = x1 < gutterw + pl + 10;
1049 if (x1 < screenleft + gutterw || atLeft) {
1050 if (atLeft) x1 = 0;
1051 result.scrollLeft = Math.max(0, x1 - 10 - gutterw);
1052 } else if (x2 > screenw + screenleft - 3) {
1053 result.scrollLeft = x2 + 10 - screenw;
826 1054 }
827 if (scrolled && options.onScroll) options.onScroll(instance);
828 1055 return result;
829 1056 }
830 1057
831 function visibleLines() {
832 var lh = textHeight(), top = scroller.scrollTop - paddingTop();
833 var from_height = Math.max(0, Math.floor(top / lh));
834 var to_height = Math.ceil((top + scroller.clientHeight) / lh);
835 return {from: lineAtHeight(doc, from_height),
836 to: lineAtHeight(doc, to_height)};
1058 function visibleLines(scrollTop) {
1059 var lh = textHeight(), top = (scrollTop != null ? scrollTop : scrollbar.scrollTop) - paddingTop();
1060 var fromHeight = Math.max(0, Math.floor(top / lh));
1061 var toHeight = Math.ceil((top + scroller.clientHeight) / lh);
1062 return {from: lineAtHeight(doc, fromHeight),
1063 to: lineAtHeight(doc, toHeight)};
837 1064 }
838 1065 // Uses a set of changes plus the current scroll position to
839 1066 // determine which DOM updates have to be made, and makes the
840 1067 // updates.
841 function updateDisplay(changes, suppressCallback) {
1068 function updateDisplay(changes, suppressCallback, scrollTop) {
842 1069 if (!scroller.clientWidth) {
843 1070 showingFrom = showingTo = displayOffset = 0;
844 1071 return;
845 1072 }
846 1073 // Compute the new visible window
847 var visible = visibleLines();
1074 // If scrollTop is specified, use that to determine which lines
1075 // to render instead of the current scrollbar position.
1076 var visible = visibleLines(scrollTop);
848 1077 // Bail out if the visible area is already rendered and nothing changed.
849 if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) return;
1078 if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) {
1079 updateVerticalScroll(scrollTop);
1080 return;
1081 }
850 1082 var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100);
851 1083 if (showingFrom < from && from - showingFrom < 20) from = showingFrom;
852 1084 if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo);
853 1085
854 1086 // Create a range of theoretically intact lines, and punch holes
855 1087 // in that using the change info.
856 1088 var intact = changes === true ? [] :
857 1089 computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes);
858 1090 // Clip off the parts that won't be visible
859 1091 var intactLines = 0;
860 1092 for (var i = 0; i < intact.length; ++i) {
861 1093 var range = intact[i];
862 1094 if (range.from < from) {range.domStart += (from - range.from); range.from = from;}
863 1095 if (range.to > to) range.to = to;
864 1096 if (range.from >= range.to) intact.splice(i--, 1);
865 1097 else intactLines += range.to - range.from;
866 1098 }
867 if (intactLines == to - from) return;
1099 if (intactLines == to - from && from == showingFrom && to == showingTo) {
1100 updateVerticalScroll(scrollTop);
1101 return;
1102 }
868 1103 intact.sort(function(a, b) {return a.domStart - b.domStart;});
869 1104
870 1105 var th = textHeight(), gutterDisplay = gutter.style.display;
871 1106 lineDiv.style.display = "none";
872 1107 patchDisplay(from, to, intact);
873 1108 lineDiv.style.display = gutter.style.display = "";
874 1109
875 // Position the mover div to align with the lines it's supposed
876 // to be showing (which will cover the visible display)
877 1110 var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th;
878 1111 // This is just a bogus formula that detects when the editor is
879 1112 // resized or the font size changes.
880 1113 if (different) lastSizeC = scroller.clientHeight + th;
881 1114 showingFrom = from; showingTo = to;
882 1115 displayOffset = heightAtLine(doc, from);
883 mover.style.top = (displayOffset * th) + "px";
884 if (scroller.clientHeight)
885 code.style.height = (doc.height * th + 2 * paddingTop()) + "px";
886 1116
887 1117 // Since this is all rather error prone, it is honoured with the
888 1118 // only assertion in the whole file.
889 1119 if (lineDiv.childNodes.length != showingTo - showingFrom)
890 1120 throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) +
891 1121 " nodes=" + lineDiv.childNodes.length);
892 1122
893 if (options.lineWrapping) {
894 maxWidth = scroller.clientWidth;
1123 function checkHeights() {
895 1124 var curNode = lineDiv.firstChild, heightChanged = false;
896 1125 doc.iter(showingFrom, showingTo, function(line) {
897 1126 if (!line.hidden) {
898 1127 var height = Math.round(curNode.offsetHeight / th) || 1;
899 1128 if (line.height != height) {
900 1129 updateLineHeight(line, height);
901 1130 gutterDirty = heightChanged = true;
902 1131 }
903 1132 }
904 1133 curNode = curNode.nextSibling;
905 1134 });
906 if (heightChanged)
907 code.style.height = (doc.height * th + 2 * paddingTop()) + "px";
908 } else {
909 if (maxWidth == null) maxWidth = stringWidth(maxLine);
910 if (maxWidth > scroller.clientWidth) {
911 lineSpace.style.width = maxWidth + "px";
912 // Needed to prevent odd wrapping/hiding of widgets placed in here.
913 code.style.width = "";
914 code.style.width = scroller.scrollWidth + "px";
915 } else {
916 lineSpace.style.width = code.style.width = "";
1135 return heightChanged;
1136 }
1137
1138 if (options.lineWrapping) {
1139 checkHeights();
1140 var scrollHeight = needsScrollbar();
1141 var shouldHaveScrollbar = scrollHeight ? "block" : "none";
1142 if (scrollbar.style.display != shouldHaveScrollbar) {
1143 scrollbar.style.display = shouldHaveScrollbar;
1144 if (scrollHeight) scrollbarInner.style.height = scrollHeight + "px";
1145 checkHeights();
917 1146 }
918 1147 }
1148
919 1149 gutter.style.display = gutterDisplay;
920 if (different || gutterDirty) updateGutter();
1150 if (different || gutterDirty) {
1151 // If the gutter grew in size, re-check heights. If those changed, re-draw gutter.
1152 updateGutter() && options.lineWrapping && checkHeights() && updateGutter();
1153 }
1154 updateVerticalScroll(scrollTop);
921 1155 updateSelection();
922 1156 if (!suppressCallback && options.onUpdate) options.onUpdate(instance);
923 1157 return true;
924 1158 }
925 1159
926 1160 function computeIntact(intact, changes) {
927 1161 for (var i = 0, l = changes.length || 0; i < l; ++i) {
928 1162 var change = changes[i], intact2 = [], diff = change.diff || 0;
929 1163 for (var j = 0, l2 = intact.length; j < l2; ++j) {
930 1164 var range = intact[j];
931 1165 if (change.to <= range.from && change.diff)
932 1166 intact2.push({from: range.from + diff, to: range.to + diff,
933 1167 domStart: range.domStart});
934 1168 else if (change.to <= range.from || change.from >= range.to)
935 1169 intact2.push(range);
936 1170 else {
937 1171 if (change.from > range.from)
938 1172 intact2.push({from: range.from, to: change.from, domStart: range.domStart});
939 1173 if (change.to < range.to)
940 1174 intact2.push({from: change.to + diff, to: range.to + diff,
941 1175 domStart: range.domStart + (change.to - range.from)});
942 1176 }
943 1177 }
944 1178 intact = intact2;
945 1179 }
946 1180 return intact;
947 1181 }
948 1182
949 1183 function patchDisplay(from, to, intact) {
950 1184 // The first pass removes the DOM nodes that aren't intact.
951 1185 if (!intact.length) lineDiv.innerHTML = "";
952 1186 else {
953 1187 function killNode(node) {
954 1188 var tmp = node.nextSibling;
955 1189 node.parentNode.removeChild(node);
956 1190 return tmp;
957 1191 }
958 1192 var domPos = 0, curNode = lineDiv.firstChild, n;
959 1193 for (var i = 0; i < intact.length; ++i) {
960 1194 var cur = intact[i];
961 1195 while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;}
962 1196 for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;}
963 1197 }
964 1198 while (curNode) curNode = killNode(curNode);
965 1199 }
966 1200 // This pass fills in the lines that actually changed.
967 1201 var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from;
968 var scratch = targetDocument.createElement("div"), newElt;
1202 var scratch = document.createElement("div");
969 1203 doc.iter(from, to, function(line) {
970 1204 if (nextIntact && nextIntact.to == j) nextIntact = intact.shift();
971 1205 if (!nextIntact || nextIntact.from > j) {
972 1206 if (line.hidden) var html = scratch.innerHTML = "<pre></pre>";
973 1207 else {
974 var html = '<pre>' + line.getHTML(tabText) + '</pre>';
1208 var html = '<pre' + (line.className ? ' class="' + line.className + '"' : '') + '>'
1209 + line.getHTML(makeTab) + '</pre>';
975 1210 // Kludge to make sure the styled element lies behind the selection (by z-index)
976 if (line.className)
977 html = '<div style="position: relative"><pre class="' + line.className +
1211 if (line.bgClassName)
1212 html = '<div style="position: relative"><pre class="' + line.bgClassName +
978 1213 '" style="position: absolute; left: 0; right: 0; top: 0; bottom: 0; z-index: -2">&#160;</pre>' + html + "</div>";
979 1214 }
980 1215 scratch.innerHTML = html;
981 1216 lineDiv.insertBefore(scratch.firstChild, curNode);
982 1217 } else {
983 1218 curNode = curNode.nextSibling;
984 1219 }
985 1220 ++j;
986 1221 });
987 1222 }
988 1223
989 1224 function updateGutter() {
990 1225 if (!options.gutter && !options.lineNumbers) return;
991 1226 var hText = mover.offsetHeight, hEditor = scroller.clientHeight;
992 1227 gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
993 var html = [], i = showingFrom;
1228 var html = [], i = showingFrom, normalNode;
994 1229 doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) {
995 1230 if (line.hidden) {
996 1231 html.push("<pre></pre>");
997 1232 } else {
998 1233 var marker = line.gutterMarker;
999 var text = options.lineNumbers ? i + options.firstLineNumber : null;
1234 var text = options.lineNumbers ? options.lineNumberFormatter(i + options.firstLineNumber) : null;
1000 1235 if (marker && marker.text)
1001 1236 text = marker.text.replace("%N%", text != null ? text : "");
1002 1237 else if (text == null)
1003 1238 text = "\u00a0";
1004 1239 html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text);
1005 1240 for (var j = 1; j < line.height; ++j) html.push("<br/>&#160;");
1006 1241 html.push("</pre>");
1242 if (!marker) normalNode = i;
1007 1243 }
1008 1244 ++i;
1009 1245 });
1010 1246 gutter.style.display = "none";
1011 1247 gutterText.innerHTML = html.join("");
1012 var minwidth = String(doc.size).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = "";
1013 while (val.length + pad.length < minwidth) pad += "\u00a0";
1014 if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild);
1248 // Make sure scrolling doesn't cause number gutter size to pop
1249 if (normalNode != null && options.lineNumbers) {
1250 var node = gutterText.childNodes[normalNode - showingFrom];
1251 var minwidth = String(doc.size).length, val = eltText(node.firstChild), pad = "";
1252 while (val.length + pad.length < minwidth) pad += "\u00a0";
1253 if (pad) node.insertBefore(document.createTextNode(pad), node.firstChild);
1254 }
1015 1255 gutter.style.display = "";
1256 var resized = Math.abs((parseInt(lineSpace.style.marginLeft) || 0) - gutter.offsetWidth) > 2;
1016 1257 lineSpace.style.marginLeft = gutter.offsetWidth + "px";
1017 1258 gutterDirty = false;
1259 return resized;
1018 1260 }
1019 1261 function updateSelection() {
1020 1262 var collapsed = posEq(sel.from, sel.to);
1021 1263 var fromPos = localCoords(sel.from, true);
1022 1264 var toPos = collapsed ? fromPos : localCoords(sel.to, true);
1023 1265 var headPos = sel.inverted ? fromPos : toPos, th = textHeight();
1024 1266 var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv);
1025 1267 inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px";
1026 1268 inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px";
1027 1269 if (collapsed) {
1028 1270 cursor.style.top = headPos.y + "px";
1029 1271 cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px";
1030 1272 cursor.style.display = "";
1031 1273 selectionDiv.style.display = "none";
1032 1274 } else {
1033 1275 var sameLine = fromPos.y == toPos.y, html = "";
1276 var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth;
1277 var clientHeight = lineSpace.clientHeight || lineSpace.offsetHeight;
1034 1278 function add(left, top, right, height) {
1279 var rstyle = quirksMode ? "width: " + (!right ? clientWidth : clientWidth - right - left) + "px"
1280 : "right: " + right + "px";
1035 1281 html += '<div class="CodeMirror-selected" style="position: absolute; left: ' + left +
1036 'px; top: ' + top + 'px; right: ' + right + 'px; height: ' + height + 'px"></div>';
1282 'px; top: ' + top + 'px; ' + rstyle + '; height: ' + height + 'px"></div>';
1037 1283 }
1038 1284 if (sel.from.ch && fromPos.y >= 0) {
1039 var right = sameLine ? lineSpace.clientWidth - toPos.x : 0;
1285 var right = sameLine ? clientWidth - toPos.x : 0;
1040 1286 add(fromPos.x, fromPos.y, right, th);
1041 1287 }
1042 1288 var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0));
1043 var middleHeight = Math.min(toPos.y, lineSpace.clientHeight) - middleStart;
1289 var middleHeight = Math.min(toPos.y, clientHeight) - middleStart;
1044 1290 if (middleHeight > 0.2 * th)
1045 1291 add(0, middleStart, 0, middleHeight);
1046 if ((!sameLine || !sel.from.ch) && toPos.y < lineSpace.clientHeight - .5 * th)
1047 add(0, toPos.y, lineSpace.clientWidth - toPos.x, th);
1292 if ((!sameLine || !sel.from.ch) && toPos.y < clientHeight - .5 * th)
1293 add(0, toPos.y, clientWidth - toPos.x, th);
1048 1294 selectionDiv.innerHTML = html;
1049 1295 cursor.style.display = "none";
1050 1296 selectionDiv.style.display = "";
1051 1297 }
1052 1298 }
1053 1299
1054 1300 function setShift(val) {
1055 1301 if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
1056 1302 else shiftSelecting = null;
1057 1303 }
1058 1304 function setSelectionUser(from, to) {
1059 1305 var sh = shiftSelecting && clipPos(shiftSelecting);
1060 1306 if (sh) {
1061 1307 if (posLess(sh, from)) from = sh;
1062 1308 else if (posLess(to, sh)) to = sh;
1063 1309 }
1064 1310 setSelection(from, to);
1065 1311 userSelChange = true;
1066 1312 }
1067 1313 // Update the selection. Last two args are only used by
1068 1314 // updateLines, since they have to be expressed in the line
1069 1315 // numbers before the update.
1070 1316 function setSelection(from, to, oldFrom, oldTo) {
1071 1317 goalColumn = null;
1072 1318 if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
1073 1319 if (posEq(sel.from, from) && posEq(sel.to, to)) return;
1074 1320 if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
1075 1321
1076 1322 // Skip over hidden lines.
1077 if (from.line != oldFrom) from = skipHidden(from, oldFrom, sel.from.ch);
1323 if (from.line != oldFrom) {
1324 var from1 = skipHidden(from, oldFrom, sel.from.ch);
1325 // If there is no non-hidden line left, force visibility on current line
1326 if (!from1) setLineHidden(from.line, false);
1327 else from = from1;
1328 }
1078 1329 if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch);
1079 1330
1080 1331 if (posEq(from, to)) sel.inverted = false;
1081 1332 else if (posEq(from, sel.to)) sel.inverted = false;
1082 1333 else if (posEq(to, sel.from)) sel.inverted = true;
1083 1334
1335 if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) {
1336 var head = sel.inverted ? from : to;
1337 if (head.line != sel.from.line && sel.from.line < doc.size) {
1338 var oldLine = getLine(sel.from.line);
1339 if (/^\s+$/.test(oldLine.text))
1340 setTimeout(operation(function() {
1341 if (oldLine.parent && /^\s+$/.test(oldLine.text)) {
1342 var no = lineNo(oldLine);
1343 replaceRange("", {line: no, ch: 0}, {line: no, ch: oldLine.text.length});
1344 }
1345 }, 10));
1346 }
1347 }
1348
1084 1349 sel.from = from; sel.to = to;
1085 1350 selectionChanged = true;
1086 1351 }
1087 1352 function skipHidden(pos, oldLine, oldCh) {
1088 1353 function getNonHidden(dir) {
1089 1354 var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1;
1090 1355 while (lNo != end) {
1091 1356 var line = getLine(lNo);
1092 1357 if (!line.hidden) {
1093 1358 var ch = pos.ch;
1094 if (ch > oldCh || ch > line.text.length) ch = line.text.length;
1359 if (toEnd || ch > oldCh || ch > line.text.length) ch = line.text.length;
1095 1360 return {line: lNo, ch: ch};
1096 1361 }
1097 1362 lNo += dir;
1098 1363 }
1099 1364 }
1100 1365 var line = getLine(pos.line);
1366 var toEnd = pos.ch == line.text.length && pos.ch != oldCh;
1101 1367 if (!line.hidden) return pos;
1102 1368 if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1);
1103 1369 else return getNonHidden(-1) || getNonHidden(1);
1104 1370 }
1105 1371 function setCursor(line, ch, user) {
1106 1372 var pos = clipPos({line: line, ch: ch || 0});
1107 1373 (user ? setSelectionUser : setSelection)(pos, pos);
1108 1374 }
1109 1375
1110 1376 function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));}
1111 1377 function clipPos(pos) {
1112 1378 if (pos.line < 0) return {line: 0, ch: 0};
1113 1379 if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length};
1114 1380 var ch = pos.ch, linelen = getLine(pos.line).text.length;
1115 1381 if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};
1116 1382 else if (ch < 0) return {line: pos.line, ch: 0};
1117 1383 else return pos;
1118 1384 }
1119 1385
1120 1386 function findPosH(dir, unit) {
1121 1387 var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch;
1122 1388 var lineObj = getLine(line);
1123 1389 function findNextLine() {
1124 1390 for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) {
1125 1391 var lo = getLine(l);
1126 1392 if (!lo.hidden) { line = l; lineObj = lo; return true; }
1127 1393 }
1128 1394 }
1129 1395 function moveOnce(boundToLine) {
1130 1396 if (ch == (dir < 0 ? 0 : lineObj.text.length)) {
1131 1397 if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0;
1132 1398 else return false;
1133 1399 } else ch += dir;
1134 1400 return true;
1135 1401 }
1136 1402 if (unit == "char") moveOnce();
1137 1403 else if (unit == "column") moveOnce(true);
1138 1404 else if (unit == "word") {
1139 1405 var sawWord = false;
1140 1406 for (;;) {
1141 1407 if (dir < 0) if (!moveOnce()) break;
1142 1408 if (isWordChar(lineObj.text.charAt(ch))) sawWord = true;
1143 1409 else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;}
1144 1410 if (dir > 0) if (!moveOnce()) break;
1145 1411 }
1146 1412 }
1147 1413 return {line: line, ch: ch};
1148 1414 }
1149 1415 function moveH(dir, unit) {
1150 1416 var pos = dir < 0 ? sel.from : sel.to;
1151 1417 if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit);
1152 1418 setCursor(pos.line, pos.ch, true);
1153 1419 }
1154 1420 function deleteH(dir, unit) {
1155 1421 if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to);
1156 1422 else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to);
1157 1423 else replaceRange("", sel.from, findPosH(dir, unit));
1158 1424 userSelChange = true;
1159 1425 }
1160 1426 var goalColumn = null;
1161 1427 function moveV(dir, unit) {
1162 1428 var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true);
1163 1429 if (goalColumn != null) pos.x = goalColumn;
1164 1430 if (unit == "page") dist = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight);
1165 1431 else if (unit == "line") dist = textHeight();
1166 1432 var target = coordsChar(pos.x, pos.y + dist * dir + 2);
1433 if (unit == "page") scrollbar.scrollTop += localCoords(target, true).y - pos.y;
1167 1434 setCursor(target.line, target.ch, true);
1168 1435 goalColumn = pos.x;
1169 1436 }
1170 1437
1171 function selectWordAt(pos) {
1438 function findWordAt(pos) {
1172 1439 var line = getLine(pos.line).text;
1173 1440 var start = pos.ch, end = pos.ch;
1174 while (start > 0 && isWordChar(line.charAt(start - 1))) --start;
1175 while (end < line.length && isWordChar(line.charAt(end))) ++end;
1176 setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end});
1441 var check = isWordChar(line.charAt(start < line.length ? start : start - 1)) ?
1442 isWordChar : function(ch) {return !isWordChar(ch);};
1443 while (start > 0 && check(line.charAt(start - 1))) --start;
1444 while (end < line.length && check(line.charAt(end))) ++end;
1445 return {from: {line: pos.line, ch: start}, to: {line: pos.line, ch: end}};
1177 1446 }
1178 1447 function selectLine(line) {
1179 setSelectionUser({line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
1448 setSelectionUser({line: line, ch: 0}, clipPos({line: line + 1, ch: 0}));
1180 1449 }
1181 1450 function indentSelected(mode) {
1182 1451 if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);
1183 1452 var e = sel.to.line - (sel.to.ch ? 0 : 1);
1184 1453 for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);
1185 1454 }
1186 1455
1187 1456 function indentLine(n, how) {
1188 1457 if (!how) how = "add";
1189 1458 if (how == "smart") {
1190 1459 if (!mode.indent) how = "prev";
1191 1460 else var state = getStateBefore(n);
1192 1461 }
1193 1462
1194 1463 var line = getLine(n), curSpace = line.indentation(options.tabSize),
1195 1464 curSpaceString = line.text.match(/^\s*/)[0], indentation;
1465 if (how == "smart") {
1466 indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text);
1467 if (indentation == Pass) how = "prev";
1468 }
1196 1469 if (how == "prev") {
1197 1470 if (n) indentation = getLine(n-1).indentation(options.tabSize);
1198 1471 else indentation = 0;
1199 1472 }
1200 else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text);
1201 1473 else if (how == "add") indentation = curSpace + options.indentUnit;
1202 1474 else if (how == "subtract") indentation = curSpace - options.indentUnit;
1203 1475 indentation = Math.max(0, indentation);
1204 1476 var diff = indentation - curSpace;
1205 1477
1206 if (!diff) {
1207 if (sel.from.line != n && sel.to.line != n) return;
1208 var indentString = curSpaceString;
1209 }
1210 else {
1211 var indentString = "", pos = 0;
1212 if (options.indentWithTabs)
1213 for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";}
1214 while (pos < indentation) {++pos; indentString += " ";}
1215 }
1478 var indentString = "", pos = 0;
1479 if (options.indentWithTabs)
1480 for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";}
1481 while (pos < indentation) {++pos; indentString += " ";}
1216 1482
1217 1483 replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length});
1218 1484 }
1219 1485
1220 1486 function loadMode() {
1221 1487 mode = CodeMirror.getMode(options, options.mode);
1222 1488 doc.iter(0, doc.size, function(line) { line.stateAfter = null; });
1223 1489 work = [0];
1224 1490 startWorker();
1225 1491 }
1226 1492 function gutterChanged() {
1227 1493 var visible = options.gutter || options.lineNumbers;
1228 1494 gutter.style.display = visible ? "" : "none";
1229 1495 if (visible) gutterDirty = true;
1230 1496 else lineDiv.parentNode.style.marginLeft = 0;
1231 1497 }
1232 1498 function wrappingChanged(from, to) {
1233 1499 if (options.lineWrapping) {
1234 1500 wrapper.className += " CodeMirror-wrap";
1235 1501 var perLine = scroller.clientWidth / charWidth() - 3;
1236 1502 doc.iter(0, doc.size, function(line) {
1237 1503 if (line.hidden) return;
1238 1504 var guess = Math.ceil(line.text.length / perLine) || 1;
1239 1505 if (guess != 1) updateLineHeight(line, guess);
1240 1506 });
1241 1507 lineSpace.style.width = code.style.width = "";
1508 widthForcer.style.left = "";
1242 1509 } else {
1243 1510 wrapper.className = wrapper.className.replace(" CodeMirror-wrap", "");
1244 maxWidth = null; maxLine = "";
1511 maxLine = ""; maxLineChanged = true;
1245 1512 doc.iter(0, doc.size, function(line) {
1246 1513 if (line.height != 1 && !line.hidden) updateLineHeight(line, 1);
1247 1514 if (line.text.length > maxLine.length) maxLine = line.text;
1248 1515 });
1249 1516 }
1250 1517 changes.push({from: 0, to: doc.size});
1251 1518 }
1252 function computeTabText() {
1253 for (var str = '<span class="cm-tab">', i = 0; i < options.tabSize; ++i) str += " ";
1254 return str + "</span>";
1255 }
1256 function tabsChanged() {
1257 tabText = computeTabText();
1258 updateDisplay(true);
1519 function makeTab(col) {
1520 var w = options.tabSize - col % options.tabSize, cached = tabCache[w];
1521 if (cached) return cached;
1522 for (var str = '<span class="cm-tab">', i = 0; i < w; ++i) str += " ";
1523 return (tabCache[w] = {html: str + "</span>", width: w});
1259 1524 }
1260 1525 function themeChanged() {
1261 scroller.className = scroller.className.replace(/\s*cm-s-\w+/g, "") +
1526 scroller.className = scroller.className.replace(/\s*cm-s-\S+/g, "") +
1262 1527 options.theme.replace(/(^|\s)\s*/g, " cm-s-");
1263 1528 }
1529 function keyMapChanged() {
1530 var style = keyMap[options.keyMap].style;
1531 wrapper.className = wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
1532 (style ? " cm-keymap-" + style : "");
1533 }
1264 1534
1265 1535 function TextMarker() { this.set = []; }
1266 1536 TextMarker.prototype.clear = operation(function() {
1267 1537 var min = Infinity, max = -Infinity;
1268 1538 for (var i = 0, e = this.set.length; i < e; ++i) {
1269 1539 var line = this.set[i], mk = line.marked;
1270 1540 if (!mk || !line.parent) continue;
1271 1541 var lineN = lineNo(line);
1272 1542 min = Math.min(min, lineN); max = Math.max(max, lineN);
1273 1543 for (var j = 0; j < mk.length; ++j)
1274 if (mk[j].set == this.set) mk.splice(j--, 1);
1544 if (mk[j].marker == this) mk.splice(j--, 1);
1275 1545 }
1276 1546 if (min != Infinity)
1277 1547 changes.push({from: min, to: max + 1});
1278 1548 });
1279 1549 TextMarker.prototype.find = function() {
1280 1550 var from, to;
1281 1551 for (var i = 0, e = this.set.length; i < e; ++i) {
1282 1552 var line = this.set[i], mk = line.marked;
1283 1553 for (var j = 0; j < mk.length; ++j) {
1284 1554 var mark = mk[j];
1285 if (mark.set == this.set) {
1555 if (mark.marker == this) {
1286 1556 if (mark.from != null || mark.to != null) {
1287 1557 var found = lineNo(line);
1288 1558 if (found != null) {
1289 1559 if (mark.from != null) from = {line: found, ch: mark.from};
1290 1560 if (mark.to != null) to = {line: found, ch: mark.to};
1291 1561 }
1292 1562 }
1293 1563 }
1294 1564 }
1295 1565 }
1296 1566 return {from: from, to: to};
1297 1567 };
1298 1568
1299 1569 function markText(from, to, className) {
1300 1570 from = clipPos(from); to = clipPos(to);
1301 1571 var tm = new TextMarker();
1572 if (!posLess(from, to)) return tm;
1302 1573 function add(line, from, to, className) {
1303 getLine(line).addMark(new MarkedText(from, to, className, tm.set));
1574 getLine(line).addMark(new MarkedText(from, to, className, tm));
1304 1575 }
1305 1576 if (from.line == to.line) add(from.line, from.ch, to.ch, className);
1306 1577 else {
1307 1578 add(from.line, from.ch, null, className);
1308 1579 for (var i = from.line + 1, e = to.line; i < e; ++i)
1309 1580 add(i, null, null, className);
1310 1581 add(to.line, null, to.ch, className);
1311 1582 }
1312 1583 changes.push({from: from.line, to: to.line + 1});
1313 1584 return tm;
1314 1585 }
1315 1586
1316 1587 function setBookmark(pos) {
1317 1588 pos = clipPos(pos);
1318 1589 var bm = new Bookmark(pos.ch);
1319 1590 getLine(pos.line).addMark(bm);
1320 1591 return bm;
1321 1592 }
1322 1593
1594 function findMarksAt(pos) {
1595 pos = clipPos(pos);
1596 var markers = [], marked = getLine(pos.line).marked;
1597 if (!marked) return markers;
1598 for (var i = 0, e = marked.length; i < e; ++i) {
1599 var m = marked[i];
1600 if ((m.from == null || m.from <= pos.ch) &&
1601 (m.to == null || m.to >= pos.ch))
1602 markers.push(m.marker || m);
1603 }
1604 return markers;
1605 }
1606
1323 1607 function addGutterMarker(line, text, className) {
1324 1608 if (typeof line == "number") line = getLine(clipLine(line));
1325 1609 line.gutterMarker = {text: text, style: className};
1326 1610 gutterDirty = true;
1327 1611 return line;
1328 1612 }
1329 1613 function removeGutterMarker(line) {
1330 1614 if (typeof line == "number") line = getLine(clipLine(line));
1331 1615 line.gutterMarker = null;
1332 1616 gutterDirty = true;
1333 1617 }
1334 1618
1335 1619 function changeLine(handle, op) {
1336 1620 var no = handle, line = handle;
1337 1621 if (typeof handle == "number") line = getLine(clipLine(handle));
1338 1622 else no = lineNo(handle);
1339 1623 if (no == null) return null;
1340 1624 if (op(line, no)) changes.push({from: no, to: no + 1});
1341 1625 else return null;
1342 1626 return line;
1343 1627 }
1344 function setLineClass(handle, className) {
1628 function setLineClass(handle, className, bgClassName) {
1345 1629 return changeLine(handle, function(line) {
1346 if (line.className != className) {
1630 if (line.className != className || line.bgClassName != bgClassName) {
1347 1631 line.className = className;
1632 line.bgClassName = bgClassName;
1348 1633 return true;
1349 1634 }
1350 1635 });
1351 1636 }
1352 1637 function setLineHidden(handle, hidden) {
1353 1638 return changeLine(handle, function(line, no) {
1354 1639 if (line.hidden != hidden) {
1355 1640 line.hidden = hidden;
1641 if (!options.lineWrapping) {
1642 var l = line.text;
1643 if (hidden && l.length == maxLine.length) {
1644 updateMaxLine = true;
1645 } else if (!hidden && l.length > maxLine.length) {
1646 maxLine = l; updateMaxLine = false;
1647 }
1648 }
1356 1649 updateLineHeight(line, hidden ? 0 : 1);
1357 1650 var fline = sel.from.line, tline = sel.to.line;
1358 1651 if (hidden && (fline == no || tline == no)) {
1359 1652 var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from;
1360 1653 var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to;
1654 // Can't hide the last visible line, we'd have no place to put the cursor
1655 if (!to) return;
1361 1656 setSelection(from, to);
1362 1657 }
1363 1658 return (gutterDirty = true);
1364 1659 }
1365 1660 });
1366 1661 }
1367 1662
1368 1663 function lineInfo(line) {
1369 1664 if (typeof line == "number") {
1370 1665 if (!isLine(line)) return null;
1371 1666 var n = line;
1372 1667 line = getLine(line);
1373 1668 if (!line) return null;
1374 }
1375 else {
1669 } else {
1376 1670 var n = lineNo(line);
1377 1671 if (n == null) return null;
1378 1672 }
1379 1673 var marker = line.gutterMarker;
1380 1674 return {line: n, handle: line, text: line.text, markerText: marker && marker.text,
1381 markerClass: marker && marker.style, lineClass: line.className};
1675 markerClass: marker && marker.style, lineClass: line.className, bgClass: line.bgClassName};
1382 1676 }
1383 1677
1384 1678 function stringWidth(str) {
1385 1679 measure.innerHTML = "<pre><span>x</span></pre>";
1386 1680 measure.firstChild.firstChild.firstChild.nodeValue = str;
1387 1681 return measure.firstChild.firstChild.offsetWidth || 10;
1388 1682 }
1389 1683 // These are used to go from pixel positions to character
1390 1684 // positions, taking varying character widths into account.
1391 1685 function charFromX(line, x) {
1392 1686 if (x <= 0) return 0;
1393 1687 var lineObj = getLine(line), text = lineObj.text;
1394 1688 function getX(len) {
1395 measure.innerHTML = "<pre><span>" + lineObj.getHTML(tabText, len) + "</span></pre>";
1396 return measure.firstChild.firstChild.offsetWidth;
1689 return measureLine(lineObj, len).left;
1397 1690 }
1398 1691 var from = 0, fromX = 0, to = text.length, toX;
1399 1692 // Guess a suitable upper bound for our search.
1400 1693 var estimated = Math.min(to, Math.ceil(x / charWidth()));
1401 1694 for (;;) {
1402 1695 var estX = getX(estimated);
1403 1696 if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
1404 1697 else {toX = estX; to = estimated; break;}
1405 1698 }
1406 1699 if (x > toX) return to;
1407 1700 // Try to guess a suitable lower bound as well.
1408 1701 estimated = Math.floor(to * 0.8); estX = getX(estimated);
1409 1702 if (estX < x) {from = estimated; fromX = estX;}
1410 1703 // Do a binary search between these bounds.
1411 1704 for (;;) {
1412 1705 if (to - from <= 1) return (toX - x > x - fromX) ? from : to;
1413 1706 var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
1414 1707 if (middleX > x) {to = middle; toX = middleX;}
1415 1708 else {from = middle; fromX = middleX;}
1416 1709 }
1417 1710 }
1418 1711
1419 var tempId = Math.floor(Math.random() * 0xffffff).toString(16);
1712 var tempId = "CodeMirror-temp-" + Math.floor(Math.random() * 0xffffff).toString(16);
1420 1713 function measureLine(line, ch) {
1421 1714 if (ch == 0) return {top: 0, left: 0};
1422 var extra = "";
1423 // Include extra text at the end to make sure the measured line is wrapped in the right way.
1424 if (options.lineWrapping) {
1425 var end = line.text.indexOf(" ", ch + 2);
1426 extra = htmlEscape(line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0)));
1427 }
1428 measure.innerHTML = "<pre>" + line.getHTML(tabText, ch) +
1429 '<span id="CodeMirror-temp-' + tempId + '">' + htmlEscape(line.text.charAt(ch) || " ") + "</span>" +
1430 extra + "</pre>";
1431 var elt = document.getElementById("CodeMirror-temp-" + tempId);
1715 var wbr = options.lineWrapping && ch < line.text.length &&
1716 spanAffectsWrapping.test(line.text.slice(ch - 1, ch + 1));
1717 measure.innerHTML = "<pre>" + line.getHTML(makeTab, ch, tempId, wbr) + "</pre>";
1718 var elt = document.getElementById(tempId);
1432 1719 var top = elt.offsetTop, left = elt.offsetLeft;
1433 1720 // Older IEs report zero offsets for spans directly after a wrap
1434 1721 if (ie && top == 0 && left == 0) {
1435 1722 var backup = document.createElement("span");
1436 1723 backup.innerHTML = "x";
1437 1724 elt.parentNode.insertBefore(backup, elt.nextSibling);
1438 1725 top = backup.offsetTop;
1439 1726 }
1440 1727 return {top: top, left: left};
1441 1728 }
1442 1729 function localCoords(pos, inLineWrap) {
1443 1730 var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0));
1444 1731 if (pos.ch == 0) x = 0;
1445 1732 else {
1446 1733 var sp = measureLine(getLine(pos.line), pos.ch);
1447 1734 x = sp.left;
1448 1735 if (options.lineWrapping) y += Math.max(0, sp.top);
1449 1736 }
1450 1737 return {x: x, y: y, yBot: y + lh};
1451 1738 }
1452 1739 // Coords must be lineSpace-local
1453 1740 function coordsChar(x, y) {
1454 1741 if (y < 0) y = 0;
1455 1742 var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th);
1456 1743 var lineNo = lineAtHeight(doc, heightPos);
1457 1744 if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length};
1458 1745 var lineObj = getLine(lineNo), text = lineObj.text;
1459 1746 var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0;
1460 1747 if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0};
1461 1748 function getX(len) {
1462 1749 var sp = measureLine(lineObj, len);
1463 1750 if (tw) {
1464 1751 var off = Math.round(sp.top / th);
1465 1752 return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth);
1466 1753 }
1467 1754 return sp.left;
1468 1755 }
1469 1756 var from = 0, fromX = 0, to = text.length, toX;
1470 1757 // Guess a suitable upper bound for our search.
1471 1758 var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw));
1472 1759 for (;;) {
1473 1760 var estX = getX(estimated);
1474 1761 if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
1475 1762 else {toX = estX; to = estimated; break;}
1476 1763 }
1477 1764 if (x > toX) return {line: lineNo, ch: to};
1478 1765 // Try to guess a suitable lower bound as well.
1479 1766 estimated = Math.floor(to * 0.8); estX = getX(estimated);
1480 1767 if (estX < x) {from = estimated; fromX = estX;}
1481 1768 // Do a binary search between these bounds.
1482 1769 for (;;) {
1483 1770 if (to - from <= 1) return {line: lineNo, ch: (toX - x > x - fromX) ? from : to};
1484 1771 var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
1485 1772 if (middleX > x) {to = middle; toX = middleX;}
1486 1773 else {from = middle; fromX = middleX;}
1487 1774 }
1488 1775 }
1489 1776 function pageCoords(pos) {
1490 1777 var local = localCoords(pos, true), off = eltOffset(lineSpace);
1491 1778 return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot};
1492 1779 }
1493 1780
1494 1781 var cachedHeight, cachedHeightFor, measureText;
1495 1782 function textHeight() {
1496 1783 if (measureText == null) {
1497 1784 measureText = "<pre>";
1498 1785 for (var i = 0; i < 49; ++i) measureText += "x<br/>";
1499 1786 measureText += "x</pre>";
1500 1787 }
1501 1788 var offsetHeight = lineDiv.clientHeight;
1502 1789 if (offsetHeight == cachedHeightFor) return cachedHeight;
1503 1790 cachedHeightFor = offsetHeight;
1504 1791 measure.innerHTML = measureText;
1505 1792 cachedHeight = measure.firstChild.offsetHeight / 50 || 1;
1506 1793 measure.innerHTML = "";
1507 1794 return cachedHeight;
1508 1795 }
1509 1796 var cachedWidth, cachedWidthFor = 0;
1510 1797 function charWidth() {
1511 1798 if (scroller.clientWidth == cachedWidthFor) return cachedWidth;
1512 1799 cachedWidthFor = scroller.clientWidth;
1513 1800 return (cachedWidth = stringWidth("x"));
1514 1801 }
1515 1802 function paddingTop() {return lineSpace.offsetTop;}
1516 1803 function paddingLeft() {return lineSpace.offsetLeft;}
1517 1804
1518 1805 function posFromMouse(e, liberal) {
1519 1806 var offW = eltOffset(scroller, true), x, y;
1520 1807 // Fails unpredictably on IE[67] when mouse is dragged around quickly.
1521 1808 try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
1522 1809 // This is a mess of a heuristic to try and determine whether a
1523 1810 // scroll-bar was clicked or not, and to return null if one was
1524 1811 // (and !liberal).
1525 1812 if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight))
1526 1813 return null;
1527 1814 var offL = eltOffset(lineSpace, true);
1528 1815 return coordsChar(x - offL.left, y - offL.top);
1529 1816 }
1530 1817 function onContextMenu(e) {
1531 var pos = posFromMouse(e);
1532 if (!pos || window.opera) return; // Opera is difficult.
1818 var pos = posFromMouse(e), scrollPos = scrollbar.scrollTop;
1819 if (!pos || opera) return; // Opera is difficult.
1533 1820 if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
1534 1821 operation(setCursor)(pos.line, pos.ch);
1535 1822
1536 1823 var oldCSS = input.style.cssText;
1537 1824 inputDiv.style.position = "absolute";
1538 1825 input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
1539 1826 "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " +
1540 1827 "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
1541 1828 leaveInputAlone = true;
1542 1829 var val = input.value = getSelection();
1543 1830 focusInput();
1544 1831 selectInput(input);
1545 1832 function rehide() {
1546 1833 var newVal = splitLines(input.value).join("\n");
1547 if (newVal != val) operation(replaceSelection)(newVal, "end");
1834 if (newVal != val && !options.readOnly) operation(replaceSelection)(newVal, "end");
1548 1835 inputDiv.style.position = "relative";
1549 1836 input.style.cssText = oldCSS;
1837 if (ie_lt9) scrollbar.scrollTop = scrollPos;
1550 1838 leaveInputAlone = false;
1551 1839 resetInput(true);
1552 1840 slowPoll();
1553 1841 }
1554 1842
1555 1843 if (gecko) {
1556 1844 e_stop(e);
1557 1845 var mouseup = connect(window, "mouseup", function() {
1558 1846 mouseup();
1559 1847 setTimeout(rehide, 20);
1560 1848 }, true);
1561 }
1562 else {
1849 } else {
1563 1850 setTimeout(rehide, 50);
1564 1851 }
1565 1852 }
1566 1853
1567 1854 // Cursor-blinking
1568 1855 function restartBlink() {
1569 1856 clearInterval(blinker);
1570 1857 var on = true;
1571 1858 cursor.style.visibility = "";
1572 1859 blinker = setInterval(function() {
1573 1860 cursor.style.visibility = (on = !on) ? "" : "hidden";
1574 1861 }, 650);
1575 1862 }
1576 1863
1577 1864 var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
1578 1865 function matchBrackets(autoclear) {
1579 1866 var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1;
1580 1867 var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
1581 1868 if (!match) return;
1582 1869 var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles;
1583 1870 for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2)
1584 1871 if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;}
1585 1872
1586 1873 var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;
1587 1874 function scan(line, from, to) {
1588 1875 if (!line.text) return;
1589 1876 var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur;
1590 1877 for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) {
1591 1878 var text = st[i];
1592 if (st[i+1] != null && st[i+1] != style) {pos += d * text.length; continue;}
1879 if (st[i+1] != style) {pos += d * text.length; continue;}
1593 1880 for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) {
1594 1881 if (pos >= from && pos < to && re.test(cur = text.charAt(j))) {
1595 1882 var match = matching[cur];
1596 1883 if (match.charAt(1) == ">" == forward) stack.push(cur);
1597 1884 else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};
1598 1885 else if (!stack.length) return {pos: pos, match: true};
1599 1886 }
1600 1887 }
1601 1888 }
1602 1889 }
1603 1890 for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) {
1604 1891 var line = getLine(i), first = i == head.line;
1605 1892 var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);
1606 1893 if (found) break;
1607 1894 }
1608 1895 if (!found) found = {pos: null, match: false};
1609 1896 var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
1610 1897 var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),
1611 1898 two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);
1612 1899 var clear = operation(function(){one.clear(); two && two.clear();});
1613 1900 if (autoclear) setTimeout(clear, 800);
1614 1901 else bracketHighlighted = clear;
1615 1902 }
1616 1903
1617 1904 // Finds the line to start with when starting a parse. Tries to
1618 1905 // find a line with a stateAfter, so that it can start with a
1619 1906 // valid state. If that fails, it returns the line with the
1620 1907 // smallest indentation, which tends to need the least context to
1621 1908 // parse correctly.
1622 1909 function findStartLine(n) {
1623 1910 var minindent, minline;
1624 1911 for (var search = n, lim = n - 40; search > lim; --search) {
1625 1912 if (search == 0) return 0;
1626 1913 var line = getLine(search-1);
1627 1914 if (line.stateAfter) return search;
1628 1915 var indented = line.indentation(options.tabSize);
1629 1916 if (minline == null || minindent > indented) {
1630 1917 minline = search - 1;
1631 1918 minindent = indented;
1632 1919 }
1633 1920 }
1634 1921 return minline;
1635 1922 }
1636 1923 function getStateBefore(n) {
1637 1924 var start = findStartLine(n), state = start && getLine(start-1).stateAfter;
1638 1925 if (!state) state = startState(mode);
1639 1926 else state = copyState(mode, state);
1640 1927 doc.iter(start, n, function(line) {
1641 1928 line.highlight(mode, state, options.tabSize);
1642 1929 line.stateAfter = copyState(mode, state);
1643 1930 });
1644 1931 if (start < n) changes.push({from: start, to: n});
1645 1932 if (n < doc.size && !getLine(n).stateAfter) work.push(n);
1646 1933 return state;
1647 1934 }
1648 1935 function highlightLines(start, end) {
1649 1936 var state = getStateBefore(start);
1650 1937 doc.iter(start, end, function(line) {
1651 1938 line.highlight(mode, state, options.tabSize);
1652 1939 line.stateAfter = copyState(mode, state);
1653 1940 });
1654 1941 }
1655 1942 function highlightWorker() {
1656 1943 var end = +new Date + options.workTime;
1657 1944 var foundWork = work.length;
1658 1945 while (work.length) {
1659 1946 if (!getLine(showingFrom).stateAfter) var task = showingFrom;
1660 1947 else var task = work.pop();
1661 1948 if (task >= doc.size) continue;
1662 1949 var start = findStartLine(task), state = start && getLine(start-1).stateAfter;
1663 1950 if (state) state = copyState(mode, state);
1664 1951 else state = startState(mode);
1665 1952
1666 1953 var unchanged = 0, compare = mode.compareStates, realChange = false,
1667 1954 i = start, bail = false;
1668 1955 doc.iter(i, doc.size, function(line) {
1669 1956 var hadState = line.stateAfter;
1670 1957 if (+new Date > end) {
1671 1958 work.push(i);
1672 1959 startWorker(options.workDelay);
1673 1960 if (realChange) changes.push({from: task, to: i + 1});
1674 1961 return (bail = true);
1675 1962 }
1676 1963 var changed = line.highlight(mode, state, options.tabSize);
1677 1964 if (changed) realChange = true;
1678 1965 line.stateAfter = copyState(mode, state);
1966 var done = null;
1679 1967 if (compare) {
1680 if (hadState && compare(hadState, state)) return true;
1681 } else {
1968 var same = hadState && compare(hadState, state);
1969 if (same != Pass) done = !!same;
1970 }
1971 if (done == null) {
1682 1972 if (changed !== false || !hadState) unchanged = 0;
1683 1973 else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, "") == mode.indent(state, "")))
1684 return true;
1974 done = true;
1685 1975 }
1976 if (done) return true;
1686 1977 ++i;
1687 1978 });
1688 1979 if (bail) return;
1689 1980 if (realChange) changes.push({from: task, to: i + 1});
1690 1981 }
1691 1982 if (foundWork && options.onHighlightComplete)
1692 1983 options.onHighlightComplete(instance);
1693 1984 }
1694 1985 function startWorker(time) {
1695 1986 if (!work.length) return;
1696 1987 highlight.set(time, operation(highlightWorker));
1697 1988 }
1698 1989
1699 1990 // Operations are used to wrap changes in such a way that each
1700 1991 // change won't have to update the cursor and display (which would
1701 1992 // be awkward, slow, and error-prone), but instead updates are
1702 1993 // batched and then all combined and executed at once.
1703 1994 function startOperation() {
1704 1995 updateInput = userSelChange = textChanged = null;
1705 1996 changes = []; selectionChanged = false; callbacks = [];
1706 1997 }
1707 1998 function endOperation() {
1708 var reScroll = false, updated;
1709 if (selectionChanged) reScroll = !scrollCursorIntoView();
1710 if (changes.length) updated = updateDisplay(changes, true);
1999 if (updateMaxLine) computeMaxLength();
2000 if (maxLineChanged && !options.lineWrapping) {
2001 var cursorWidth = widthForcer.offsetWidth, left = stringWidth(maxLine);
2002 widthForcer.style.left = left + "px";
2003 lineSpace.style.minWidth = (left + cursorWidth) + "px";
2004 maxLineChanged = false;
2005 }
2006 var newScrollPos, updated;
2007 if (selectionChanged) {
2008 var coords = calculateCursorCoords();
2009 newScrollPos = calculateScrollPos(coords.x, coords.y, coords.x, coords.yBot);
2010 }
2011 if (changes.length) updated = updateDisplay(changes, true, (newScrollPos ? newScrollPos.scrollTop : null));
1711 2012 else {
1712 2013 if (selectionChanged) updateSelection();
1713 2014 if (gutterDirty) updateGutter();
1714 2015 }
1715 if (reScroll) scrollCursorIntoView();
2016 if (newScrollPos) scrollCursorIntoView();
1716 2017 if (selectionChanged) {scrollEditorIntoView(); restartBlink();}
1717 2018
1718 2019 if (focused && !leaveInputAlone &&
1719 2020 (updateInput === true || (updateInput !== false && selectionChanged)))
1720 2021 resetInput(userSelChange);
1721 2022
1722 2023 if (selectionChanged && options.matchBrackets)
1723 2024 setTimeout(operation(function() {
1724 2025 if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;}
1725 2026 if (posEq(sel.from, sel.to)) matchBrackets(false);
1726 2027 }), 20);
1727 var tc = textChanged, cbs = callbacks; // these can be reset by callbacks
1728 if (selectionChanged && options.onCursorActivity)
2028 var sc = selectionChanged, cbs = callbacks; // these can be reset by callbacks
2029 if (textChanged && options.onChange && instance)
2030 options.onChange(instance, textChanged);
2031 if (sc && options.onCursorActivity)
1729 2032 options.onCursorActivity(instance);
1730 if (tc && options.onChange && instance)
1731 options.onChange(instance, tc);
1732 2033 for (var i = 0; i < cbs.length; ++i) cbs[i](instance);
1733 2034 if (updated && options.onUpdate) options.onUpdate(instance);
1734 2035 }
1735 2036 var nestedOperation = 0;
1736 2037 function operation(f) {
1737 2038 return function() {
1738 2039 if (!nestedOperation++) startOperation();
1739 2040 try {var result = f.apply(this, arguments);}
1740 2041 finally {if (!--nestedOperation) endOperation();}
1741 2042 return result;
1742 2043 };
1743 2044 }
1744 2045
2046 function compoundChange(f) {
2047 history.startCompound();
2048 try { return f(); } finally { history.endCompound(); }
2049 }
2050
1745 2051 for (var ext in extensions)
1746 2052 if (extensions.propertyIsEnumerable(ext) &&
1747 2053 !instance.propertyIsEnumerable(ext))
1748 2054 instance[ext] = extensions[ext];
1749 2055 return instance;
1750 2056 } // (end of function CodeMirror)
1751 2057
1752 2058 // The default configuration options.
1753 2059 CodeMirror.defaults = {
1754 2060 value: "",
1755 2061 mode: null,
1756 2062 theme: "default",
1757 2063 indentUnit: 2,
1758 2064 indentWithTabs: false,
1759 2065 smartIndent: true,
1760 2066 tabSize: 4,
1761 2067 keyMap: "default",
1762 2068 extraKeys: null,
1763 2069 electricChars: true,
2070 autoClearEmptyLines: false,
1764 2071 onKeyEvent: null,
2072 onDragEvent: null,
1765 2073 lineWrapping: false,
1766 2074 lineNumbers: false,
1767 2075 gutter: false,
1768 2076 fixedGutter: false,
1769 2077 firstLineNumber: 1,
1770 2078 readOnly: false,
2079 dragDrop: true,
1771 2080 onChange: null,
1772 2081 onCursorActivity: null,
1773 2082 onGutterClick: null,
1774 2083 onHighlightComplete: null,
1775 2084 onUpdate: null,
1776 2085 onFocus: null, onBlur: null, onScroll: null,
1777 2086 matchBrackets: false,
1778 2087 workTime: 100,
1779 2088 workDelay: 200,
1780 2089 pollInterval: 100,
1781 2090 undoDepth: 40,
1782 2091 tabindex: null,
1783 document: window.document
2092 autofocus: null,
2093 lineNumberFormatter: function(integer) { return integer; }
1784 2094 };
1785 2095
1786 2096 var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
1787 2097 var mac = ios || /Mac/.test(navigator.platform);
1788 2098 var win = /Win/.test(navigator.platform);
1789 2099
1790 2100 // Known modes, by name and by MIME
1791 var modes = {}, mimeModes = {};
2101 var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
1792 2102 CodeMirror.defineMode = function(name, mode) {
1793 2103 if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
2104 if (arguments.length > 2) {
2105 mode.dependencies = [];
2106 for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]);
2107 }
1794 2108 modes[name] = mode;
1795 2109 };
1796 2110 CodeMirror.defineMIME = function(mime, spec) {
1797 2111 mimeModes[mime] = spec;
1798 2112 };
1799 CodeMirror.getMode = function(options, spec) {
2113 CodeMirror.resolveMode = function(spec) {
1800 2114 if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
1801 2115 spec = mimeModes[spec];
1802 if (typeof spec == "string")
1803 var mname = spec, config = {};
1804 else if (spec != null)
1805 var mname = spec.name, config = spec;
1806 var mfactory = modes[mname];
1807 if (!mfactory) {
1808 if (window.console) console.warn("No mode " + mname + " found, falling back to plain text.");
1809 return CodeMirror.getMode(options, "text/plain");
1810 }
1811 return mfactory(options, config || {});
2116 else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec))
2117 return CodeMirror.resolveMode("application/xml");
2118 if (typeof spec == "string") return {name: spec};
2119 else return spec || {name: "null"};
2120 };
2121 CodeMirror.getMode = function(options, spec) {
2122 var spec = CodeMirror.resolveMode(spec);
2123 var mfactory = modes[spec.name];
2124 if (!mfactory) return CodeMirror.getMode(options, "text/plain");
2125 return mfactory(options, spec);
1812 2126 };
1813 2127 CodeMirror.listModes = function() {
1814 2128 var list = [];
1815 2129 for (var m in modes)
1816 2130 if (modes.propertyIsEnumerable(m)) list.push(m);
1817 2131 return list;
1818 2132 };
1819 2133 CodeMirror.listMIMEs = function() {
1820 2134 var list = [];
1821 2135 for (var m in mimeModes)
1822 2136 if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]});
1823 2137 return list;
1824 2138 };
1825 2139
1826 2140 var extensions = CodeMirror.extensions = {};
1827 2141 CodeMirror.defineExtension = function(name, func) {
1828 2142 extensions[name] = func;
1829 2143 };
1830 2144
1831 2145 var commands = CodeMirror.commands = {
1832 2146 selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});},
1833 2147 killLine: function(cm) {
1834 2148 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
1835 2149 if (!sel && cm.getLine(from.line).length == from.ch) cm.replaceRange("", from, {line: from.line + 1, ch: 0});
1836 2150 else cm.replaceRange("", from, sel ? to : {line: from.line});
1837 2151 },
1838 2152 deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange("", {line: l, ch: 0}, {line: l});},
1839 2153 undo: function(cm) {cm.undo();},
1840 2154 redo: function(cm) {cm.redo();},
1841 2155 goDocStart: function(cm) {cm.setCursor(0, 0, true);},
1842 2156 goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);},
1843 2157 goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);},
1844 2158 goLineStartSmart: function(cm) {
1845 2159 var cur = cm.getCursor();
1846 2160 var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/));
1847 2161 cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true);
1848 2162 },
1849 2163 goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);},
1850 2164 goLineUp: function(cm) {cm.moveV(-1, "line");},
1851 2165 goLineDown: function(cm) {cm.moveV(1, "line");},
1852 2166 goPageUp: function(cm) {cm.moveV(-1, "page");},
1853 2167 goPageDown: function(cm) {cm.moveV(1, "page");},
1854 2168 goCharLeft: function(cm) {cm.moveH(-1, "char");},
1855 2169 goCharRight: function(cm) {cm.moveH(1, "char");},
1856 2170 goColumnLeft: function(cm) {cm.moveH(-1, "column");},
1857 2171 goColumnRight: function(cm) {cm.moveH(1, "column");},
1858 2172 goWordLeft: function(cm) {cm.moveH(-1, "word");},
1859 2173 goWordRight: function(cm) {cm.moveH(1, "word");},
1860 2174 delCharLeft: function(cm) {cm.deleteH(-1, "char");},
1861 2175 delCharRight: function(cm) {cm.deleteH(1, "char");},
1862 2176 delWordLeft: function(cm) {cm.deleteH(-1, "word");},
1863 2177 delWordRight: function(cm) {cm.deleteH(1, "word");},
1864 2178 indentAuto: function(cm) {cm.indentSelection("smart");},
1865 2179 indentMore: function(cm) {cm.indentSelection("add");},
1866 2180 indentLess: function(cm) {cm.indentSelection("subtract");},
1867 2181 insertTab: function(cm) {cm.replaceSelection("\t", "end");},
2182 defaultTab: function(cm) {
2183 if (cm.somethingSelected()) cm.indentSelection("add");
2184 else cm.replaceSelection("\t", "end");
2185 },
1868 2186 transposeChars: function(cm) {
1869 2187 var cur = cm.getCursor(), line = cm.getLine(cur.line);
1870 2188 if (cur.ch > 0 && cur.ch < line.length - 1)
1871 2189 cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
1872 2190 {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1});
1873 2191 },
1874 2192 newlineAndIndent: function(cm) {
1875 2193 cm.replaceSelection("\n", "end");
1876 2194 cm.indentLine(cm.getCursor().line);
1877 2195 },
1878 2196 toggleOverwrite: function(cm) {cm.toggleOverwrite();}
1879 2197 };
1880 2198
1881 2199 var keyMap = CodeMirror.keyMap = {};
1882 2200 keyMap.basic = {
1883 2201 "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
1884 2202 "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
1885 "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "indentMore", "Shift-Tab": "indentLess",
2203 "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "defaultTab", "Shift-Tab": "indentAuto",
1886 2204 "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
1887 2205 };
1888 2206 // Note that the save and find-related commands aren't defined by
1889 2207 // default. Unknown commands are simply ignored.
1890 2208 keyMap.pcDefault = {
1891 2209 "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
1892 2210 "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
1893 2211 "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
1894 2212 "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find",
1895 2213 "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
2214 "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
1896 2215 fallthrough: "basic"
1897 2216 };
1898 2217 keyMap.macDefault = {
1899 2218 "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
1900 2219 "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft",
1901 2220 "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft",
1902 2221 "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find",
1903 2222 "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
2223 "Cmd-[": "indentLess", "Cmd-]": "indentMore",
1904 2224 fallthrough: ["basic", "emacsy"]
1905 2225 };
1906 2226 keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
1907 2227 keyMap.emacsy = {
1908 2228 "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
1909 2229 "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
1910 2230 "Ctrl-V": "goPageUp", "Shift-Ctrl-V": "goPageDown", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft",
1911 2231 "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
1912 2232 };
1913 2233
1914 function lookupKey(name, extraMap, map) {
1915 function lookup(name, map, ft) {
2234 function getKeyMap(val) {
2235 if (typeof val == "string") return keyMap[val];
2236 else return val;
2237 }
2238 function lookupKey(name, extraMap, map, handle, stop) {
2239 function lookup(map) {
2240 map = getKeyMap(map);
1916 2241 var found = map[name];
1917 if (found != null) return found;
1918 if (ft == null) ft = map.fallthrough;
1919 if (ft == null) return map.catchall;
1920 if (typeof ft == "string") return lookup(name, keyMap[ft]);
1921 for (var i = 0, e = ft.length; i < e; ++i) {
1922 found = lookup(name, keyMap[ft[i]]);
1923 if (found != null) return found;
2242 if (found != null && handle(found)) return true;
2243 if (map.nofallthrough) {
2244 if (stop) stop();
2245 return true;
1924 2246 }
1925 return null;
2247 var fallthrough = map.fallthrough;
2248 if (fallthrough == null) return false;
2249 if (Object.prototype.toString.call(fallthrough) != "[object Array]")
2250 return lookup(fallthrough);
2251 for (var i = 0, e = fallthrough.length; i < e; ++i) {
2252 if (lookup(fallthrough[i])) return true;
2253 }
2254 return false;
1926 2255 }
1927 return extraMap ? lookup(name, extraMap, map) : lookup(name, keyMap[map]);
2256 if (extraMap && lookup(extraMap)) return true;
2257 return lookup(map);
1928 2258 }
1929 2259 function isModifierKey(event) {
1930 2260 var name = keyNames[e_prop(event, "keyCode")];
1931 2261 return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
1932 2262 }
1933 2263
1934 2264 CodeMirror.fromTextArea = function(textarea, options) {
1935 2265 if (!options) options = {};
1936 2266 options.value = textarea.value;
1937 2267 if (!options.tabindex && textarea.tabindex)
1938 2268 options.tabindex = textarea.tabindex;
2269 if (options.autofocus == null && textarea.getAttribute("autofocus") != null)
2270 options.autofocus = true;
1939 2271
1940 2272 function save() {textarea.value = instance.getValue();}
1941 2273 if (textarea.form) {
1942 2274 // Deplorable hack to make the submit method do the right thing.
1943 2275 var rmSubmit = connect(textarea.form, "submit", save, true);
1944 2276 if (typeof textarea.form.submit == "function") {
1945 2277 var realSubmit = textarea.form.submit;
1946 2278 function wrappedSubmit() {
1947 2279 save();
1948 2280 textarea.form.submit = realSubmit;
1949 2281 textarea.form.submit();
1950 2282 textarea.form.submit = wrappedSubmit;
1951 2283 }
1952 2284 textarea.form.submit = wrappedSubmit;
1953 2285 }
1954 2286 }
1955 2287
1956 2288 textarea.style.display = "none";
1957 2289 var instance = CodeMirror(function(node) {
1958 2290 textarea.parentNode.insertBefore(node, textarea.nextSibling);
1959 2291 }, options);
1960 2292 instance.save = save;
1961 2293 instance.getTextArea = function() { return textarea; };
1962 2294 instance.toTextArea = function() {
1963 2295 save();
1964 2296 textarea.parentNode.removeChild(instance.getWrapperElement());
1965 2297 textarea.style.display = "";
1966 2298 if (textarea.form) {
1967 2299 rmSubmit();
1968 2300 if (typeof textarea.form.submit == "function")
1969 2301 textarea.form.submit = realSubmit;
1970 2302 }
1971 2303 };
1972 2304 return instance;
1973 2305 };
1974 2306
1975 2307 // Utility functions for working with state. Exported because modes
1976 2308 // sometimes need to do this.
1977 2309 function copyState(mode, state) {
1978 2310 if (state === true) return state;
1979 2311 if (mode.copyState) return mode.copyState(state);
1980 2312 var nstate = {};
1981 2313 for (var n in state) {
1982 2314 var val = state[n];
1983 2315 if (val instanceof Array) val = val.concat([]);
1984 2316 nstate[n] = val;
1985 2317 }
1986 2318 return nstate;
1987 2319 }
1988 2320 CodeMirror.copyState = copyState;
1989 2321 function startState(mode, a1, a2) {
1990 2322 return mode.startState ? mode.startState(a1, a2) : true;
1991 2323 }
1992 2324 CodeMirror.startState = startState;
1993 2325
1994 2326 // The character stream used by a mode's parser.
1995 2327 function StringStream(string, tabSize) {
1996 2328 this.pos = this.start = 0;
1997 2329 this.string = string;
1998 2330 this.tabSize = tabSize || 8;
1999 2331 }
2000 2332 StringStream.prototype = {
2001 2333 eol: function() {return this.pos >= this.string.length;},
2002 2334 sol: function() {return this.pos == 0;},
2003 2335 peek: function() {return this.string.charAt(this.pos);},
2004 2336 next: function() {
2005 2337 if (this.pos < this.string.length)
2006 2338 return this.string.charAt(this.pos++);
2007 2339 },
2008 2340 eat: function(match) {
2009 2341 var ch = this.string.charAt(this.pos);
2010 2342 if (typeof match == "string") var ok = ch == match;
2011 2343 else var ok = ch && (match.test ? match.test(ch) : match(ch));
2012 2344 if (ok) {++this.pos; return ch;}
2013 2345 },
2014 2346 eatWhile: function(match) {
2015 2347 var start = this.pos;
2016 2348 while (this.eat(match)){}
2017 2349 return this.pos > start;
2018 2350 },
2019 2351 eatSpace: function() {
2020 2352 var start = this.pos;
2021 2353 while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
2022 2354 return this.pos > start;
2023 2355 },
2024 2356 skipToEnd: function() {this.pos = this.string.length;},
2025 2357 skipTo: function(ch) {
2026 2358 var found = this.string.indexOf(ch, this.pos);
2027 2359 if (found > -1) {this.pos = found; return true;}
2028 2360 },
2029 2361 backUp: function(n) {this.pos -= n;},
2030 2362 column: function() {return countColumn(this.string, this.start, this.tabSize);},
2031 2363 indentation: function() {return countColumn(this.string, null, this.tabSize);},
2032 2364 match: function(pattern, consume, caseInsensitive) {
2033 2365 if (typeof pattern == "string") {
2034 2366 function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
2035 2367 if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
2036 2368 if (consume !== false) this.pos += pattern.length;
2037 2369 return true;
2038 2370 }
2039 }
2040 else {
2371 } else {
2041 2372 var match = this.string.slice(this.pos).match(pattern);
2042 2373 if (match && consume !== false) this.pos += match[0].length;
2043 2374 return match;
2044 2375 }
2045 2376 },
2046 2377 current: function(){return this.string.slice(this.start, this.pos);}
2047 2378 };
2048 2379 CodeMirror.StringStream = StringStream;
2049 2380
2050 function MarkedText(from, to, className, set) {
2051 this.from = from; this.to = to; this.style = className; this.set = set;
2381 function MarkedText(from, to, className, marker) {
2382 this.from = from; this.to = to; this.style = className; this.marker = marker;
2052 2383 }
2053 2384 MarkedText.prototype = {
2054 attach: function(line) { this.set.push(line); },
2385 attach: function(line) { this.marker.set.push(line); },
2055 2386 detach: function(line) {
2056 var ix = indexOf(this.set, line);
2057 if (ix > -1) this.set.splice(ix, 1);
2387 var ix = indexOf(this.marker.set, line);
2388 if (ix > -1) this.marker.set.splice(ix, 1);
2058 2389 },
2059 2390 split: function(pos, lenBefore) {
2060 2391 if (this.to <= pos && this.to != null) return null;
2061 2392 var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore;
2062 2393 var to = this.to == null ? null : this.to - pos + lenBefore;
2063 return new MarkedText(from, to, this.style, this.set);
2394 return new MarkedText(from, to, this.style, this.marker);
2064 2395 },
2065 dup: function() { return new MarkedText(null, null, this.style, this.set); },
2396 dup: function() { return new MarkedText(null, null, this.style, this.marker); },
2066 2397 clipTo: function(fromOpen, from, toOpen, to, diff) {
2067 if (this.from != null && this.from >= from)
2068 this.from = Math.max(to, this.from) + diff;
2069 if (this.to != null && this.to > from)
2070 this.to = to < this.to ? this.to + diff : from;
2071 2398 if (fromOpen && to > this.from && (to < this.to || this.to == null))
2072 2399 this.from = null;
2400 else if (this.from != null && this.from >= from)
2401 this.from = Math.max(to, this.from) + diff;
2073 2402 if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null))
2074 2403 this.to = null;
2404 else if (this.to != null && this.to > from)
2405 this.to = to < this.to ? this.to + diff : from;
2075 2406 },
2076 2407 isDead: function() { return this.from != null && this.to != null && this.from >= this.to; },
2077 sameSet: function(x) { return this.set == x.set; }
2408 sameSet: function(x) { return this.marker == x.marker; }
2078 2409 };
2079 2410
2080 2411 function Bookmark(pos) {
2081 2412 this.from = pos; this.to = pos; this.line = null;
2082 2413 }
2083 2414 Bookmark.prototype = {
2084 2415 attach: function(line) { this.line = line; },
2085 2416 detach: function(line) { if (this.line == line) this.line = null; },
2086 2417 split: function(pos, lenBefore) {
2087 2418 if (pos < this.from) {
2088 2419 this.from = this.to = (this.from - pos) + lenBefore;
2089 2420 return this;
2090 2421 }
2091 2422 },
2092 2423 isDead: function() { return this.from > this.to; },
2093 2424 clipTo: function(fromOpen, from, toOpen, to, diff) {
2094 2425 if ((fromOpen || from < this.from) && (toOpen || to > this.to)) {
2095 2426 this.from = 0; this.to = -1;
2096 2427 } else if (this.from > from) {
2097 2428 this.from = this.to = Math.max(to, this.from) + diff;
2098 2429 }
2099 2430 },
2100 2431 sameSet: function(x) { return false; },
2101 2432 find: function() {
2102 2433 if (!this.line || !this.line.parent) return null;
2103 2434 return {line: lineNo(this.line), ch: this.from};
2104 2435 },
2105 2436 clear: function() {
2106 2437 if (this.line) {
2107 2438 var found = indexOf(this.line.marked, this);
2108 2439 if (found != -1) this.line.marked.splice(found, 1);
2109 2440 this.line = null;
2110 2441 }
2111 2442 }
2112 2443 };
2113 2444
2114 2445 // Line objects. These hold state related to a line, including
2115 2446 // highlighting info (the styles array).
2116 2447 function Line(text, styles) {
2117 2448 this.styles = styles || [text, null];
2118 2449 this.text = text;
2119 2450 this.height = 1;
2120 this.marked = this.gutterMarker = this.className = this.handlers = null;
2451 this.marked = this.gutterMarker = this.className = this.bgClassName = this.handlers = null;
2121 2452 this.stateAfter = this.parent = this.hidden = null;
2122 2453 }
2123 2454 Line.inheritMarks = function(text, orig) {
2124 2455 var ln = new Line(text), mk = orig && orig.marked;
2125 2456 if (mk) {
2126 2457 for (var i = 0; i < mk.length; ++i) {
2127 2458 if (mk[i].to == null && mk[i].style) {
2128 2459 var newmk = ln.marked || (ln.marked = []), mark = mk[i];
2129 2460 var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln);
2130 2461 }
2131 2462 }
2132 2463 }
2133 2464 return ln;
2134 2465 }
2135 2466 Line.prototype = {
2136 2467 // Replace a piece of a line, keeping the styles around it intact.
2137 2468 replace: function(from, to_, text) {
2138 2469 var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_;
2139 2470 copyStyles(0, from, this.styles, st);
2140 2471 if (text) st.push(text, null);
2141 2472 copyStyles(to, this.text.length, this.styles, st);
2142 2473 this.styles = st;
2143 2474 this.text = this.text.slice(0, from) + text + this.text.slice(to);
2144 2475 this.stateAfter = null;
2145 2476 if (mk) {
2146 2477 var diff = text.length - (to - from);
2147 2478 for (var i = 0; i < mk.length; ++i) {
2148 2479 var mark = mk[i];
2149 2480 mark.clipTo(from == null, from || 0, to_ == null, to, diff);
2150 2481 if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);}
2151 2482 }
2152 2483 }
2153 2484 },
2154 2485 // Split a part off a line, keeping styles and markers intact.
2155 2486 split: function(pos, textBefore) {
2156 2487 var st = [textBefore, null], mk = this.marked;
2157 2488 copyStyles(pos, this.text.length, this.styles, st);
2158 2489 var taken = new Line(textBefore + this.text.slice(pos), st);
2159 2490 if (mk) {
2160 2491 for (var i = 0; i < mk.length; ++i) {
2161 2492 var mark = mk[i];
2162 2493 var newmark = mark.split(pos, textBefore.length);
2163 2494 if (newmark) {
2164 2495 if (!taken.marked) taken.marked = [];
2165 2496 taken.marked.push(newmark); newmark.attach(taken);
2497 if (newmark == mark) mk.splice(i--, 1);
2166 2498 }
2167 2499 }
2168 2500 }
2169 2501 return taken;
2170 2502 },
2171 2503 append: function(line) {
2172 2504 var mylen = this.text.length, mk = line.marked, mymk = this.marked;
2173 2505 this.text += line.text;
2174 2506 copyStyles(0, line.text.length, line.styles, this.styles);
2175 2507 if (mymk) {
2176 2508 for (var i = 0; i < mymk.length; ++i)
2177 2509 if (mymk[i].to == null) mymk[i].to = mylen;
2178 2510 }
2179 2511 if (mk && mk.length) {
2180 2512 if (!mymk) this.marked = mymk = [];
2181 2513 outer: for (var i = 0; i < mk.length; ++i) {
2182 2514 var mark = mk[i];
2183 2515 if (!mark.from) {
2184 2516 for (var j = 0; j < mymk.length; ++j) {
2185 2517 var mymark = mymk[j];
2186 2518 if (mymark.to == mylen && mymark.sameSet(mark)) {
2187 2519 mymark.to = mark.to == null ? null : mark.to + mylen;
2188 2520 if (mymark.isDead()) {
2189 2521 mymark.detach(this);
2190 2522 mk.splice(i--, 1);
2191 2523 }
2192 2524 continue outer;
2193 2525 }
2194 2526 }
2195 2527 }
2196 2528 mymk.push(mark);
2197 2529 mark.attach(this);
2198 2530 mark.from += mylen;
2199 2531 if (mark.to != null) mark.to += mylen;
2200 2532 }
2201 2533 }
2202 2534 },
2203 2535 fixMarkEnds: function(other) {
2204 2536 var mk = this.marked, omk = other.marked;
2205 2537 if (!mk) return;
2206 for (var i = 0; i < mk.length; ++i) {
2538 outer: for (var i = 0; i < mk.length; ++i) {
2207 2539 var mark = mk[i], close = mark.to == null;
2208 2540 if (close && omk) {
2209 for (var j = 0; j < omk.length; ++j)
2210 if (omk[j].sameSet(mark)) {close = false; break;}
2541 for (var j = 0; j < omk.length; ++j) {
2542 var om = omk[j];
2543 if (!om.sameSet(mark) || om.from != null) continue
2544 if (mark.from == this.text.length && om.to == 0) {
2545 omk.splice(j, 1);
2546 mk.splice(i--, 1);
2547 continue outer;
2548 } else {
2549 close = false; break;
2550 }
2551 }
2211 2552 }
2212 2553 if (close) mark.to = this.text.length;
2213 2554 }
2214 2555 },
2215 2556 fixMarkStarts: function() {
2216 2557 var mk = this.marked;
2217 2558 if (!mk) return;
2218 2559 for (var i = 0; i < mk.length; ++i)
2219 2560 if (mk[i].from == null) mk[i].from = 0;
2220 2561 },
2221 2562 addMark: function(mark) {
2222 2563 mark.attach(this);
2223 2564 if (this.marked == null) this.marked = [];
2224 2565 this.marked.push(mark);
2225 2566 this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);});
2226 2567 },
2227 2568 // Run the given mode's parser over a line, update the styles
2228 2569 // array, which contains alternating fragments of text and CSS
2229 2570 // classes.
2230 2571 highlight: function(mode, state, tabSize) {
2231 2572 var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0;
2232 2573 var changed = false, curWord = st[0], prevWord;
2233 2574 if (this.text == "" && mode.blankLine) mode.blankLine(state);
2234 2575 while (!stream.eol()) {
2235 2576 var style = mode.token(stream, state);
2236 2577 var substr = this.text.slice(stream.start, stream.pos);
2237 2578 stream.start = stream.pos;
2238 2579 if (pos && st[pos-1] == style)
2239 2580 st[pos-2] += substr;
2240 2581 else if (substr) {
2241 2582 if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true;
2242 2583 st[pos++] = substr; st[pos++] = style;
2243 2584 prevWord = curWord; curWord = st[pos];
2244 2585 }
2245 2586 // Give up when line is ridiculously long
2246 2587 if (stream.pos > 5000) {
2247 2588 st[pos++] = this.text.slice(stream.pos); st[pos++] = null;
2248 2589 break;
2249 2590 }
2250 2591 }
2251 2592 if (st.length != pos) {st.length = pos; changed = true;}
2252 2593 if (pos && st[pos-2] != prevWord) changed = true;
2253 2594 // Short lines with simple highlights return null, and are
2254 2595 // counted as changed by the driver because they are likely to
2255 2596 // highlight the same way in various contexts.
2256 2597 return changed || (st.length < 5 && this.text.length < 10 ? null : false);
2257 2598 },
2258 2599 // Fetch the parser token for a given character. Useful for hacks
2259 2600 // that want to inspect the mode state (say, for completion).
2260 2601 getTokenAt: function(mode, state, ch) {
2261 2602 var txt = this.text, stream = new StringStream(txt);
2262 2603 while (stream.pos < ch && !stream.eol()) {
2263 2604 stream.start = stream.pos;
2264 2605 var style = mode.token(stream, state);
2265 2606 }
2266 2607 return {start: stream.start,
2267 2608 end: stream.pos,
2268 2609 string: stream.current(),
2269 2610 className: style || null,
2270 2611 state: state};
2271 2612 },
2272 2613 indentation: function(tabSize) {return countColumn(this.text, null, tabSize);},
2273 2614 // Produces an HTML fragment for the line, taking selection,
2274 2615 // marking, and highlighting into account.
2275 getHTML: function(tabText, endAt) {
2276 var html = [], first = true;
2277 function span(text, style) {
2616 getHTML: function(makeTab, wrapAt, wrapId, wrapWBR) {
2617 var html = [], first = true, col = 0;
2618 function span_(text, style) {
2278 2619 if (!text) return;
2279 2620 // Work around a bug where, in some compat modes, IE ignores leading spaces
2280 2621 if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1);
2281 2622 first = false;
2282 if (style) html.push('<span class="', style, '">', htmlEscape(text).replace(/\t/g, tabText), "</span>");
2283 else html.push(htmlEscape(text).replace(/\t/g, tabText));
2623 if (text.indexOf("\t") == -1) {
2624 col += text.length;
2625 var escaped = htmlEscape(text);
2626 } else {
2627 var escaped = "";
2628 for (var pos = 0;;) {
2629 var idx = text.indexOf("\t", pos);
2630 if (idx == -1) {
2631 escaped += htmlEscape(text.slice(pos));
2632 col += text.length - pos;
2633 break;
2634 } else {
2635 col += idx - pos;
2636 var tab = makeTab(col);
2637 escaped += htmlEscape(text.slice(pos, idx)) + tab.html;
2638 col += tab.width;
2639 pos = idx + 1;
2640 }
2641 }
2642 }
2643 if (style) html.push('<span class="', style, '">', escaped, "</span>");
2644 else html.push(escaped);
2645 }
2646 var span = span_;
2647 if (wrapAt != null) {
2648 var outPos = 0, open = "<span id=\"" + wrapId + "\">";
2649 span = function(text, style) {
2650 var l = text.length;
2651 if (wrapAt >= outPos && wrapAt < outPos + l) {
2652 if (wrapAt > outPos) {
2653 span_(text.slice(0, wrapAt - outPos), style);
2654 // See comment at the definition of spanAffectsWrapping
2655 if (wrapWBR) html.push("<wbr>");
2656 }
2657 html.push(open);
2658 var cut = wrapAt - outPos;
2659 span_(opera ? text.slice(cut, cut + 1) : text.slice(cut), style);
2660 html.push("</span>");
2661 if (opera) span_(text.slice(cut + 1), style);
2662 wrapAt--;
2663 outPos += l;
2664 } else {
2665 outPos += l;
2666 span_(text, style);
2667 // Output empty wrapper when at end of line
2668 // (Gecko and IE8+ do strange wrapping when adding a space
2669 // to the end of the line. Other browsers don't react well
2670 // to zero-width spaces. So we do hideous browser sniffing
2671 // to determine which to use.)
2672 if (outPos == wrapAt && outPos == len)
2673 html.push(open + (gecko || (ie && !ie_lt8) ? "&#x200b;" : " ") + "</span>");
2674 // Stop outputting HTML when gone sufficiently far beyond measure
2675 else if (outPos > wrapAt + 10 && /\s/.test(text)) span = function(){};
2676 }
2677 }
2284 2678 }
2679
2285 2680 var st = this.styles, allText = this.text, marked = this.marked;
2286 2681 var len = allText.length;
2287 if (endAt != null) len = Math.min(endAt, len);
2288 2682 function styleToClass(style) {
2289 2683 if (!style) return null;
2290 2684 return "cm-" + style.replace(/ +/g, " cm-");
2291 2685 }
2292 2686
2293 if (!allText && endAt == null)
2687 if (!allText && wrapAt == null) {
2294 2688 span(" ");
2295 else if (!marked || !marked.length)
2689 } else if (!marked || !marked.length) {
2296 2690 for (var i = 0, ch = 0; ch < len; i+=2) {
2297 2691 var str = st[i], style = st[i+1], l = str.length;
2298 2692 if (ch + l > len) str = str.slice(0, len - ch);
2299 2693 ch += l;
2300 2694 span(str, styleToClass(style));
2301 2695 }
2302 else {
2696 } else {
2303 2697 var pos = 0, i = 0, text = "", style, sg = 0;
2304 2698 var nextChange = marked[0].from || 0, marks = [], markpos = 0;
2305 2699 function advanceMarks() {
2306 2700 var m;
2307 2701 while (markpos < marked.length &&
2308 2702 ((m = marked[markpos]).from == pos || m.from == null)) {
2309 2703 if (m.style != null) marks.push(m);
2310 2704 ++markpos;
2311 2705 }
2312 2706 nextChange = markpos < marked.length ? marked[markpos].from : Infinity;
2313 2707 for (var i = 0; i < marks.length; ++i) {
2314 var to = marks[i].to || Infinity;
2708 var to = marks[i].to;
2709 if (to == null) to = Infinity;
2315 2710 if (to == pos) marks.splice(i--, 1);
2316 2711 else nextChange = Math.min(to, nextChange);
2317 2712 }
2318 2713 }
2319 2714 var m = 0;
2320 2715 while (pos < len) {
2321 2716 if (nextChange == pos) advanceMarks();
2322 2717 var upto = Math.min(len, nextChange);
2323 2718 while (true) {
2324 2719 if (text) {
2325 2720 var end = pos + text.length;
2326 2721 var appliedStyle = style;
2327 2722 for (var j = 0; j < marks.length; ++j)
2328 2723 appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].style;
2329 2724 span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle);
2330 2725 if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
2331 2726 pos = end;
2332 2727 }
2333 2728 text = st[i++]; style = styleToClass(st[i++]);
2334 2729 }
2335 2730 }
2336 2731 }
2337 2732 return html.join("");
2338 2733 },
2339 2734 cleanUp: function() {
2340 2735 this.parent = null;
2341 2736 if (this.marked)
2342 2737 for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this);
2343 2738 }
2344 2739 };
2345 2740 // Utility used by replace and split above
2346 2741 function copyStyles(from, to, source, dest) {
2347 2742 for (var i = 0, pos = 0, state = 0; pos < to; i+=2) {
2348 2743 var part = source[i], end = pos + part.length;
2349 2744 if (state == 0) {
2350 2745 if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]);
2351 2746 if (end >= from) state = 1;
2352 }
2353 else if (state == 1) {
2747 } else if (state == 1) {
2354 2748 if (end > to) dest.push(part.slice(0, to - pos), source[i+1]);
2355 2749 else dest.push(part, source[i+1]);
2356 2750 }
2357 2751 pos = end;
2358 2752 }
2359 2753 }
2360 2754
2361 2755 // Data structure that holds the sequence of lines.
2362 2756 function LeafChunk(lines) {
2363 2757 this.lines = lines;
2364 2758 this.parent = null;
2365 2759 for (var i = 0, e = lines.length, height = 0; i < e; ++i) {
2366 2760 lines[i].parent = this;
2367 2761 height += lines[i].height;
2368 2762 }
2369 2763 this.height = height;
2370 2764 }
2371 2765 LeafChunk.prototype = {
2372 2766 chunkSize: function() { return this.lines.length; },
2373 2767 remove: function(at, n, callbacks) {
2374 2768 for (var i = at, e = at + n; i < e; ++i) {
2375 2769 var line = this.lines[i];
2376 2770 this.height -= line.height;
2377 2771 line.cleanUp();
2378 2772 if (line.handlers)
2379 2773 for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]);
2380 2774 }
2381 2775 this.lines.splice(at, n);
2382 2776 },
2383 2777 collapse: function(lines) {
2384 2778 lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
2385 2779 },
2386 2780 insertHeight: function(at, lines, height) {
2387 2781 this.height += height;
2388 this.lines.splice.apply(this.lines, [at, 0].concat(lines));
2782 this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));
2389 2783 for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
2390 2784 },
2391 2785 iterN: function(at, n, op) {
2392 2786 for (var e = at + n; at < e; ++at)
2393 2787 if (op(this.lines[at])) return true;
2394 2788 }
2395 2789 };
2396 2790 function BranchChunk(children) {
2397 2791 this.children = children;
2398 2792 var size = 0, height = 0;
2399 2793 for (var i = 0, e = children.length; i < e; ++i) {
2400 2794 var ch = children[i];
2401 2795 size += ch.chunkSize(); height += ch.height;
2402 2796 ch.parent = this;
2403 2797 }
2404 2798 this.size = size;
2405 2799 this.height = height;
2406 2800 this.parent = null;
2407 2801 }
2408 2802 BranchChunk.prototype = {
2409 2803 chunkSize: function() { return this.size; },
2410 2804 remove: function(at, n, callbacks) {
2411 2805 this.size -= n;
2412 2806 for (var i = 0; i < this.children.length; ++i) {
2413 2807 var child = this.children[i], sz = child.chunkSize();
2414 2808 if (at < sz) {
2415 2809 var rm = Math.min(n, sz - at), oldHeight = child.height;
2416 2810 child.remove(at, rm, callbacks);
2417 2811 this.height -= oldHeight - child.height;
2418 2812 if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
2419 2813 if ((n -= rm) == 0) break;
2420 2814 at = 0;
2421 2815 } else at -= sz;
2422 2816 }
2423 2817 if (this.size - n < 25) {
2424 2818 var lines = [];
2425 2819 this.collapse(lines);
2426 2820 this.children = [new LeafChunk(lines)];
2427 2821 this.children[0].parent = this;
2428 2822 }
2429 2823 },
2430 2824 collapse: function(lines) {
2431 2825 for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
2432 2826 },
2433 2827 insert: function(at, lines) {
2434 2828 var height = 0;
2435 2829 for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
2436 2830 this.insertHeight(at, lines, height);
2437 2831 },
2438 2832 insertHeight: function(at, lines, height) {
2439 2833 this.size += lines.length;
2440 2834 this.height += height;
2441 2835 for (var i = 0, e = this.children.length; i < e; ++i) {
2442 2836 var child = this.children[i], sz = child.chunkSize();
2443 2837 if (at <= sz) {
2444 2838 child.insertHeight(at, lines, height);
2445 2839 if (child.lines && child.lines.length > 50) {
2446 2840 while (child.lines.length > 50) {
2447 2841 var spilled = child.lines.splice(child.lines.length - 25, 25);
2448 2842 var newleaf = new LeafChunk(spilled);
2449 2843 child.height -= newleaf.height;
2450 2844 this.children.splice(i + 1, 0, newleaf);
2451 2845 newleaf.parent = this;
2452 2846 }
2453 2847 this.maybeSpill();
2454 2848 }
2455 2849 break;
2456 2850 }
2457 2851 at -= sz;
2458 2852 }
2459 2853 },
2460 2854 maybeSpill: function() {
2461 2855 if (this.children.length <= 10) return;
2462 2856 var me = this;
2463 2857 do {
2464 2858 var spilled = me.children.splice(me.children.length - 5, 5);
2465 2859 var sibling = new BranchChunk(spilled);
2466 2860 if (!me.parent) { // Become the parent node
2467 2861 var copy = new BranchChunk(me.children);
2468 2862 copy.parent = me;
2469 2863 me.children = [copy, sibling];
2470 2864 me = copy;
2471 2865 } else {
2472 2866 me.size -= sibling.size;
2473 2867 me.height -= sibling.height;
2474 2868 var myIndex = indexOf(me.parent.children, me);
2475 2869 me.parent.children.splice(myIndex + 1, 0, sibling);
2476 2870 }
2477 2871 sibling.parent = me.parent;
2478 2872 } while (me.children.length > 10);
2479 2873 me.parent.maybeSpill();
2480 2874 },
2481 2875 iter: function(from, to, op) { this.iterN(from, to - from, op); },
2482 2876 iterN: function(at, n, op) {
2483 2877 for (var i = 0, e = this.children.length; i < e; ++i) {
2484 2878 var child = this.children[i], sz = child.chunkSize();
2485 2879 if (at < sz) {
2486 2880 var used = Math.min(n, sz - at);
2487 2881 if (child.iterN(at, used, op)) return true;
2488 2882 if ((n -= used) == 0) break;
2489 2883 at = 0;
2490 2884 } else at -= sz;
2491 2885 }
2492 2886 }
2493 2887 };
2494 2888
2495 2889 function getLineAt(chunk, n) {
2496 2890 while (!chunk.lines) {
2497 2891 for (var i = 0;; ++i) {
2498 2892 var child = chunk.children[i], sz = child.chunkSize();
2499 2893 if (n < sz) { chunk = child; break; }
2500 2894 n -= sz;
2501 2895 }
2502 2896 }
2503 2897 return chunk.lines[n];
2504 2898 }
2505 2899 function lineNo(line) {
2506 2900 if (line.parent == null) return null;
2507 2901 var cur = line.parent, no = indexOf(cur.lines, line);
2508 2902 for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
2509 2903 for (var i = 0, e = chunk.children.length; ; ++i) {
2510 2904 if (chunk.children[i] == cur) break;
2511 2905 no += chunk.children[i].chunkSize();
2512 2906 }
2513 2907 }
2514 2908 return no;
2515 2909 }
2516 2910 function lineAtHeight(chunk, h) {
2517 2911 var n = 0;
2518 2912 outer: do {
2519 2913 for (var i = 0, e = chunk.children.length; i < e; ++i) {
2520 2914 var child = chunk.children[i], ch = child.height;
2521 2915 if (h < ch) { chunk = child; continue outer; }
2522 2916 h -= ch;
2523 2917 n += child.chunkSize();
2524 2918 }
2525 2919 return n;
2526 2920 } while (!chunk.lines);
2527 2921 for (var i = 0, e = chunk.lines.length; i < e; ++i) {
2528 2922 var line = chunk.lines[i], lh = line.height;
2529 2923 if (h < lh) break;
2530 2924 h -= lh;
2531 2925 }
2532 2926 return n + i;
2533 2927 }
2534 2928 function heightAtLine(chunk, n) {
2535 2929 var h = 0;
2536 2930 outer: do {
2537 2931 for (var i = 0, e = chunk.children.length; i < e; ++i) {
2538 2932 var child = chunk.children[i], sz = child.chunkSize();
2539 2933 if (n < sz) { chunk = child; continue outer; }
2540 2934 n -= sz;
2541 2935 h += child.height;
2542 2936 }
2543 2937 return h;
2544 2938 } while (!chunk.lines);
2545 2939 for (var i = 0; i < n; ++i) h += chunk.lines[i].height;
2546 2940 return h;
2547 2941 }
2548 2942
2549 2943 // The history object 'chunks' changes that are made close together
2550 2944 // and at almost the same time into bigger undoable units.
2551 2945 function History() {
2552 2946 this.time = 0;
2553 2947 this.done = []; this.undone = [];
2948 this.compound = 0;
2949 this.closed = false;
2554 2950 }
2555 2951 History.prototype = {
2556 2952 addChange: function(start, added, old) {
2557 2953 this.undone.length = 0;
2558 2954 var time = +new Date, cur = this.done[this.done.length - 1], last = cur && cur[cur.length - 1];
2559 2955 var dtime = time - this.time;
2560 if (dtime > 400 || !last) {
2561 this.done.push([{start: start, added: added, old: old}]);
2562 } else if (last.start > start + added || last.start + last.added < start - last.added + last.old.length) {
2956
2957 if (this.compound && cur && !this.closed) {
2563 2958 cur.push({start: start, added: added, old: old});
2959 } else if (dtime > 400 || !last || this.closed ||
2960 last.start > start + old.length || last.start + last.added < start) {
2961 this.done.push([{start: start, added: added, old: old}]);
2962 this.closed = false;
2564 2963 } else {
2565 var oldoff = 0;
2566 if (start < last.start) {
2567 for (var i = last.start - start - 1; i >= 0; --i)
2568 last.old.unshift(old[i]);
2569 last.added += last.start - start;
2570 last.start = start;
2571 }
2572 else if (last.start < start) {
2573 oldoff = start - last.start;
2574 added += oldoff;
2575 }
2576 for (var i = last.added - oldoff, e = old.length; i < e; ++i)
2577 last.old.push(old[i]);
2578 if (last.added < added) last.added = added;
2964 var startBefore = Math.max(0, last.start - start),
2965 endAfter = Math.max(0, (start + old.length) - (last.start + last.added));
2966 for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]);
2967 for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]);
2968 if (startBefore) last.start = start;
2969 last.added += added - (old.length - startBefore - endAfter);
2579 2970 }
2580 2971 this.time = time;
2972 },
2973 startCompound: function() {
2974 if (!this.compound++) this.closed = true;
2975 },
2976 endCompound: function() {
2977 if (!--this.compound) this.closed = true;
2581 2978 }
2582 2979 };
2583 2980
2584 2981 function stopMethod() {e_stop(this);}
2585 2982 // Ensure an event has a stop method.
2586 2983 function addStop(event) {
2587 2984 if (!event.stop) event.stop = stopMethod;
2588 2985 return event;
2589 2986 }
2590 2987
2591 2988 function e_preventDefault(e) {
2592 2989 if (e.preventDefault) e.preventDefault();
2593 2990 else e.returnValue = false;
2594 2991 }
2595 2992 function e_stopPropagation(e) {
2596 2993 if (e.stopPropagation) e.stopPropagation();
2597 2994 else e.cancelBubble = true;
2598 2995 }
2599 2996 function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
2600 2997 CodeMirror.e_stop = e_stop;
2601 2998 CodeMirror.e_preventDefault = e_preventDefault;
2602 2999 CodeMirror.e_stopPropagation = e_stopPropagation;
2603 3000
2604 3001 function e_target(e) {return e.target || e.srcElement;}
2605 3002 function e_button(e) {
2606 if (e.which) return e.which;
2607 else if (e.button & 1) return 1;
2608 else if (e.button & 2) return 3;
2609 else if (e.button & 4) return 2;
3003 var b = e.which;
3004 if (b == null) {
3005 if (e.button & 1) b = 1;
3006 else if (e.button & 2) b = 3;
3007 else if (e.button & 4) b = 2;
3008 }
3009 if (mac && e.ctrlKey && b == 1) b = 3;
3010 return b;
2610 3011 }
2611 3012
2612 3013 // Allow 3rd-party code to override event properties by adding an override
2613 3014 // object to an event object.
2614 3015 function e_prop(e, prop) {
2615 3016 var overridden = e.override && e.override.hasOwnProperty(prop);
2616 3017 return overridden ? e.override[prop] : e[prop];
2617 3018 }
2618 3019
2619 3020 // Event handler registration. If disconnect is true, it'll return a
2620 3021 // function that unregisters the handler.
2621 3022 function connect(node, type, handler, disconnect) {
2622 3023 if (typeof node.addEventListener == "function") {
2623 3024 node.addEventListener(type, handler, false);
2624 3025 if (disconnect) return function() {node.removeEventListener(type, handler, false);};
2625 }
2626 else {
3026 } else {
2627 3027 var wrapHandler = function(event) {handler(event || window.event);};
2628 3028 node.attachEvent("on" + type, wrapHandler);
2629 3029 if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);};
2630 3030 }
2631 3031 }
2632 3032 CodeMirror.connect = connect;
2633 3033
2634 3034 function Delayed() {this.id = null;}
2635 3035 Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
2636 3036
2637 // Detect drag-and-drop
2638 var dragAndDrop = function() {
2639 // IE8 has ondragstart and ondrop properties, but doesn't seem to
2640 // actually support ondragstart the way it's supposed to work.
2641 if (/MSIE [1-8]\b/.test(navigator.userAgent)) return false;
2642 var div = document.createElement('div');
2643 return "draggable" in div;
2644 }();
3037 var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
2645 3038
2646 3039 var gecko = /gecko\/\d{7}/i.test(navigator.userAgent);
2647 3040 var ie = /MSIE \d/.test(navigator.userAgent);
3041 var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);
3042 var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent);
3043 var quirksMode = ie && document.documentMode == 5;
2648 3044 var webkit = /WebKit\//.test(navigator.userAgent);
3045 var chrome = /Chrome\//.test(navigator.userAgent);
3046 var opera = /Opera\//.test(navigator.userAgent);
3047 var safari = /Apple Computer/.test(navigator.vendor);
3048 var khtml = /KHTML\//.test(navigator.userAgent);
3049 var mac_geLion = /Mac OS X 10\D([7-9]|\d\d)\D/.test(navigator.userAgent);
3050
3051 // Detect drag-and-drop
3052 var dragAndDrop = function() {
3053 // There is *some* kind of drag-and-drop support in IE6-8, but I
3054 // couldn't get it to work yet.
3055 if (ie_lt9) return false;
3056 var div = document.createElement('div');
3057 return "draggable" in div || "dragDrop" in div;
3058 }();
2649 3059
2650 var lineSep = "\n";
2651 3060 // Feature-detect whether newlines in textareas are converted to \r\n
2652 (function () {
3061 var lineSep = function () {
2653 3062 var te = document.createElement("textarea");
2654 3063 te.value = "foo\nbar";
2655 if (te.value.indexOf("\r") > -1) lineSep = "\r\n";
2656 }());
3064 if (te.value.indexOf("\r") > -1) return "\r\n";
3065 return "\n";
3066 }();
3067
3068 // For a reason I have yet to figure out, some browsers disallow
3069 // word wrapping between certain characters *only* if a new inline
3070 // element is started between them. This makes it hard to reliably
3071 // measure the position of things, since that requires inserting an
3072 // extra span. This terribly fragile set of regexps matches the
3073 // character combinations that suffer from this phenomenon on the
3074 // various browsers.
3075 var spanAffectsWrapping = /^$/; // Won't match any two-character string
3076 if (gecko) spanAffectsWrapping = /$'/;
3077 else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/;
3078 else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/;
2657 3079
2658 3080 // Counts the column offset in a string, taking tabs into account.
2659 3081 // Used mostly to find indentation.
2660 3082 function countColumn(string, end, tabSize) {
2661 3083 if (end == null) {
2662 3084 end = string.search(/[^\s\u00a0]/);
2663 3085 if (end == -1) end = string.length;
2664 3086 }
2665 3087 for (var i = 0, n = 0; i < end; ++i) {
2666 3088 if (string.charAt(i) == "\t") n += tabSize - (n % tabSize);
2667 3089 else ++n;
2668 3090 }
2669 3091 return n;
2670 3092 }
2671 3093
2672 3094 function computedStyle(elt) {
2673 3095 if (elt.currentStyle) return elt.currentStyle;
2674 3096 return window.getComputedStyle(elt, null);
2675 3097 }
2676 3098
2677 // Find the position of an element by following the offsetParent chain.
2678 // If screen==true, it returns screen (rather than page) coordinates.
2679 3099 function eltOffset(node, screen) {
2680 var bod = node.ownerDocument.body;
2681 var x = 0, y = 0, skipBody = false;
2682 for (var n = node; n; n = n.offsetParent) {
2683 var ol = n.offsetLeft, ot = n.offsetTop;
2684 // Firefox reports weird inverted offsets when the body has a border.
2685 if (n == bod) { x += Math.abs(ol); y += Math.abs(ot); }
2686 else { x += ol, y += ot; }
2687 if (screen && computedStyle(n).position == "fixed")
2688 skipBody = true;
2689 }
2690 var e = screen && !skipBody ? null : bod;
2691 for (var n = node.parentNode; n != e; n = n.parentNode)
2692 if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;}
2693 return {left: x, top: y};
2694 }
2695 // Use the faster and saner getBoundingClientRect method when possible.
2696 if (document.documentElement.getBoundingClientRect != null) eltOffset = function(node, screen) {
2697 3100 // Take the parts of bounding client rect that we are interested in so we are able to edit if need be,
2698 3101 // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page)
2699 3102 try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; }
2700 3103 catch(e) { box = {top: 0, left: 0}; }
2701 3104 if (!screen) {
2702 3105 // Get the toplevel scroll, working around browser differences.
2703 3106 if (window.pageYOffset == null) {
2704 3107 var t = document.documentElement || document.body.parentNode;
2705 3108 if (t.scrollTop == null) t = document.body;
2706 3109 box.top += t.scrollTop; box.left += t.scrollLeft;
2707 3110 } else {
2708 3111 box.top += window.pageYOffset; box.left += window.pageXOffset;
2709 3112 }
2710 3113 }
2711 3114 return box;
2712 };
3115 }
2713 3116
2714 3117 // Get a node's text content.
2715 3118 function eltText(node) {
2716 3119 return node.textContent || node.innerText || node.nodeValue || "";
2717 3120 }
2718 3121 function selectInput(node) {
2719 3122 if (ios) { // Mobile Safari apparently has a bug where select() is broken.
2720 3123 node.selectionStart = 0;
2721 3124 node.selectionEnd = node.value.length;
2722 3125 } else node.select();
2723 3126 }
2724 3127
2725 3128 // Operations on {line, ch} objects.
2726 3129 function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
2727 3130 function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
2728 3131 function copyPos(x) {return {line: x.line, ch: x.ch};}
2729 3132
2730 3133 var escapeElement = document.createElement("pre");
2731 3134 function htmlEscape(str) {
2732 3135 escapeElement.textContent = str;
2733 3136 return escapeElement.innerHTML;
2734 3137 }
2735 3138 // Recent (late 2011) Opera betas insert bogus newlines at the start
2736 3139 // of the textContent, so we strip those.
2737 if (htmlEscape("a") == "\na")
3140 if (htmlEscape("a") == "\na") {
2738 3141 htmlEscape = function(str) {
2739 3142 escapeElement.textContent = str;
2740 3143 return escapeElement.innerHTML.slice(1);
2741 3144 };
2742 3145 // Some IEs don't preserve tabs through innerHTML
2743 else if (htmlEscape("\t") != "\t")
3146 } else if (htmlEscape("\t") != "\t") {
2744 3147 htmlEscape = function(str) {
2745 3148 escapeElement.innerHTML = "";
2746 3149 escapeElement.appendChild(document.createTextNode(str));
2747 3150 return escapeElement.innerHTML;
2748 3151 };
3152 }
2749 3153 CodeMirror.htmlEscape = htmlEscape;
2750 3154
2751 3155 // Used to position the cursor after an undo/redo by finding the
2752 3156 // last edited character.
2753 3157 function editEnd(from, to) {
2754 3158 if (!to) return 0;
2755 3159 if (!from) return to.length;
2756 3160 for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j)
2757 3161 if (from.charAt(i) != to.charAt(j)) break;
2758 3162 return j + 1;
2759 3163 }
2760 3164
2761 3165 function indexOf(collection, elt) {
2762 3166 if (collection.indexOf) return collection.indexOf(elt);
2763 3167 for (var i = 0, e = collection.length; i < e; ++i)
2764 3168 if (collection[i] == elt) return i;
2765 3169 return -1;
2766 3170 }
2767 3171 function isWordChar(ch) {
2768 3172 return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase();
2769 3173 }
2770 3174
2771 3175 // See if "".split is the broken IE version, if so, provide an
2772 3176 // alternative way to split lines.
2773 3177 var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
2774 var pos = 0, nl, result = [];
2775 while ((nl = string.indexOf("\n", pos)) > -1) {
2776 result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl));
2777 pos = nl + 1;
3178 var pos = 0, result = [], l = string.length;
3179 while (pos <= l) {
3180 var nl = string.indexOf("\n", pos);
3181 if (nl == -1) nl = string.length;
3182 var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl);
3183 var rt = line.indexOf("\r");
3184 if (rt != -1) {
3185 result.push(line.slice(0, rt));
3186 pos += rt + 1;
3187 } else {
3188 result.push(line);
3189 pos = nl + 1;
3190 }
2778 3191 }
2779 result.push(string.slice(pos));
2780 3192 return result;
2781 } : function(string){return string.split(/\r?\n/);};
3193 } : function(string){return string.split(/\r\n?|\n/);};
2782 3194 CodeMirror.splitLines = splitLines;
2783 3195
2784 3196 var hasSelection = window.getSelection ? function(te) {
2785 3197 try { return te.selectionStart != te.selectionEnd; }
2786 3198 catch(e) { return false; }
2787 3199 } : function(te) {
2788 3200 try {var range = te.ownerDocument.selection.createRange();}
2789 3201 catch(e) {}
2790 3202 if (!range || range.parentElement() != te) return false;
2791 3203 return range.compareEndPoints("StartToEnd", range) != 0;
2792 3204 };
2793 3205
2794 3206 CodeMirror.defineMode("null", function() {
2795 3207 return {token: function(stream) {stream.skipToEnd();}};
2796 3208 });
2797 3209 CodeMirror.defineMIME("text/plain", "null");
2798 3210
2799 3211 var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
2800 3212 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
2801 3213 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
2802 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 186: ";", 187: "=", 188: ",",
2803 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'", 63276: "PageUp",
2804 63277: "PageDown", 63275: "End", 63273: "Home", 63234: "Left", 63232: "Up", 63235: "Right",
2805 63233: "Down", 63302: "Insert", 63272: "Delete"};
3214 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete",
3215 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
3216 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home",
3217 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"};
2806 3218 CodeMirror.keyNames = keyNames;
2807 3219 (function() {
2808 3220 // Number keys
2809 3221 for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i);
2810 3222 // Alphabetic keys
2811 3223 for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
2812 3224 // Function keys
2813 3225 for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
2814 3226 })();
2815 3227
2816 3228 return CodeMirror;
2817 3229 })();
@@ -1,23 +1,27
1 1 .CodeMirror-dialog {
2 2 position: relative;
3 3 }
4 4
5 5 .CodeMirror-dialog > div {
6 6 position: absolute;
7 7 top: 0; left: 0; right: 0;
8 8 background: white;
9 9 border-bottom: 1px solid #eee;
10 10 z-index: 15;
11 11 padding: .1em .8em;
12 12 overflow: hidden;
13 13 color: #333;
14 14 }
15 15
16 16 .CodeMirror-dialog input {
17 17 border: none;
18 18 outline: none;
19 19 background: transparent;
20 20 width: 20em;
21 21 color: inherit;
22 22 font-family: monospace;
23 23 }
24
25 .CodeMirror-dialog button {
26 font-size: 70%;
27 } No newline at end of file
@@ -1,63 +1,67
1 1 // Open simple dialogs on top of an editor. Relies on dialog.css.
2 2
3 3 (function() {
4 4 function dialogDiv(cm, template) {
5 5 var wrap = cm.getWrapperElement();
6 6 var dialog = wrap.insertBefore(document.createElement("div"), wrap.firstChild);
7 7 dialog.className = "CodeMirror-dialog";
8 8 dialog.innerHTML = '<div>' + template + '</div>';
9 9 return dialog;
10 10 }
11 11
12 12 CodeMirror.defineExtension("openDialog", function(template, callback) {
13 13 var dialog = dialogDiv(this, template);
14 14 var closed = false, me = this;
15 15 function close() {
16 16 if (closed) return;
17 17 closed = true;
18 18 dialog.parentNode.removeChild(dialog);
19 19 }
20 var inp = dialog.getElementsByTagName("input")[0];
20 var inp = dialog.getElementsByTagName("input")[0], button;
21 21 if (inp) {
22 22 CodeMirror.connect(inp, "keydown", function(e) {
23 23 if (e.keyCode == 13 || e.keyCode == 27) {
24 24 CodeMirror.e_stop(e);
25 25 close();
26 26 me.focus();
27 27 if (e.keyCode == 13) callback(inp.value);
28 28 }
29 29 });
30 30 inp.focus();
31 31 CodeMirror.connect(inp, "blur", close);
32 } else if (button = dialog.getElementsByTagName("button")[0]) {
33 CodeMirror.connect(button, "click", close);
34 button.focus();
35 CodeMirror.connect(button, "blur", close);
32 36 }
33 37 return close;
34 38 });
35 39
36 40 CodeMirror.defineExtension("openConfirm", function(template, callbacks) {
37 41 var dialog = dialogDiv(this, template);
38 42 var buttons = dialog.getElementsByTagName("button");
39 43 var closed = false, me = this, blurring = 1;
40 44 function close() {
41 45 if (closed) return;
42 46 closed = true;
43 47 dialog.parentNode.removeChild(dialog);
44 48 me.focus();
45 49 }
46 50 buttons[0].focus();
47 51 for (var i = 0; i < buttons.length; ++i) {
48 52 var b = buttons[i];
49 53 (function(callback) {
50 54 CodeMirror.connect(b, "click", function(e) {
51 55 CodeMirror.e_preventDefault(e);
52 56 close();
53 57 if (callback) callback(me);
54 58 });
55 59 })(callbacks[i]);
56 60 CodeMirror.connect(b, "blur", function() {
57 61 --blurring;
58 62 setTimeout(function() { if (blurring <= 0) close(); }, 200);
59 63 });
60 64 CodeMirror.connect(b, "focus", function() { ++blurring; });
61 65 }
62 66 });
63 67 })(); No newline at end of file
@@ -1,173 +1,196
1 1 // the tagRangeFinder function is
2 2 // Copyright (C) 2011 by Daniel Glazman <daniel@glazman.org>
3 3 // released under the MIT license (../../LICENSE) like the rest of CodeMirror
4 CodeMirror.tagRangeFinder = function(cm, line) {
4 CodeMirror.tagRangeFinder = function(cm, line, hideEnd) {
5 5 var nameStartChar = "A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";
6 var nameChar = nameStartChar + "\-\.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
6 var nameChar = nameStartChar + "\-\:\.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
7 7 var xmlNAMERegExp = new RegExp("^[" + nameStartChar + "][" + nameChar + "]*");
8 8
9 9 var lineText = cm.getLine(line);
10 10 var found = false;
11 11 var tag = null;
12 12 var pos = 0;
13 13 while (!found) {
14 14 pos = lineText.indexOf("<", pos);
15 15 if (-1 == pos) // no tag on line
16 16 return;
17 17 if (pos + 1 < lineText.length && lineText[pos + 1] == "/") { // closing tag
18 18 pos++;
19 19 continue;
20 20 }
21 21 // ok we weem to have a start tag
22 22 if (!lineText.substr(pos + 1).match(xmlNAMERegExp)) { // not a tag name...
23 23 pos++;
24 24 continue;
25 25 }
26 26 var gtPos = lineText.indexOf(">", pos + 1);
27 27 if (-1 == gtPos) { // end of start tag not in line
28 28 var l = line + 1;
29 29 var foundGt = false;
30 30 var lastLine = cm.lineCount();
31 31 while (l < lastLine && !foundGt) {
32 32 var lt = cm.getLine(l);
33 33 var gt = lt.indexOf(">");
34 34 if (-1 != gt) { // found a >
35 35 foundGt = true;
36 36 var slash = lt.lastIndexOf("/", gt);
37 37 if (-1 != slash && slash < gt) {
38 38 var str = lineText.substr(slash, gt - slash + 1);
39 if (!str.match( /\/\s*\>/ )) // yep, that's the end of empty tag
40 return l+1;
39 if (!str.match( /\/\s*\>/ )) { // yep, that's the end of empty tag
40 if (hideEnd === true) l++;
41 return l;
42 }
41 43 }
42 44 }
43 45 l++;
44 46 }
45 47 found = true;
46 48 }
47 49 else {
48 50 var slashPos = lineText.lastIndexOf("/", gtPos);
49 51 if (-1 == slashPos) { // cannot be empty tag
50 52 found = true;
51 53 // don't continue
52 54 }
53 55 else { // empty tag?
54 56 // check if really empty tag
55 57 var str = lineText.substr(slashPos, gtPos - slashPos + 1);
56 58 if (!str.match( /\/\s*\>/ )) { // finally not empty
57 59 found = true;
58 60 // don't continue
59 61 }
60 62 }
61 63 }
62 64 if (found) {
63 65 var subLine = lineText.substr(pos + 1);
64 66 tag = subLine.match(xmlNAMERegExp);
65 67 if (tag) {
66 68 // we have an element name, wooohooo !
67 69 tag = tag[0];
68 70 // do we have the close tag on same line ???
69 71 if (-1 != lineText.indexOf("</" + tag + ">", pos)) // yep
70 72 {
71 73 found = false;
72 74 }
73 75 // we don't, so we have a candidate...
74 76 }
75 77 else
76 78 found = false;
77 79 }
78 80 if (!found)
79 81 pos++;
80 82 }
81 83
82 84 if (found) {
83 var startTag = "(\\<\\/" + tag + "\\>)|(\\<" + tag + "\\>)|(\\<" + tag + "\s)|(\\<" + tag + "$)";
85 var startTag = "(\\<\\/" + tag + "\\>)|(\\<" + tag + "\\>)|(\\<" + tag + "\\s)|(\\<" + tag + "$)";
84 86 var startTagRegExp = new RegExp(startTag, "g");
85 87 var endTag = "</" + tag + ">";
86 88 var depth = 1;
87 89 var l = line + 1;
88 90 var lastLine = cm.lineCount();
89 91 while (l < lastLine) {
90 92 lineText = cm.getLine(l);
91 93 var match = lineText.match(startTagRegExp);
92 94 if (match) {
93 95 for (var i = 0; i < match.length; i++) {
94 96 if (match[i] == endTag)
95 97 depth--;
96 98 else
97 99 depth++;
98 if (!depth)
99 return l+1;
100 if (!depth) {
101 if (hideEnd === true) l++;
102 return l;
103 }
100 104 }
101 105 }
102 106 l++;
103 107 }
104 108 return;
105 109 }
106 110 };
107 111
108 CodeMirror.braceRangeFinder = function(cm, line) {
109 var lineText = cm.getLine(line);
110 var startChar = lineText.lastIndexOf("{");
111 if (startChar < 0 || lineText.lastIndexOf("}") > startChar) return;
112 var tokenType = cm.getTokenAt({line: line, ch: startChar}).className;
112 CodeMirror.braceRangeFinder = function(cm, line, hideEnd) {
113 var lineText = cm.getLine(line), at = lineText.length, startChar, tokenType;
114 for (;;) {
115 var found = lineText.lastIndexOf("{", at);
116 if (found < 0) break;
117 tokenType = cm.getTokenAt({line: line, ch: found}).className;
118 if (!/^(comment|string)/.test(tokenType)) { startChar = found; break; }
119 at = found - 1;
120 }
121 if (startChar == null || lineText.lastIndexOf("}") > startChar) return;
113 122 var count = 1, lastLine = cm.lineCount(), end;
114 123 outer: for (var i = line + 1; i < lastLine; ++i) {
115 124 var text = cm.getLine(i), pos = 0;
116 125 for (;;) {
117 126 var nextOpen = text.indexOf("{", pos), nextClose = text.indexOf("}", pos);
118 127 if (nextOpen < 0) nextOpen = text.length;
119 128 if (nextClose < 0) nextClose = text.length;
120 129 pos = Math.min(nextOpen, nextClose);
121 130 if (pos == text.length) break;
122 131 if (cm.getTokenAt({line: i, ch: pos + 1}).className == tokenType) {
123 132 if (pos == nextOpen) ++count;
124 133 else if (!--count) { end = i; break outer; }
125 134 }
126 135 ++pos;
127 136 }
128 137 }
129 138 if (end == null || end == line + 1) return;
139 if (hideEnd === true) end++;
130 140 return end;
131 141 };
132 142
143 CodeMirror.indentRangeFinder = function(cm, line) {
144 var tabSize = cm.getOption("tabSize");
145 var myIndent = cm.getLineHandle(line).indentation(tabSize), last;
146 for (var i = line + 1, end = cm.lineCount(); i < end; ++i) {
147 var handle = cm.getLineHandle(i);
148 if (!/^\s*$/.test(handle.text)) {
149 if (handle.indentation(tabSize) <= myIndent) break;
150 last = i;
151 }
152 }
153 if (!last) return null;
154 return last + 1;
155 };
133 156
134 CodeMirror.newFoldFunction = function(rangeFinder, markText) {
157 CodeMirror.newFoldFunction = function(rangeFinder, markText, hideEnd) {
135 158 var folded = [];
136 159 if (markText == null) markText = '<div style="position: absolute; left: 2px; color:#600">&#x25bc;</div>%N%';
137 160
138 161 function isFolded(cm, n) {
139 162 for (var i = 0; i < folded.length; ++i) {
140 163 var start = cm.lineInfo(folded[i].start);
141 164 if (!start) folded.splice(i--, 1);
142 165 else if (start.line == n) return {pos: i, region: folded[i]};
143 166 }
144 167 }
145 168
146 169 function expand(cm, region) {
147 170 cm.clearMarker(region.start);
148 171 for (var i = 0; i < region.hidden.length; ++i)
149 172 cm.showLine(region.hidden[i]);
150 173 }
151 174
152 175 return function(cm, line) {
153 176 cm.operation(function() {
154 177 var known = isFolded(cm, line);
155 178 if (known) {
156 179 folded.splice(known.pos, 1);
157 180 expand(cm, known.region);
158 181 } else {
159 var end = rangeFinder(cm, line);
182 var end = rangeFinder(cm, line, hideEnd);
160 183 if (end == null) return;
161 184 var hidden = [];
162 185 for (var i = line + 1; i < end; ++i) {
163 186 var handle = cm.hideLine(i);
164 187 if (handle) hidden.push(handle);
165 188 }
166 189 var first = cm.setMarker(line, markText);
167 190 var region = {start: first, hidden: hidden};
168 191 cm.onDeleteLine(first, function() { expand(cm, region); });
169 192 folded.push(region);
170 193 }
171 194 });
172 195 };
173 196 };
@@ -1,291 +1,299
1 // ============== Formatting extensions ============================
1 // ============== Formatting extensions ============================
2 2 // A common storage for all mode-specific formatting features
3 3 if (!CodeMirror.modeExtensions) CodeMirror.modeExtensions = {};
4 4
5 5 // Returns the extension of the editor's current mode
6 6 CodeMirror.defineExtension("getModeExt", function () {
7 return CodeMirror.modeExtensions[this.getOption("mode")];
7 var mname = CodeMirror.resolveMode(this.getOption("mode")).name;
8 var ext = CodeMirror.modeExtensions[mname];
9 if (!ext) throw new Error("No extensions found for mode " + mname);
10 return ext;
8 11 });
9 12
10 13 // If the current mode is 'htmlmixed', returns the extension of a mode located at
11 14 // the specified position (can be htmlmixed, css or javascript). Otherwise, simply
12 15 // returns the extension of the editor's current mode.
13 16 CodeMirror.defineExtension("getModeExtAtPos", function (pos) {
14 17 var token = this.getTokenAt(pos);
15 18 if (token && token.state && token.state.mode)
16 19 return CodeMirror.modeExtensions[token.state.mode == "html" ? "htmlmixed" : token.state.mode];
17 20 else
18 21 return this.getModeExt();
19 22 });
20 23
21 24 // Comment/uncomment the specified range
22 25 CodeMirror.defineExtension("commentRange", function (isComment, from, to) {
23 26 var curMode = this.getModeExtAtPos(this.getCursor());
24 27 if (isComment) { // Comment range
25 28 var commentedText = this.getRange(from, to);
26 29 this.replaceRange(curMode.commentStart + this.getRange(from, to) + curMode.commentEnd
27 30 , from, to);
28 31 if (from.line == to.line && from.ch == to.ch) { // An empty comment inserted - put cursor inside
29 32 this.setCursor(from.line, from.ch + curMode.commentStart.length);
30 33 }
31 34 }
32 35 else { // Uncomment range
33 36 var selText = this.getRange(from, to);
34 37 var startIndex = selText.indexOf(curMode.commentStart);
35 38 var endIndex = selText.lastIndexOf(curMode.commentEnd);
36 39 if (startIndex > -1 && endIndex > -1 && endIndex > startIndex) {
37 40 // Take string till comment start
38 41 selText = selText.substr(0, startIndex)
39 42 // From comment start till comment end
40 43 + selText.substring(startIndex + curMode.commentStart.length, endIndex)
41 44 // From comment end till string end
42 45 + selText.substr(endIndex + curMode.commentEnd.length);
43 46 }
44 47 this.replaceRange(selText, from, to);
45 48 }
46 49 });
47 50
48 51 // Applies automatic mode-aware indentation to the specified range
49 52 CodeMirror.defineExtension("autoIndentRange", function (from, to) {
50 53 var cmInstance = this;
51 54 this.operation(function () {
52 55 for (var i = from.line; i <= to.line; i++) {
53 56 cmInstance.indentLine(i, "smart");
54 57 }
55 58 });
56 59 });
57 60
58 61 // Applies automatic formatting to the specified range
59 62 CodeMirror.defineExtension("autoFormatRange", function (from, to) {
60 63 var absStart = this.indexFromPos(from);
61 64 var absEnd = this.indexFromPos(to);
62 65 // Insert additional line breaks where necessary according to the
63 66 // mode's syntax
64 67 var res = this.getModeExt().autoFormatLineBreaks(this.getValue(), absStart, absEnd);
65 68 var cmInstance = this;
66 69
67 70 // Replace and auto-indent the range
68 71 this.operation(function () {
69 72 cmInstance.replaceRange(res, from, to);
70 73 var startLine = cmInstance.posFromIndex(absStart).line;
71 74 var endLine = cmInstance.posFromIndex(absStart + res.length).line;
72 75 for (var i = startLine; i <= endLine; i++) {
73 76 cmInstance.indentLine(i, "smart");
74 77 }
75 78 });
76 79 });
77 80
78 81 // Define extensions for a few modes
79 82
80 83 CodeMirror.modeExtensions["css"] = {
81 84 commentStart: "/*",
82 85 commentEnd: "*/",
83 86 wordWrapChars: [";", "\\{", "\\}"],
84 autoFormatLineBreaks: function (text) {
87 autoFormatLineBreaks: function (text, startPos, endPos) {
88 text = text.substring(startPos, endPos);
85 89 return text.replace(new RegExp("(;|\\{|\\})([^\r\n])", "g"), "$1\n$2");
86 90 }
87 91 };
88 92
89 93 CodeMirror.modeExtensions["javascript"] = {
90 94 commentStart: "/*",
91 95 commentEnd: "*/",
92 96 wordWrapChars: [";", "\\{", "\\}"],
93 97
94 98 getNonBreakableBlocks: function (text) {
95 99 var nonBreakableRegexes = [
96 100 new RegExp("for\\s*?\\(([\\s\\S]*?)\\)"),
101 new RegExp("\\\\\"([\\s\\S]*?)(\\\\\"|$)"),
102 new RegExp("\\\\\'([\\s\\S]*?)(\\\\\'|$)"),
97 103 new RegExp("'([\\s\\S]*?)('|$)"),
98 104 new RegExp("\"([\\s\\S]*?)(\"|$)"),
99 105 new RegExp("//.*([\r\n]|$)")
100 106 ];
101 107 var nonBreakableBlocks = new Array();
102 108 for (var i = 0; i < nonBreakableRegexes.length; i++) {
103 109 var curPos = 0;
104 110 while (curPos < text.length) {
105 111 var m = text.substr(curPos).match(nonBreakableRegexes[i]);
106 112 if (m != null) {
107 113 nonBreakableBlocks.push({
108 114 start: curPos + m.index,
109 115 end: curPos + m.index + m[0].length
110 116 });
111 117 curPos += m.index + Math.max(1, m[0].length);
112 118 }
113 119 else { // No more matches
114 120 break;
115 121 }
116 122 }
117 123 }
118 124 nonBreakableBlocks.sort(function (a, b) {
119 125 return a.start - b.start;
120 126 });
121 127
122 128 return nonBreakableBlocks;
123 129 },
124 130
125 autoFormatLineBreaks: function (text) {
131 autoFormatLineBreaks: function (text, startPos, endPos) {
132 text = text.substring(startPos, endPos);
126 133 var curPos = 0;
127 134 var reLinesSplitter = new RegExp("(;|\\{|\\})([^\r\n])", "g");
128 135 var nonBreakableBlocks = this.getNonBreakableBlocks(text);
129 136 if (nonBreakableBlocks != null) {
130 137 var res = "";
131 138 for (var i = 0; i < nonBreakableBlocks.length; i++) {
132 139 if (nonBreakableBlocks[i].start > curPos) { // Break lines till the block
133 140 res += text.substring(curPos, nonBreakableBlocks[i].start).replace(reLinesSplitter, "$1\n$2");
134 141 curPos = nonBreakableBlocks[i].start;
135 142 }
136 143 if (nonBreakableBlocks[i].start <= curPos
137 144 && nonBreakableBlocks[i].end >= curPos) { // Skip non-breakable block
138 145 res += text.substring(curPos, nonBreakableBlocks[i].end);
139 146 curPos = nonBreakableBlocks[i].end;
140 147 }
141 148 }
142 149 if (curPos < text.length - 1) {
143 150 res += text.substr(curPos).replace(reLinesSplitter, "$1\n$2");
144 151 }
145 152 return res;
146 153 }
147 154 else {
148 155 return text.replace(reLinesSplitter, "$1\n$2");
149 156 }
150 157 }
151 158 };
152 159
153 160 CodeMirror.modeExtensions["xml"] = {
154 161 commentStart: "<!--",
155 162 commentEnd: "-->",
156 163 wordWrapChars: [">"],
157 164
158 autoFormatLineBreaks: function (text) {
165 autoFormatLineBreaks: function (text, startPos, endPos) {
166 text = text.substring(startPos, endPos);
159 167 var lines = text.split("\n");
160 168 var reProcessedPortion = new RegExp("(^\\s*?<|^[^<]*?)(.+)(>\\s*?$|[^>]*?$)");
161 169 var reOpenBrackets = new RegExp("<", "g");
162 170 var reCloseBrackets = new RegExp("(>)([^\r\n])", "g");
163 171 for (var i = 0; i < lines.length; i++) {
164 172 var mToProcess = lines[i].match(reProcessedPortion);
165 173 if (mToProcess != null && mToProcess.length > 3) { // The line starts with whitespaces and ends with whitespaces
166 174 lines[i] = mToProcess[1]
167 175 + mToProcess[2].replace(reOpenBrackets, "\n$&").replace(reCloseBrackets, "$1\n$2")
168 176 + mToProcess[3];
169 177 continue;
170 178 }
171 179 }
172 180
173 181 return lines.join("\n");
174 182 }
175 183 };
176 184
177 185 CodeMirror.modeExtensions["htmlmixed"] = {
178 186 commentStart: "<!--",
179 187 commentEnd: "-->",
180 188 wordWrapChars: [">", ";", "\\{", "\\}"],
181 189
182 190 getModeInfos: function (text, absPos) {
183 191 var modeInfos = new Array();
184 192 modeInfos[0] =
185 193 {
186 194 pos: 0,
187 195 modeExt: CodeMirror.modeExtensions["xml"],
188 196 modeName: "xml"
189 197 };
190 198
191 199 var modeMatchers = new Array();
192 200 modeMatchers[0] =
193 201 {
194 202 regex: new RegExp("<style[^>]*>([\\s\\S]*?)(</style[^>]*>|$)", "i"),
195 203 modeExt: CodeMirror.modeExtensions["css"],
196 204 modeName: "css"
197 205 };
198 206 modeMatchers[1] =
199 207 {
200 208 regex: new RegExp("<script[^>]*>([\\s\\S]*?)(</script[^>]*>|$)", "i"),
201 209 modeExt: CodeMirror.modeExtensions["javascript"],
202 210 modeName: "javascript"
203 211 };
204 212
205 213 var lastCharPos = (typeof (absPos) !== "undefined" ? absPos : text.length - 1);
206 214 // Detect modes for the entire text
207 215 for (var i = 0; i < modeMatchers.length; i++) {
208 216 var curPos = 0;
209 217 while (curPos <= lastCharPos) {
210 218 var m = text.substr(curPos).match(modeMatchers[i].regex);
211 219 if (m != null) {
212 220 if (m.length > 1 && m[1].length > 0) {
213 221 // Push block begin pos
214 222 var blockBegin = curPos + m.index + m[0].indexOf(m[1]);
215 223 modeInfos.push(
216 224 {
217 225 pos: blockBegin,
218 226 modeExt: modeMatchers[i].modeExt,
219 227 modeName: modeMatchers[i].modeName
220 228 });
221 229 // Push block end pos
222 230 modeInfos.push(
223 231 {
224 232 pos: blockBegin + m[1].length,
225 233 modeExt: modeInfos[0].modeExt,
226 234 modeName: modeInfos[0].modeName
227 235 });
228 236 curPos += m.index + m[0].length;
229 237 continue;
230 238 }
231 239 else {
232 240 curPos += m.index + Math.max(m[0].length, 1);
233 241 }
234 242 }
235 243 else { // No more matches
236 244 break;
237 245 }
238 246 }
239 247 }
240 248 // Sort mode infos
241 249 modeInfos.sort(function sortModeInfo(a, b) {
242 250 return a.pos - b.pos;
243 251 });
244 252
245 253 return modeInfos;
246 254 },
247 255
248 256 autoFormatLineBreaks: function (text, startPos, endPos) {
249 257 var modeInfos = this.getModeInfos(text);
250 258 var reBlockStartsWithNewline = new RegExp("^\\s*?\n");
251 259 var reBlockEndsWithNewline = new RegExp("\n\\s*?$");
252 260 var res = "";
253 261 // Use modes info to break lines correspondingly
254 262 if (modeInfos.length > 1) { // Deal with multi-mode text
255 263 for (var i = 1; i <= modeInfos.length; i++) {
256 264 var selStart = modeInfos[i - 1].pos;
257 265 var selEnd = (i < modeInfos.length ? modeInfos[i].pos : endPos);
258 266
259 267 if (selStart >= endPos) { // The block starts later than the needed fragment
260 268 break;
261 269 }
262 270 if (selStart < startPos) {
263 271 if (selEnd <= startPos) { // The block starts earlier than the needed fragment
264 272 continue;
265 273 }
266 274 selStart = startPos;
267 275 }
268 276 if (selEnd > endPos) {
269 277 selEnd = endPos;
270 278 }
271 279 var textPortion = text.substring(selStart, selEnd);
272 280 if (modeInfos[i - 1].modeName != "xml") { // Starting a CSS or JavaScript block
273 281 if (!reBlockStartsWithNewline.test(textPortion)
274 282 && selStart > 0) { // The block does not start with a line break
275 283 textPortion = "\n" + textPortion;
276 284 }
277 285 if (!reBlockEndsWithNewline.test(textPortion)
278 286 && selEnd < text.length - 1) { // The block does not end with a line break
279 287 textPortion += "\n";
280 288 }
281 289 }
282 290 res += modeInfos[i - 1].modeExt.autoFormatLineBreaks(textPortion);
283 291 }
284 292 }
285 293 else { // Single-mode text
286 294 res = modeInfos[0].modeExt.autoFormatLineBreaks(text.substring(startPos, endPos));
287 295 }
288 296
289 297 return res;
290 298 }
291 299 };
@@ -1,83 +1,134
1 1 (function () {
2 2 function forEach(arr, f) {
3 3 for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
4 4 }
5 5
6 6 function arrayContains(arr, item) {
7 7 if (!Array.prototype.indexOf) {
8 8 var i = arr.length;
9 9 while (i--) {
10 10 if (arr[i] === item) {
11 11 return true;
12 12 }
13 13 }
14 14 return false;
15 15 }
16 16 return arr.indexOf(item) != -1;
17 17 }
18
19 CodeMirror.javascriptHint = function(editor) {
18
19 function scriptHint(editor, keywords, getToken) {
20 20 // Find the token at the cursor
21 var cur = editor.getCursor(), token = editor.getTokenAt(cur), tprop = token;
21 var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token;
22 22 // If it's not a 'word-style' token, ignore the token.
23 if (!/^[\w$_]*$/.test(token.string)) {
23 if (!/^[\w$_]*$/.test(token.string)) {
24 24 token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state,
25 25 className: token.string == "." ? "property" : null};
26 26 }
27 27 // If it is a property, find out what it is a property of.
28 28 while (tprop.className == "property") {
29 tprop = editor.getTokenAt({line: cur.line, ch: tprop.start});
29 tprop = getToken(editor, {line: cur.line, ch: tprop.start});
30 30 if (tprop.string != ".") return;
31 tprop = editor.getTokenAt({line: cur.line, ch: tprop.start});
31 tprop = getToken(editor, {line: cur.line, ch: tprop.start});
32 if (tprop.string == ')') {
33 var level = 1;
34 do {
35 tprop = getToken(editor, {line: cur.line, ch: tprop.start});
36 switch (tprop.string) {
37 case ')': level++; break;
38 case '(': level--; break;
39 default: break;
40 }
41 } while (level > 0)
42 tprop = getToken(editor, {line: cur.line, ch: tprop.start});
43 if (tprop.className == 'variable')
44 tprop.className = 'function';
45 else return; // no clue
46 }
32 47 if (!context) var context = [];
33 48 context.push(tprop);
34 49 }
35 return {list: getCompletions(token, context),
50 return {list: getCompletions(token, context, keywords),
36 51 from: {line: cur.line, ch: token.start},
37 52 to: {line: cur.line, ch: token.end}};
38 53 }
39 54
55 CodeMirror.javascriptHint = function(editor) {
56 return scriptHint(editor, javascriptKeywords,
57 function (e, cur) {return e.getTokenAt(cur);});
58 }
59
60 function getCoffeeScriptToken(editor, cur) {
61 // This getToken, it is for coffeescript, imitates the behavior of
62 // getTokenAt method in javascript.js, that is, returning "property"
63 // type and treat "." as indepenent token.
64 var token = editor.getTokenAt(cur);
65 if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') {
66 token.end = token.start;
67 token.string = '.';
68 token.className = "property";
69 }
70 else if (/^\.[\w$_]*$/.test(token.string)) {
71 token.className = "property";
72 token.start++;
73 token.string = token.string.replace(/\./, '');
74 }
75 return token;
76 }
77
78 CodeMirror.coffeescriptHint = function(editor) {
79 return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken);
80 }
81
40 82 var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
41 83 "toUpperCase toLowerCase split concat match replace search").split(" ");
42 84 var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
43 85 "lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
44 86 var funcProps = "prototype apply call bind".split(" ");
45 var keywords = ("break case catch continue debugger default delete do else false finally for function " +
87 var javascriptKeywords = ("break case catch continue debugger default delete do else false finally for function " +
46 88 "if in instanceof new null return switch throw true try typeof var void while with").split(" ");
89 var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
90 "if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
47 91
48 function getCompletions(token, context) {
92 function getCompletions(token, context, keywords) {
49 93 var found = [], start = token.string;
50 94 function maybeAdd(str) {
51 95 if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str);
52 96 }
53 97 function gatherCompletions(obj) {
54 98 if (typeof obj == "string") forEach(stringProps, maybeAdd);
55 99 else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
56 100 else if (obj instanceof Function) forEach(funcProps, maybeAdd);
57 101 for (var name in obj) maybeAdd(name);
58 102 }
59 103
60 104 if (context) {
61 105 // If this is a property, see if it belongs to some object we can
62 106 // find in the current environment.
63 107 var obj = context.pop(), base;
64 108 if (obj.className == "variable")
65 109 base = window[obj.string];
66 110 else if (obj.className == "string")
67 111 base = "";
68 112 else if (obj.className == "atom")
69 113 base = 1;
114 else if (obj.className == "function") {
115 if (window.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
116 (typeof window.jQuery == 'function'))
117 base = window.jQuery();
118 else if (window._ != null && (obj.string == '_') && (typeof window._ == 'function'))
119 base = window._();
120 }
70 121 while (base != null && context.length)
71 122 base = base[context.pop().string];
72 123 if (base != null) gatherCompletions(base);
73 124 }
74 125 else {
75 126 // If not, just look in the window object and any local scope
76 127 // (reading into JS mode internals to get at the local variables)
77 128 for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
78 129 gatherCompletions(window);
79 130 forEach(keywords, maybeAdd);
80 131 }
81 132 return found;
82 133 }
83 134 })();
@@ -1,51 +1,52
1 1 // Utility function that allows modes to be combined. The mode given
2 2 // as the base argument takes care of most of the normal mode
3 3 // functionality, but a second (typically simple) mode is used, which
4 4 // can override the style of text. Both modes get to parse all of the
5 5 // text, but when both assign a non-null style to a piece of code, the
6 6 // overlay wins, unless the combine argument was true, in which case
7 7 // the styles are combined.
8 8
9 CodeMirror.overlayParser = function(base, overlay, combine) {
9 // overlayParser is the old, deprecated name
10 CodeMirror.overlayMode = CodeMirror.overlayParser = function(base, overlay, combine) {
10 11 return {
11 12 startState: function() {
12 13 return {
13 14 base: CodeMirror.startState(base),
14 15 overlay: CodeMirror.startState(overlay),
15 16 basePos: 0, baseCur: null,
16 17 overlayPos: 0, overlayCur: null
17 18 };
18 19 },
19 20 copyState: function(state) {
20 21 return {
21 22 base: CodeMirror.copyState(base, state.base),
22 23 overlay: CodeMirror.copyState(overlay, state.overlay),
23 24 basePos: state.basePos, baseCur: null,
24 25 overlayPos: state.overlayPos, overlayCur: null
25 26 };
26 27 },
27 28
28 29 token: function(stream, state) {
29 30 if (stream.start == state.basePos) {
30 31 state.baseCur = base.token(stream, state.base);
31 32 state.basePos = stream.pos;
32 33 }
33 34 if (stream.start == state.overlayPos) {
34 35 stream.pos = stream.start;
35 36 state.overlayCur = overlay.token(stream, state.overlay);
36 37 state.overlayPos = stream.pos;
37 38 }
38 39 stream.pos = Math.min(state.basePos, state.overlayPos);
39 40 if (stream.eol()) state.basePos = state.overlayPos = 0;
40 41
41 42 if (state.overlayCur == null) return state.baseCur;
42 43 if (state.baseCur != null && combine) return state.baseCur + " " + state.overlayCur;
43 44 else return state.overlayCur;
44 45 },
45 46
46 indent: function(state, textAfter) {
47 indent: base.indent && function(state, textAfter) {
47 48 return base.indent(state.base, textAfter);
48 49 },
49 50 electricChars: base.electricChars
50 51 };
51 52 };
@@ -1,27 +1,49
1 CodeMirror.runMode = function(string, modespec, callback) {
2 var mode = CodeMirror.getMode({indentUnit: 2}, modespec);
1 CodeMirror.runMode = function(string, modespec, callback, options) {
2 var mode = CodeMirror.getMode(CodeMirror.defaults, modespec);
3 3 var isNode = callback.nodeType == 1;
4 var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize;
4 5 if (isNode) {
5 var node = callback, accum = [];
6 callback = function(string, style) {
7 if (string == "\n")
6 var node = callback, accum = [], col = 0;
7 callback = function(text, style) {
8 if (text == "\n") {
8 9 accum.push("<br>");
9 else if (style)
10 accum.push("<span class=\"cm-" + CodeMirror.htmlEscape(style) + "\">" + CodeMirror.htmlEscape(string) + "</span>");
10 col = 0;
11 return;
12 }
13 var escaped = "";
14 // HTML-escape and replace tabs
15 for (var pos = 0;;) {
16 var idx = text.indexOf("\t", pos);
17 if (idx == -1) {
18 escaped += CodeMirror.htmlEscape(text.slice(pos));
19 col += text.length - pos;
20 break;
21 } else {
22 col += idx - pos;
23 escaped += CodeMirror.htmlEscape(text.slice(pos, idx));
24 var size = tabSize - col % tabSize;
25 col += size;
26 for (var i = 0; i < size; ++i) escaped += " ";
27 pos = idx + 1;
28 }
29 }
30
31 if (style)
32 accum.push("<span class=\"cm-" + CodeMirror.htmlEscape(style) + "\">" + escaped + "</span>");
11 33 else
12 accum.push(CodeMirror.htmlEscape(string));
34 accum.push(escaped);
13 35 }
14 36 }
15 37 var lines = CodeMirror.splitLines(string), state = CodeMirror.startState(mode);
16 38 for (var i = 0, e = lines.length; i < e; ++i) {
17 39 if (i) callback("\n");
18 40 var stream = new CodeMirror.StringStream(lines[i]);
19 41 while (!stream.eol()) {
20 42 var style = mode.token(stream, state);
21 43 callback(stream.current(), style, i, stream.start);
22 44 stream.start = stream.pos;
23 45 }
24 46 }
25 47 if (isNode)
26 48 node.innerHTML = accum.join("");
27 49 };
@@ -1,114 +1,118
1 1 // Define search commands. Depends on dialog.js or another
2 2 // implementation of the openDialog method.
3 3
4 4 // Replace works a little oddly -- it will do the replace on the next
5 5 // Ctrl-G (or whatever is bound to findNext) press. You prevent a
6 6 // replace by making sure the match is no longer selected when hitting
7 7 // Ctrl-G.
8 8
9 9 (function() {
10 10 function SearchState() {
11 11 this.posFrom = this.posTo = this.query = null;
12 12 this.marked = [];
13 13 }
14 14 function getSearchState(cm) {
15 15 return cm._searchState || (cm._searchState = new SearchState());
16 16 }
17 function getSearchCursor(cm, query, pos) {
18 // Heuristic: if the query string is all lowercase, do a case insensitive search.
19 return cm.getSearchCursor(query, pos, typeof query == "string" && query == query.toLowerCase());
20 }
17 21 function dialog(cm, text, shortText, f) {
18 22 if (cm.openDialog) cm.openDialog(text, f);
19 23 else f(prompt(shortText, ""));
20 24 }
21 25 function confirmDialog(cm, text, shortText, fs) {
22 26 if (cm.openConfirm) cm.openConfirm(text, fs);
23 27 else if (confirm(shortText)) fs[0]();
24 28 }
25 29 function parseQuery(query) {
26 var isRE = query.match(/^\/(.*)\/$/);
27 return isRE ? new RegExp(isRE[1]) : query;
30 var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
31 return isRE ? new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i") : query;
28 32 }
29 33 var queryDialog =
30 'Search: <input type="text" style="width: 10em"> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
34 'Search: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
31 35 function doSearch(cm, rev) {
32 36 var state = getSearchState(cm);
33 37 if (state.query) return findNext(cm, rev);
34 38 dialog(cm, queryDialog, "Search for:", function(query) {
35 39 cm.operation(function() {
36 40 if (!query || state.query) return;
37 41 state.query = parseQuery(query);
38 42 if (cm.lineCount() < 2000) { // This is too expensive on big documents.
39 for (var cursor = cm.getSearchCursor(query); cursor.findNext();)
43 for (var cursor = getSearchCursor(cm, query); cursor.findNext();)
40 44 state.marked.push(cm.markText(cursor.from(), cursor.to(), "CodeMirror-searching"));
41 45 }
42 46 state.posFrom = state.posTo = cm.getCursor();
43 47 findNext(cm, rev);
44 48 });
45 49 });
46 50 }
47 51 function findNext(cm, rev) {cm.operation(function() {
48 52 var state = getSearchState(cm);
49 var cursor = cm.getSearchCursor(state.query, rev ? state.posFrom : state.posTo);
53 var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
50 54 if (!cursor.find(rev)) {
51 cursor = cm.getSearchCursor(state.query, rev ? {line: cm.lineCount() - 1} : {line: 0, ch: 0});
55 cursor = getSearchCursor(cm, state.query, rev ? {line: cm.lineCount() - 1} : {line: 0, ch: 0});
52 56 if (!cursor.find(rev)) return;
53 57 }
54 58 cm.setSelection(cursor.from(), cursor.to());
55 59 state.posFrom = cursor.from(); state.posTo = cursor.to();
56 60 })}
57 61 function clearSearch(cm) {cm.operation(function() {
58 62 var state = getSearchState(cm);
59 63 if (!state.query) return;
60 64 state.query = null;
61 65 for (var i = 0; i < state.marked.length; ++i) state.marked[i].clear();
62 66 state.marked.length = 0;
63 67 })}
64 68
65 69 var replaceQueryDialog =
66 'Replace: <input type="text" style="width: 10em"> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
67 var replacementQueryDialog = 'With: <input type="text" style="width: 10em">';
70 'Replace: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
71 var replacementQueryDialog = 'With: <input type="text" style="width: 10em"/>';
68 72 var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
69 73 function replace(cm, all) {
70 74 dialog(cm, replaceQueryDialog, "Replace:", function(query) {
71 75 if (!query) return;
72 76 query = parseQuery(query);
73 77 dialog(cm, replacementQueryDialog, "Replace with:", function(text) {
74 78 if (all) {
75 cm.operation(function() {
76 for (var cursor = cm.getSearchCursor(query); cursor.findNext();) {
79 cm.compoundChange(function() { cm.operation(function() {
80 for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
77 81 if (typeof query != "string") {
78 82 var match = cm.getRange(cursor.from(), cursor.to()).match(query);
79 83 cursor.replace(text.replace(/\$(\d)/, function(w, i) {return match[i];}));
80 84 } else cursor.replace(text);
81 85 }
82 });
86 })});
83 87 } else {
84 88 clearSearch(cm);
85 var cursor = cm.getSearchCursor(query, cm.getCursor());
89 var cursor = getSearchCursor(cm, query, cm.getCursor());
86 90 function advance() {
87 91 var start = cursor.from(), match;
88 92 if (!(match = cursor.findNext())) {
89 cursor = cm.getSearchCursor(query);
93 cursor = getSearchCursor(cm, query);
90 94 if (!(match = cursor.findNext()) ||
91 (cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
95 (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
92 96 }
93 97 cm.setSelection(cursor.from(), cursor.to());
94 98 confirmDialog(cm, doReplaceConfirm, "Replace?",
95 99 [function() {doReplace(match);}, advance]);
96 100 }
97 101 function doReplace(match) {
98 102 cursor.replace(typeof query == "string" ? text :
99 103 text.replace(/\$(\d)/, function(w, i) {return match[i];}));
100 104 advance();
101 105 }
102 106 advance();
103 107 }
104 108 });
105 109 });
106 110 }
107 111
108 112 CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
109 113 CodeMirror.commands.findNext = doSearch;
110 114 CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
111 115 CodeMirror.commands.clearSearch = clearSearch;
112 116 CodeMirror.commands.replace = replace;
113 117 CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
114 118 })();
@@ -1,117 +1,117
1 1 (function(){
2 2 function SearchCursor(cm, query, pos, caseFold) {
3 3 this.atOccurrence = false; this.cm = cm;
4 if (caseFold == null) caseFold = typeof query == "string" && query == query.toLowerCase();
4 if (caseFold == null && typeof query == "string") caseFold = false;
5 5
6 6 pos = pos ? cm.clipPos(pos) : {line: 0, ch: 0};
7 7 this.pos = {from: pos, to: pos};
8 8
9 9 // The matches method is filled in based on the type of query.
10 10 // It takes a position and a direction, and returns an object
11 11 // describing the next occurrence of the query, or null if no
12 12 // more matches were found.
13 13 if (typeof query != "string") // Regexp match
14 14 this.matches = function(reverse, pos) {
15 15 if (reverse) {
16 16 var line = cm.getLine(pos.line).slice(0, pos.ch), match = line.match(query), start = 0;
17 17 while (match) {
18 18 var ind = line.indexOf(match[0]);
19 19 start += ind;
20 20 line = line.slice(ind + 1);
21 21 var newmatch = line.match(query);
22 22 if (newmatch) match = newmatch;
23 23 else break;
24 24 start++;
25 25 }
26 26 }
27 27 else {
28 28 var line = cm.getLine(pos.line).slice(pos.ch), match = line.match(query),
29 29 start = match && pos.ch + line.indexOf(match[0]);
30 30 }
31 31 if (match)
32 32 return {from: {line: pos.line, ch: start},
33 33 to: {line: pos.line, ch: start + match[0].length},
34 34 match: match};
35 35 };
36 36 else { // String query
37 37 if (caseFold) query = query.toLowerCase();
38 38 var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
39 39 var target = query.split("\n");
40 40 // Different methods for single-line and multi-line queries
41 41 if (target.length == 1)
42 42 this.matches = function(reverse, pos) {
43 43 var line = fold(cm.getLine(pos.line)), len = query.length, match;
44 44 if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
45 45 : (match = line.indexOf(query, pos.ch)) != -1)
46 46 return {from: {line: pos.line, ch: match},
47 47 to: {line: pos.line, ch: match + len}};
48 48 };
49 49 else
50 50 this.matches = function(reverse, pos) {
51 51 var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(cm.getLine(ln));
52 52 var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
53 53 if (reverse ? offsetA >= pos.ch || offsetA != match.length
54 54 : offsetA <= pos.ch || offsetA != line.length - match.length)
55 55 return;
56 56 for (;;) {
57 57 if (reverse ? !ln : ln == cm.lineCount() - 1) return;
58 58 line = fold(cm.getLine(ln += reverse ? -1 : 1));
59 59 match = target[reverse ? --idx : ++idx];
60 60 if (idx > 0 && idx < target.length - 1) {
61 61 if (line != match) return;
62 62 else continue;
63 63 }
64 64 var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length);
65 65 if (reverse ? offsetB != line.length - match.length : offsetB != match.length)
66 66 return;
67 67 var start = {line: pos.line, ch: offsetA}, end = {line: ln, ch: offsetB};
68 68 return {from: reverse ? end : start, to: reverse ? start : end};
69 69 }
70 70 };
71 71 }
72 72 }
73 73
74 74 SearchCursor.prototype = {
75 75 findNext: function() {return this.find(false);},
76 76 findPrevious: function() {return this.find(true);},
77 77
78 78 find: function(reverse) {
79 79 var self = this, pos = this.cm.clipPos(reverse ? this.pos.from : this.pos.to);
80 80 function savePosAndFail(line) {
81 81 var pos = {line: line, ch: 0};
82 82 self.pos = {from: pos, to: pos};
83 83 self.atOccurrence = false;
84 84 return false;
85 85 }
86 86
87 87 for (;;) {
88 88 if (this.pos = this.matches(reverse, pos)) {
89 89 this.atOccurrence = true;
90 90 return this.pos.match || true;
91 91 }
92 92 if (reverse) {
93 93 if (!pos.line) return savePosAndFail(0);
94 94 pos = {line: pos.line-1, ch: this.cm.getLine(pos.line-1).length};
95 95 }
96 96 else {
97 97 var maxLine = this.cm.lineCount();
98 98 if (pos.line == maxLine - 1) return savePosAndFail(maxLine);
99 99 pos = {line: pos.line+1, ch: 0};
100 100 }
101 101 }
102 102 },
103 103
104 104 from: function() {if (this.atOccurrence) return this.pos.from;},
105 105 to: function() {if (this.atOccurrence) return this.pos.to;},
106 106
107 107 replace: function(newText) {
108 108 var self = this;
109 109 if (this.atOccurrence)
110 110 self.pos.to = this.cm.replaceRange(newText, self.pos.from, self.pos.to);
111 111 }
112 112 };
113 113
114 114 CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
115 115 return new SearchCursor(this, query, pos, caseFold);
116 116 });
117 117 })();
1 NO CONTENT: modified file chmod 100755 => 100644
@@ -1,66 +1,76
1 1 (function() {
2 2 CodeMirror.simpleHint = function(editor, getHints) {
3 3 // We want a single cursor position.
4 4 if (editor.somethingSelected()) return;
5 //don't show completion if the token is empty
6 var tempToken = editor.getTokenAt(editor.getCursor());
7 if(!(/[\S]/gi.test(tempToken.string))) return;
8
5 9 var result = getHints(editor);
6 10 if (!result || !result.list.length) return;
7 11 var completions = result.list;
8 12 function insert(str) {
9 13 editor.replaceRange(str, result.from, result.to);
10 14 }
11 15 // When there is only one completion, use it directly.
12 16 if (completions.length == 1) {insert(completions[0]); return true;}
13 17
14 18 // Build the select widget
15 19 var complete = document.createElement("div");
16 20 complete.className = "CodeMirror-completions";
17 21 var sel = complete.appendChild(document.createElement("select"));
18 22 // Opera doesn't move the selection when pressing up/down in a
19 23 // multi-select, but it does properly support the size property on
20 24 // single-selects, so no multi-select is necessary.
21 25 if (!window.opera) sel.multiple = true;
22 26 for (var i = 0; i < completions.length; ++i) {
23 27 var opt = sel.appendChild(document.createElement("option"));
24 28 opt.appendChild(document.createTextNode(completions[i]));
25 29 }
26 30 sel.firstChild.selected = true;
27 31 sel.size = Math.min(10, completions.length);
28 32 var pos = editor.cursorCoords();
29 33 complete.style.left = pos.x + "px";
30 34 complete.style.top = pos.yBot + "px";
31 35 document.body.appendChild(complete);
36 // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
37 var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
38 if(winW - pos.x < sel.clientWidth)
39 complete.style.left = (pos.x - sel.clientWidth) + "px";
32 40 // Hack to hide the scrollbar.
33 41 if (completions.length <= 10)
34 42 complete.style.width = (sel.clientWidth - 1) + "px";
35 43
36 44 var done = false;
37 45 function close() {
38 46 if (done) return;
39 47 done = true;
40 48 complete.parentNode.removeChild(complete);
41 49 }
42 50 function pick() {
43 51 insert(completions[sel.selectedIndex]);
44 52 close();
45 53 setTimeout(function(){editor.focus();}, 50);
46 54 }
47 55 CodeMirror.connect(sel, "blur", close);
48 56 CodeMirror.connect(sel, "keydown", function(event) {
49 57 var code = event.keyCode;
50 58 // Enter
51 59 if (code == 13) {CodeMirror.e_stop(event); pick();}
52 60 // Escape
53 61 else if (code == 27) {CodeMirror.e_stop(event); close(); editor.focus();}
54 62 else if (code != 38 && code != 40) {
55 63 close(); editor.focus();
64 // Pass the event to the CodeMirror instance so that it can handle things like backspace properly.
65 editor.triggerOnKeyDown(event);
56 66 setTimeout(function(){CodeMirror.simpleHint(editor, getHints);}, 50);
57 67 }
58 68 });
59 69 CodeMirror.connect(sel, "dblclick", pick);
60 70
61 71 sel.focus();
62 72 // Opera sometimes ignores focusing a freshly created node
63 73 if (window.opera) setTimeout(function(){if (!done) sel.focus();}, 100);
64 74 return true;
65 75 };
66 76 })();
@@ -1,124 +1,124
1 1 CodeMirror.defineMode("css", function(config) {
2 2 var indentUnit = config.indentUnit, type;
3 3 function ret(style, tp) {type = tp; return style;}
4 4
5 5 function tokenBase(stream, state) {
6 6 var ch = stream.next();
7 7 if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("meta", stream.current());}
8 8 else if (ch == "/" && stream.eat("*")) {
9 9 state.tokenize = tokenCComment;
10 10 return tokenCComment(stream, state);
11 11 }
12 12 else if (ch == "<" && stream.eat("!")) {
13 13 state.tokenize = tokenSGMLComment;
14 14 return tokenSGMLComment(stream, state);
15 15 }
16 16 else if (ch == "=") ret(null, "compare");
17 17 else if ((ch == "~" || ch == "|") && stream.eat("=")) return ret(null, "compare");
18 18 else if (ch == "\"" || ch == "'") {
19 19 state.tokenize = tokenString(ch);
20 20 return state.tokenize(stream, state);
21 21 }
22 22 else if (ch == "#") {
23 23 stream.eatWhile(/[\w\\\-]/);
24 24 return ret("atom", "hash");
25 25 }
26 26 else if (ch == "!") {
27 27 stream.match(/^\s*\w*/);
28 28 return ret("keyword", "important");
29 29 }
30 30 else if (/\d/.test(ch)) {
31 31 stream.eatWhile(/[\w.%]/);
32 32 return ret("number", "unit");
33 33 }
34 34 else if (/[,.+>*\/]/.test(ch)) {
35 35 return ret(null, "select-op");
36 36 }
37 37 else if (/[;{}:\[\]]/.test(ch)) {
38 38 return ret(null, ch);
39 39 }
40 40 else {
41 41 stream.eatWhile(/[\w\\\-]/);
42 42 return ret("variable", "variable");
43 43 }
44 44 }
45 45
46 46 function tokenCComment(stream, state) {
47 47 var maybeEnd = false, ch;
48 48 while ((ch = stream.next()) != null) {
49 49 if (maybeEnd && ch == "/") {
50 50 state.tokenize = tokenBase;
51 51 break;
52 52 }
53 53 maybeEnd = (ch == "*");
54 54 }
55 55 return ret("comment", "comment");
56 56 }
57 57
58 58 function tokenSGMLComment(stream, state) {
59 59 var dashes = 0, ch;
60 60 while ((ch = stream.next()) != null) {
61 61 if (dashes >= 2 && ch == ">") {
62 62 state.tokenize = tokenBase;
63 63 break;
64 64 }
65 65 dashes = (ch == "-") ? dashes + 1 : 0;
66 66 }
67 67 return ret("comment", "comment");
68 68 }
69 69
70 70 function tokenString(quote) {
71 71 return function(stream, state) {
72 72 var escaped = false, ch;
73 73 while ((ch = stream.next()) != null) {
74 74 if (ch == quote && !escaped)
75 75 break;
76 76 escaped = !escaped && ch == "\\";
77 77 }
78 78 if (!escaped) state.tokenize = tokenBase;
79 79 return ret("string", "string");
80 80 };
81 81 }
82 82
83 83 return {
84 84 startState: function(base) {
85 85 return {tokenize: tokenBase,
86 86 baseIndent: base || 0,
87 87 stack: []};
88 88 },
89 89
90 90 token: function(stream, state) {
91 91 if (stream.eatSpace()) return null;
92 92 var style = state.tokenize(stream, state);
93 93
94 94 var context = state.stack[state.stack.length-1];
95 if (type == "hash" && context == "rule") style = "atom";
95 if (type == "hash" && context != "rule") style = "string-2";
96 96 else if (style == "variable") {
97 97 if (context == "rule") style = "number";
98 98 else if (!context || context == "@media{") style = "tag";
99 99 }
100 100
101 101 if (context == "rule" && /^[\{\};]$/.test(type))
102 102 state.stack.pop();
103 103 if (type == "{") {
104 104 if (context == "@media") state.stack[state.stack.length-1] = "@media{";
105 105 else state.stack.push("{");
106 106 }
107 107 else if (type == "}") state.stack.pop();
108 108 else if (type == "@media") state.stack.push("@media");
109 109 else if (context == "{" && type != "comment") state.stack.push("rule");
110 110 return style;
111 111 },
112 112
113 113 indent: function(state, textAfter) {
114 114 var n = state.stack.length;
115 115 if (/^\}/.test(textAfter))
116 116 n -= state.stack[state.stack.length-1] == "rule" ? 2 : 1;
117 117 return state.baseIndent + n * indentUnit;
118 118 },
119 119
120 120 electricChars: "}"
121 121 };
122 122 });
123 123
124 124 CodeMirror.defineMIME("text/css", "css");
1 NO CONTENT: modified file chmod 100755 => 100644
@@ -1,83 +1,85
1 1 CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
2 2 var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true});
3 3 var jsMode = CodeMirror.getMode(config, "javascript");
4 4 var cssMode = CodeMirror.getMode(config, "css");
5 5
6 6 function html(stream, state) {
7 7 var style = htmlMode.token(stream, state.htmlState);
8 8 if (style == "tag" && stream.current() == ">" && state.htmlState.context) {
9 9 if (/^script$/i.test(state.htmlState.context.tagName)) {
10 10 state.token = javascript;
11 11 state.localState = jsMode.startState(htmlMode.indent(state.htmlState, ""));
12 12 state.mode = "javascript";
13 13 }
14 14 else if (/^style$/i.test(state.htmlState.context.tagName)) {
15 15 state.token = css;
16 16 state.localState = cssMode.startState(htmlMode.indent(state.htmlState, ""));
17 17 state.mode = "css";
18 18 }
19 19 }
20 20 return style;
21 21 }
22 22 function maybeBackup(stream, pat, style) {
23 23 var cur = stream.current();
24 24 var close = cur.search(pat);
25 25 if (close > -1) stream.backUp(cur.length - close);
26 26 return style;
27 27 }
28 28 function javascript(stream, state) {
29 29 if (stream.match(/^<\/\s*script\s*>/i, false)) {
30 30 state.token = html;
31 state.curState = null;
31 state.localState = null;
32 32 state.mode = "html";
33 33 return html(stream, state);
34 34 }
35 35 return maybeBackup(stream, /<\/\s*script\s*>/,
36 36 jsMode.token(stream, state.localState));
37 37 }
38 38 function css(stream, state) {
39 39 if (stream.match(/^<\/\s*style\s*>/i, false)) {
40 40 state.token = html;
41 41 state.localState = null;
42 42 state.mode = "html";
43 43 return html(stream, state);
44 44 }
45 45 return maybeBackup(stream, /<\/\s*style\s*>/,
46 46 cssMode.token(stream, state.localState));
47 47 }
48 48
49 49 return {
50 50 startState: function() {
51 51 var state = htmlMode.startState();
52 52 return {token: html, localState: null, mode: "html", htmlState: state};
53 53 },
54 54
55 55 copyState: function(state) {
56 56 if (state.localState)
57 57 var local = CodeMirror.copyState(state.token == css ? cssMode : jsMode, state.localState);
58 58 return {token: state.token, localState: local, mode: state.mode,
59 59 htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
60 60 },
61 61
62 62 token: function(stream, state) {
63 63 return state.token(stream, state);
64 64 },
65 65
66 66 indent: function(state, textAfter) {
67 67 if (state.token == html || /^\s*<\//.test(textAfter))
68 68 return htmlMode.indent(state.htmlState, textAfter);
69 69 else if (state.token == javascript)
70 70 return jsMode.indent(state.localState, textAfter);
71 71 else
72 72 return cssMode.indent(state.localState, textAfter);
73 73 },
74 74
75 75 compareStates: function(a, b) {
76 if (a.mode != b.mode) return false;
77 if (a.localState) return CodeMirror.Pass;
76 78 return htmlMode.compareStates(a.htmlState, b.htmlState);
77 79 },
78 80
79 81 electricChars: "/{}:"
80 82 }
81 });
83 }, "xml", "javascript", "css");
82 84
83 85 CodeMirror.defineMIME("text/html", "htmlmixed");
1 NO CONTENT: modified file chmod 100755 => 100644
1 NO CONTENT: modified file chmod 100755 => 100644
@@ -1,360 +1,361
1 1 CodeMirror.defineMode("javascript", function(config, parserConfig) {
2 2 var indentUnit = config.indentUnit;
3 3 var jsonMode = parserConfig.json;
4 4
5 5 // Tokenizer
6 6
7 7 var keywords = function(){
8 8 function kw(type) {return {type: type, style: "keyword"};}
9 9 var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
10 10 var operator = kw("operator"), atom = {type: "atom", style: "atom"};
11 11 return {
12 12 "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
13 13 "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
14 14 "var": kw("var"), "const": kw("var"), "let": kw("var"),
15 15 "function": kw("function"), "catch": kw("catch"),
16 16 "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
17 17 "in": operator, "typeof": operator, "instanceof": operator,
18 18 "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
19 19 };
20 20 }();
21 21
22 22 var isOperatorChar = /[+\-*&%=<>!?|]/;
23 23
24 24 function chain(stream, state, f) {
25 25 state.tokenize = f;
26 26 return f(stream, state);
27 27 }
28 28
29 29 function nextUntilUnescaped(stream, end) {
30 30 var escaped = false, next;
31 31 while ((next = stream.next()) != null) {
32 32 if (next == end && !escaped)
33 33 return false;
34 34 escaped = !escaped && next == "\\";
35 35 }
36 36 return escaped;
37 37 }
38 38
39 39 // Used as scratch variables to communicate multiple values without
40 40 // consing up tons of objects.
41 41 var type, content;
42 42 function ret(tp, style, cont) {
43 43 type = tp; content = cont;
44 44 return style;
45 45 }
46 46
47 47 function jsTokenBase(stream, state) {
48 48 var ch = stream.next();
49 49 if (ch == '"' || ch == "'")
50 50 return chain(stream, state, jsTokenString(ch));
51 51 else if (/[\[\]{}\(\),;\:\.]/.test(ch))
52 52 return ret(ch);
53 53 else if (ch == "0" && stream.eat(/x/i)) {
54 54 stream.eatWhile(/[\da-f]/i);
55 55 return ret("number", "number");
56 56 }
57 else if (/\d/.test(ch)) {
57 else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) {
58 58 stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
59 59 return ret("number", "number");
60 60 }
61 61 else if (ch == "/") {
62 62 if (stream.eat("*")) {
63 63 return chain(stream, state, jsTokenComment);
64 64 }
65 65 else if (stream.eat("/")) {
66 66 stream.skipToEnd();
67 67 return ret("comment", "comment");
68 68 }
69 69 else if (state.reAllowed) {
70 70 nextUntilUnescaped(stream, "/");
71 71 stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
72 72 return ret("regexp", "string-2");
73 73 }
74 74 else {
75 75 stream.eatWhile(isOperatorChar);
76 76 return ret("operator", null, stream.current());
77 77 }
78 78 }
79 79 else if (ch == "#") {
80 80 stream.skipToEnd();
81 81 return ret("error", "error");
82 82 }
83 83 else if (isOperatorChar.test(ch)) {
84 84 stream.eatWhile(isOperatorChar);
85 85 return ret("operator", null, stream.current());
86 86 }
87 87 else {
88 88 stream.eatWhile(/[\w\$_]/);
89 89 var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
90 90 return (known && state.kwAllowed) ? ret(known.type, known.style, word) :
91 91 ret("variable", "variable", word);
92 92 }
93 93 }
94 94
95 95 function jsTokenString(quote) {
96 96 return function(stream, state) {
97 97 if (!nextUntilUnescaped(stream, quote))
98 98 state.tokenize = jsTokenBase;
99 99 return ret("string", "string");
100 100 };
101 101 }
102 102
103 103 function jsTokenComment(stream, state) {
104 104 var maybeEnd = false, ch;
105 105 while (ch = stream.next()) {
106 106 if (ch == "/" && maybeEnd) {
107 107 state.tokenize = jsTokenBase;
108 108 break;
109 109 }
110 110 maybeEnd = (ch == "*");
111 111 }
112 112 return ret("comment", "comment");
113 113 }
114 114
115 115 // Parser
116 116
117 117 var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
118 118
119 119 function JSLexical(indented, column, type, align, prev, info) {
120 120 this.indented = indented;
121 121 this.column = column;
122 122 this.type = type;
123 123 this.prev = prev;
124 124 this.info = info;
125 125 if (align != null) this.align = align;
126 126 }
127 127
128 128 function inScope(state, varname) {
129 129 for (var v = state.localVars; v; v = v.next)
130 130 if (v.name == varname) return true;
131 131 }
132 132
133 133 function parseJS(state, style, type, content, stream) {
134 134 var cc = state.cc;
135 135 // Communicate our context to the combinators.
136 136 // (Less wasteful than consing up a hundred closures on every call.)
137 137 cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
138 138
139 139 if (!state.lexical.hasOwnProperty("align"))
140 140 state.lexical.align = true;
141 141
142 142 while(true) {
143 143 var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
144 144 if (combinator(type, content)) {
145 145 while(cc.length && cc[cc.length - 1].lex)
146 146 cc.pop()();
147 147 if (cx.marked) return cx.marked;
148 148 if (type == "variable" && inScope(state, content)) return "variable-2";
149 149 return style;
150 150 }
151 151 }
152 152 }
153 153
154 154 // Combinator utils
155 155
156 156 var cx = {state: null, column: null, marked: null, cc: null};
157 157 function pass() {
158 158 for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
159 159 }
160 160 function cont() {
161 161 pass.apply(null, arguments);
162 162 return true;
163 163 }
164 164 function register(varname) {
165 165 var state = cx.state;
166 166 if (state.context) {
167 167 cx.marked = "def";
168 168 for (var v = state.localVars; v; v = v.next)
169 169 if (v.name == varname) return;
170 170 state.localVars = {name: varname, next: state.localVars};
171 171 }
172 172 }
173 173
174 174 // Combinators
175 175
176 176 var defaultVars = {name: "this", next: {name: "arguments"}};
177 177 function pushcontext() {
178 178 if (!cx.state.context) cx.state.localVars = defaultVars;
179 179 cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
180 180 }
181 181 function popcontext() {
182 182 cx.state.localVars = cx.state.context.vars;
183 183 cx.state.context = cx.state.context.prev;
184 184 }
185 185 function pushlex(type, info) {
186 186 var result = function() {
187 187 var state = cx.state;
188 188 state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info)
189 189 };
190 190 result.lex = true;
191 191 return result;
192 192 }
193 193 function poplex() {
194 194 var state = cx.state;
195 195 if (state.lexical.prev) {
196 196 if (state.lexical.type == ")")
197 197 state.indented = state.lexical.indented;
198 198 state.lexical = state.lexical.prev;
199 199 }
200 200 }
201 201 poplex.lex = true;
202 202
203 203 function expect(wanted) {
204 204 return function expecting(type) {
205 205 if (type == wanted) return cont();
206 206 else if (wanted == ";") return pass();
207 207 else return cont(arguments.callee);
208 208 };
209 209 }
210 210
211 211 function statement(type) {
212 212 if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
213 213 if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
214 214 if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
215 215 if (type == "{") return cont(pushlex("}"), block, poplex);
216 216 if (type == ";") return cont();
217 217 if (type == "function") return cont(functiondef);
218 218 if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
219 219 poplex, statement, poplex);
220 220 if (type == "variable") return cont(pushlex("stat"), maybelabel);
221 221 if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
222 222 block, poplex, poplex);
223 223 if (type == "case") return cont(expression, expect(":"));
224 224 if (type == "default") return cont(expect(":"));
225 225 if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
226 226 statement, poplex, popcontext);
227 227 return pass(pushlex("stat"), expression, expect(";"), poplex);
228 228 }
229 229 function expression(type) {
230 230 if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
231 231 if (type == "function") return cont(functiondef);
232 232 if (type == "keyword c") return cont(maybeexpression);
233 233 if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
234 234 if (type == "operator") return cont(expression);
235 235 if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
236 236 if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
237 237 return cont();
238 238 }
239 239 function maybeexpression(type) {
240 240 if (type.match(/[;\}\)\],]/)) return pass();
241 241 return pass(expression);
242 242 }
243 243
244 244 function maybeoperator(type, value) {
245 245 if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
246 if (type == "operator") return cont(expression);
246 if (type == "operator" || type == ":") return cont(expression);
247 247 if (type == ";") return;
248 248 if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
249 249 if (type == ".") return cont(property, maybeoperator);
250 250 if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
251 251 }
252 252 function maybelabel(type) {
253 253 if (type == ":") return cont(poplex, statement);
254 254 return pass(maybeoperator, expect(";"), poplex);
255 255 }
256 256 function property(type) {
257 257 if (type == "variable") {cx.marked = "property"; return cont();}
258 258 }
259 259 function objprop(type) {
260 260 if (type == "variable") cx.marked = "property";
261 261 if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
262 262 }
263 263 function commasep(what, end) {
264 264 function proceed(type) {
265 265 if (type == ",") return cont(what, proceed);
266 266 if (type == end) return cont();
267 267 return cont(expect(end));
268 268 }
269 269 return function commaSeparated(type) {
270 270 if (type == end) return cont();
271 271 else return pass(what, proceed);
272 272 };
273 273 }
274 274 function block(type) {
275 275 if (type == "}") return cont();
276 276 return pass(statement, block);
277 277 }
278 278 function vardef1(type, value) {
279 279 if (type == "variable"){register(value); return cont(vardef2);}
280 280 return cont();
281 281 }
282 282 function vardef2(type, value) {
283 283 if (value == "=") return cont(expression, vardef2);
284 284 if (type == ",") return cont(vardef1);
285 285 }
286 286 function forspec1(type) {
287 287 if (type == "var") return cont(vardef1, forspec2);
288 288 if (type == ";") return pass(forspec2);
289 289 if (type == "variable") return cont(formaybein);
290 290 return pass(forspec2);
291 291 }
292 292 function formaybein(type, value) {
293 293 if (value == "in") return cont(expression);
294 294 return cont(maybeoperator, forspec2);
295 295 }
296 296 function forspec2(type, value) {
297 297 if (type == ";") return cont(forspec3);
298 298 if (value == "in") return cont(expression);
299 299 return cont(expression, expect(";"), forspec3);
300 300 }
301 301 function forspec3(type) {
302 302 if (type != ")") cont(expression);
303 303 }
304 304 function functiondef(type, value) {
305 305 if (type == "variable") {register(value); return cont(functiondef);}
306 306 if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
307 307 }
308 308 function funarg(type, value) {
309 309 if (type == "variable") {register(value); return cont();}
310 310 }
311 311
312 312 // Interface
313 313
314 314 return {
315 315 startState: function(basecolumn) {
316 316 return {
317 317 tokenize: jsTokenBase,
318 318 reAllowed: true,
319 319 kwAllowed: true,
320 320 cc: [],
321 321 lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
322 localVars: null,
323 context: null,
322 localVars: parserConfig.localVars,
323 context: parserConfig.localVars && {vars: parserConfig.localVars},
324 324 indented: 0
325 325 };
326 326 },
327 327
328 328 token: function(stream, state) {
329 329 if (stream.sol()) {
330 330 if (!state.lexical.hasOwnProperty("align"))
331 331 state.lexical.align = false;
332 332 state.indented = stream.indentation();
333 333 }
334 334 if (stream.eatSpace()) return null;
335 335 var style = state.tokenize(stream, state);
336 336 if (type == "comment") return style;
337 state.reAllowed = type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/);
337 state.reAllowed = !!(type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/));
338 338 state.kwAllowed = type != '.';
339 339 return parseJS(state, style, type, content, stream);
340 340 },
341 341
342 342 indent: function(state, textAfter) {
343 343 if (state.tokenize != jsTokenBase) return 0;
344 var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical,
345 type = lexical.type, closing = firstChar == type;
344 var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
345 if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
346 var type = lexical.type, closing = firstChar == type;
346 347 if (type == "vardef") return lexical.indented + 4;
347 348 else if (type == "form" && firstChar == "{") return lexical.indented;
348 349 else if (type == "stat" || type == "form") return lexical.indented + indentUnit;
349 350 else if (lexical.info == "switch" && !closing)
350 351 return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
351 352 else if (lexical.align) return lexical.column + (closing ? 0 : 1);
352 353 else return lexical.indented + (closing ? 0 : indentUnit);
353 354 },
354 355
355 356 electricChars: ":{}"
356 357 };
357 358 });
358 359
359 360 CodeMirror.defineMIME("text/javascript", "javascript");
360 361 CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
1 NO CONTENT: modified file chmod 100755 => 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file chmod 100755 => 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file chmod 100755 => 100644
1 NO CONTENT: modified file chmod 100755 => 100644
1 NO CONTENT: modified file chmod 100755 => 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file chmod 100755 => 100644
1 NO CONTENT: modified file chmod 100755 => 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file chmod 100755 => 100644
1 NO CONTENT: modified file chmod 100755 => 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file chmod 100755 => 100644
1 NO CONTENT: modified file chmod 100755 => 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file chmod 100755 => 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file chmod 100755 => 100644
1 NO CONTENT: modified file chmod 100755 => 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file chmod 100755 => 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file chmod 100755 => 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now