AmberRack 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +74 -0
- data/.rvmrc +4 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +62 -0
- data/README.md +17 -0
- data/Rakefile +24 -0
- data/amber-rack.gemspec +33 -0
- data/app/amber_app.rb +35 -0
- data/app/javascripts/Benchfib.deploy.js +159 -0
- data/app/javascripts/Benchfib.js +159 -0
- data/app/javascripts/Canvas.deploy.js +1464 -0
- data/app/javascripts/Canvas.js +1464 -0
- data/app/javascripts/Compiler.deploy.js +1587 -0
- data/app/javascripts/Compiler.js +1587 -0
- data/app/javascripts/Examples.deploy.js +800 -0
- data/app/javascripts/Examples.js +800 -0
- data/app/javascripts/IDE.deploy.js +3457 -0
- data/app/javascripts/IDE.js +3457 -0
- data/app/javascripts/IDE.js.bak +3420 -0
- data/app/javascripts/JQuery.deploy.js +898 -0
- data/app/javascripts/JQuery.js +898 -0
- data/app/javascripts/Kernel.deploy.js +6761 -0
- data/app/javascripts/Kernel.js +6761 -0
- data/app/javascripts/Parser.deploy.js +1655 -0
- data/app/javascripts/Parser.js +1655 -0
- data/app/javascripts/Raphael-Core.js +0 -0
- data/app/javascripts/Raphael-Graph.js +0 -0
- data/app/javascripts/SUnit.deploy.js +1059 -0
- data/app/javascripts/SUnit.js +1059 -0
- data/app/javascripts/application.js +2 -0
- data/app/javascripts/boot.js +466 -0
- data/app/javascripts/init.js +2 -0
- data/app/javascripts/jquery-1.4.4.min.js +167 -0
- data/app/javascripts/jquery-ui-1.8.9.custom.min.js +781 -0
- data/app/javascripts/jquery.textarea.js +267 -0
- data/app/javascripts/jtalk.js +78 -0
- data/app/javascripts/lib/CodeMirror/LICENSE +19 -0
- data/app/javascripts/lib/CodeMirror/README.md +6 -0
- data/app/javascripts/lib/CodeMirror/compress.html +92 -0
- data/app/javascripts/lib/CodeMirror/css/baboon.png +0 -0
- data/app/javascripts/lib/CodeMirror/css/baboon_vector.svg +153 -0
- data/app/javascripts/lib/CodeMirror/css/docs.css +154 -0
- data/app/javascripts/lib/CodeMirror/demo/activeline.html +72 -0
- data/app/javascripts/lib/CodeMirror/demo/changemode.html +51 -0
- data/app/javascripts/lib/CodeMirror/demo/complete.html +79 -0
- data/app/javascripts/lib/CodeMirror/demo/complete.js +151 -0
- data/app/javascripts/lib/CodeMirror/demo/fullscreen.html +158 -0
- data/app/javascripts/lib/CodeMirror/demo/marker.html +53 -0
- data/app/javascripts/lib/CodeMirror/demo/mustache.html +57 -0
- data/app/javascripts/lib/CodeMirror/demo/preview.html +75 -0
- data/app/javascripts/lib/CodeMirror/demo/resize.html +44 -0
- data/app/javascripts/lib/CodeMirror/demo/runmode.html +50 -0
- data/app/javascripts/lib/CodeMirror/demo/search.html +106 -0
- data/app/javascripts/lib/CodeMirror/demo/theme.html +53 -0
- data/app/javascripts/lib/CodeMirror/index.html +239 -0
- data/app/javascripts/lib/CodeMirror/internals.html +389 -0
- data/app/javascripts/lib/CodeMirror/lib/codemirror.css +67 -0
- data/app/javascripts/lib/CodeMirror/lib/codemirror.js +2144 -0
- data/app/javascripts/lib/CodeMirror/lib/overlay.js +51 -0
- data/app/javascripts/lib/CodeMirror/lib/runmode.js +27 -0
- data/app/javascripts/lib/CodeMirror/manual.html +854 -0
- data/app/javascripts/lib/CodeMirror/mode/clike/clike.js +247 -0
- data/app/javascripts/lib/CodeMirror/mode/clike/index.html +102 -0
- data/app/javascripts/lib/CodeMirror/mode/css/css.js +124 -0
- data/app/javascripts/lib/CodeMirror/mode/css/index.html +56 -0
- data/app/javascripts/lib/CodeMirror/mode/diff/diff.css +3 -0
- data/app/javascripts/lib/CodeMirror/mode/diff/diff.js +13 -0
- data/app/javascripts/lib/CodeMirror/mode/diff/index.html +99 -0
- data/app/javascripts/lib/CodeMirror/mode/haskell/haskell.js +242 -0
- data/app/javascripts/lib/CodeMirror/mode/haskell/index.html +60 -0
- data/app/javascripts/lib/CodeMirror/mode/htmlmixed/htmlmixed.js +79 -0
- data/app/javascripts/lib/CodeMirror/mode/htmlmixed/index.html +52 -0
- data/app/javascripts/lib/CodeMirror/mode/javascript/index.html +78 -0
- data/app/javascripts/lib/CodeMirror/mode/javascript/javascript.js +348 -0
- data/app/javascripts/lib/CodeMirror/mode/lua/index.html +72 -0
- data/app/javascripts/lib/CodeMirror/mode/lua/lua.js +138 -0
- data/app/javascripts/lib/CodeMirror/mode/php/index.html +49 -0
- data/app/javascripts/lib/CodeMirror/mode/php/php.js +110 -0
- data/app/javascripts/lib/CodeMirror/mode/plsql/index.html +63 -0
- data/app/javascripts/lib/CodeMirror/mode/plsql/plsql.js +217 -0
- data/app/javascripts/lib/CodeMirror/mode/python/LICENSE.txt +21 -0
- data/app/javascripts/lib/CodeMirror/mode/python/index.html +123 -0
- data/app/javascripts/lib/CodeMirror/mode/python/python.js +321 -0
- data/app/javascripts/lib/CodeMirror/mode/rst/index.html +526 -0
- data/app/javascripts/lib/CodeMirror/mode/rst/rst.css +75 -0
- data/app/javascripts/lib/CodeMirror/mode/rst/rst.js +333 -0
- data/app/javascripts/lib/CodeMirror/mode/scheme/index.html +65 -0
- data/app/javascripts/lib/CodeMirror/mode/scheme/scheme.js +181 -0
- data/app/javascripts/lib/CodeMirror/mode/smalltalk/index.html +56 -0
- data/app/javascripts/lib/CodeMirror/mode/smalltalk/smalltalk.js +134 -0
- data/app/javascripts/lib/CodeMirror/mode/sparql/index.html +41 -0
- data/app/javascripts/lib/CodeMirror/mode/sparql/sparql.js +143 -0
- data/app/javascripts/lib/CodeMirror/mode/stex/index.html +96 -0
- data/app/javascripts/lib/CodeMirror/mode/stex/stex.js +167 -0
- data/app/javascripts/lib/CodeMirror/mode/xml/index.html +42 -0
- data/app/javascripts/lib/CodeMirror/mode/xml/xml.js +231 -0
- data/app/javascripts/lib/CodeMirror/mode/yaml/index.html +68 -0
- data/app/javascripts/lib/CodeMirror/mode/yaml/yaml.js +95 -0
- data/app/javascripts/lib/CodeMirror/oldrelease.html +178 -0
- data/app/javascripts/lib/CodeMirror/test/index.html +29 -0
- data/app/javascripts/lib/CodeMirror/test/test.js +249 -0
- data/app/javascripts/lib/CodeMirror/theme/default.css +18 -0
- data/app/javascripts/lib/CodeMirror/theme/elegant.css +9 -0
- data/app/javascripts/lib/CodeMirror/theme/jtalk.css +21 -0
- data/app/javascripts/lib/CodeMirror/theme/neat.css +8 -0
- data/app/javascripts/lib/CodeMirror/theme/night.css +20 -0
- data/app/javascripts/lib/jQuery/jquery-1.4.4.min.js +167 -0
- data/app/javascripts/lib/jQuery/jquery-ui-1.8.9.custom.min.js +781 -0
- data/app/javascripts/lib/jQuery/jquery.textarea.js +267 -0
- data/app/smalltalk/Canvas.st +481 -0
- data/app/smalltalk/IDE.st +1752 -0
- data/config.ru +2 -0
- data/lib/amber-rack.rb +4 -0
- data/lib/amber-rack/version.rb +3 -0
- data/public/CodeMirror/LICENSE +19 -0
- data/public/CodeMirror/README.md +6 -0
- data/public/CodeMirror/compress.html +92 -0
- data/public/CodeMirror/css/baboon.png +0 -0
- data/public/CodeMirror/css/baboon_vector.svg +153 -0
- data/public/CodeMirror/css/docs.css +154 -0
- data/public/CodeMirror/demo/activeline.html +72 -0
- data/public/CodeMirror/demo/changemode.html +51 -0
- data/public/CodeMirror/demo/complete.html +79 -0
- data/public/CodeMirror/demo/complete.js +151 -0
- data/public/CodeMirror/demo/fullscreen.html +158 -0
- data/public/CodeMirror/demo/marker.html +53 -0
- data/public/CodeMirror/demo/mustache.html +57 -0
- data/public/CodeMirror/demo/preview.html +75 -0
- data/public/CodeMirror/demo/resize.html +44 -0
- data/public/CodeMirror/demo/runmode.html +50 -0
- data/public/CodeMirror/demo/search.html +106 -0
- data/public/CodeMirror/demo/theme.html +53 -0
- data/public/CodeMirror/index.html +239 -0
- data/public/CodeMirror/internals.html +389 -0
- data/public/CodeMirror/lib/codemirror.css +67 -0
- data/public/CodeMirror/lib/codemirror.js +2144 -0
- data/public/CodeMirror/lib/overlay.js +51 -0
- data/public/CodeMirror/lib/runmode.js +27 -0
- data/public/CodeMirror/manual.html +854 -0
- data/public/CodeMirror/mode/clike/clike.js +247 -0
- data/public/CodeMirror/mode/clike/index.html +102 -0
- data/public/CodeMirror/mode/css/css.js +124 -0
- data/public/CodeMirror/mode/css/index.html +56 -0
- data/public/CodeMirror/mode/diff/diff.css +3 -0
- data/public/CodeMirror/mode/diff/diff.js +13 -0
- data/public/CodeMirror/mode/diff/index.html +99 -0
- data/public/CodeMirror/mode/haskell/haskell.js +242 -0
- data/public/CodeMirror/mode/haskell/index.html +60 -0
- data/public/CodeMirror/mode/htmlmixed/htmlmixed.js +79 -0
- data/public/CodeMirror/mode/htmlmixed/index.html +52 -0
- data/public/CodeMirror/mode/javascript/index.html +78 -0
- data/public/CodeMirror/mode/javascript/javascript.js +348 -0
- data/public/CodeMirror/mode/lua/index.html +72 -0
- data/public/CodeMirror/mode/lua/lua.js +138 -0
- data/public/CodeMirror/mode/php/index.html +49 -0
- data/public/CodeMirror/mode/php/php.js +110 -0
- data/public/CodeMirror/mode/plsql/index.html +63 -0
- data/public/CodeMirror/mode/plsql/plsql.js +217 -0
- data/public/CodeMirror/mode/python/LICENSE.txt +21 -0
- data/public/CodeMirror/mode/python/index.html +123 -0
- data/public/CodeMirror/mode/python/python.js +321 -0
- data/public/CodeMirror/mode/rst/index.html +526 -0
- data/public/CodeMirror/mode/rst/rst.css +75 -0
- data/public/CodeMirror/mode/rst/rst.js +333 -0
- data/public/CodeMirror/mode/scheme/index.html +65 -0
- data/public/CodeMirror/mode/scheme/scheme.js +181 -0
- data/public/CodeMirror/mode/smalltalk/index.html +56 -0
- data/public/CodeMirror/mode/smalltalk/smalltalk.js +134 -0
- data/public/CodeMirror/mode/sparql/index.html +41 -0
- data/public/CodeMirror/mode/sparql/sparql.js +143 -0
- data/public/CodeMirror/mode/stex/index.html +96 -0
- data/public/CodeMirror/mode/stex/stex.js +167 -0
- data/public/CodeMirror/mode/xml/index.html +42 -0
- data/public/CodeMirror/mode/xml/xml.js +231 -0
- data/public/CodeMirror/mode/yaml/index.html +68 -0
- data/public/CodeMirror/mode/yaml/yaml.js +95 -0
- data/public/CodeMirror/oldrelease.html +178 -0
- data/public/CodeMirror/test/index.html +29 -0
- data/public/CodeMirror/test/test.js +249 -0
- data/public/CodeMirror/theme/default.css +18 -0
- data/public/CodeMirror/theme/elegant.css +9 -0
- data/public/CodeMirror/theme/jtalk.css +21 -0
- data/public/CodeMirror/theme/neat.css +8 -0
- data/public/CodeMirror/theme/night.css +20 -0
- data/public/css/jtalk.css +362 -0
- data/public/css/style.css +456 -0
- data/public/css/sunit.css +66 -0
- data/public/ide/fork_me.png +0 -0
- data/public/ide/screen2.png +0 -0
- data/public/ide/style.css +456 -0
- data/public/ide/syntax.css +61 -0
- data/public/ide/text_header.png +0 -0
- data/public/ide/title_container1.png +0 -0
- data/public/images/background_box.png +0 -0
- data/public/images/background_header.png +0 -0
- data/public/images/balloon_header.png +0 -0
- data/views/index.haml +66 -0
- metadata +341 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>CodeMirror: Internals</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="css/docs.css"/>
|
|
7
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
|
8
|
+
<style>dl dl {margin: 0;}</style>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
|
|
12
|
+
<h1><span class="logo-braces">{ }</span> <a href="http://codemirror.net/">CodeMirror</a></h1>
|
|
13
|
+
|
|
14
|
+
<pre class="grey">
|
|
15
|
+
<img src="css/baboon.png" class="logo" alt="logo"/>/* (Re-) Implementing A Syntax-
|
|
16
|
+
Highlighting Editor in JavaScript */
|
|
17
|
+
</pre>
|
|
18
|
+
|
|
19
|
+
<div class="clear"><div class="leftbig blk">
|
|
20
|
+
|
|
21
|
+
<p style="font-size: 85%" id="intro">
|
|
22
|
+
<strong>Topic:</strong> JavaScript, code editor implementation<br>
|
|
23
|
+
<strong>Author:</strong> Marijn Haverbeke<br>
|
|
24
|
+
<strong>Date:</strong> March 2nd 2011
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
<p>This is a followup to
|
|
28
|
+
my <a href="http://codemirror.net/story.html">Brutal Odyssey to the
|
|
29
|
+
Dark Side of the DOM Tree</a> story. That one describes the
|
|
30
|
+
mind-bending process of implementing (what would become) CodeMirror 1.
|
|
31
|
+
This one describes the internals of CodeMirror 2, a complete rewrite
|
|
32
|
+
and rethink of the old code base. I wanted to give this piece another
|
|
33
|
+
Hunter Thompson copycat subtitle, but somehow that would be out of
|
|
34
|
+
place—the process this time around was one of straightforward
|
|
35
|
+
engineering, requiring no serious mind-bending whatsoever.</p>
|
|
36
|
+
|
|
37
|
+
<p>So, what is wrong with CodeMirror 1? I'd estimate, by mailing list
|
|
38
|
+
activity and general search-engine presence, that it has been
|
|
39
|
+
integrated into about a thousand systems by now. The most prominent
|
|
40
|
+
one, since a few weeks,
|
|
41
|
+
being <a href="http://googlecode.blogspot.com/2011/01/make-quick-fixes-quicker-on-google.html">Google
|
|
42
|
+
code's project hosting</a>. It works, and it's being used widely.</a>
|
|
43
|
+
|
|
44
|
+
<p>Still, I did not start replacing it because I was bored. CodeMirror
|
|
45
|
+
1 was heavily reliant on <code>designMode</code>
|
|
46
|
+
or <code>contentEditable</code> (depending on the browser). Neither of
|
|
47
|
+
these are well specified (HTML5 tries
|
|
48
|
+
to <a href="http://www.w3.org/TR/html5/editing.html#contenteditable">specify</a>
|
|
49
|
+
their basics), and, more importantly, they tend to be one of the more
|
|
50
|
+
obscure and buggy areas of browser functionality—CodeMirror, by using
|
|
51
|
+
this functionality in a non-typical way, was constantly running up
|
|
52
|
+
against browser bugs. WebKit wouldn't show an empty line at the end of
|
|
53
|
+
the document, and in some releases would suddenly get unbearably slow.
|
|
54
|
+
Firefox would show the cursor in the wrong place. Internet Explorer
|
|
55
|
+
would insist on linkifying everything that looked like a URL or email
|
|
56
|
+
address, a behaviour that can't be turned off. Some bugs I managed to
|
|
57
|
+
work around (which was often a frustrating, painful process), others,
|
|
58
|
+
such as the Firefox cursor placement, I gave up on, and had to tell
|
|
59
|
+
user after user that they were known problems, but not something I
|
|
60
|
+
could help.</p>
|
|
61
|
+
|
|
62
|
+
<p>Also, there is the fact that <code>designMode</code> (which seemed
|
|
63
|
+
to be less buggy than <code>contentEditable</code> in Webkit and
|
|
64
|
+
Firefox, and was thus used by CodeMirror 1 in those browsers) requires
|
|
65
|
+
a frame. Frames are another tricky area. It takes some effort to
|
|
66
|
+
prevent getting tripped up by domain restrictions, they don't
|
|
67
|
+
initialize synchronously, behave strangely in response to the back
|
|
68
|
+
button, and, on several browsers, can't be moved around the DOM
|
|
69
|
+
without having them re-initialize. They did provide a very nice way to
|
|
70
|
+
namespace the library, though—CodeMirror 1 could freely pollute the
|
|
71
|
+
namespace inside the frame.</p>
|
|
72
|
+
|
|
73
|
+
<p>Finally, working with an editable document means working with
|
|
74
|
+
selection in arbitrary DOM structures. Internet Explorer (8 and
|
|
75
|
+
before) has an utterly different (and awkward) selection API than all
|
|
76
|
+
of the other browsers, and even among the different implementations of
|
|
77
|
+
<code>document.selection</code>, details about how exactly a selection
|
|
78
|
+
is represented vary quite a bit. Add to that the fact that Opera's
|
|
79
|
+
selection support tended to be very buggy until recently, and you can
|
|
80
|
+
imagine why CodeMirror 1 contains 700 lines of selection-handling
|
|
81
|
+
code.</p>
|
|
82
|
+
|
|
83
|
+
<p>And that brings us to the main issue with the CodeMirror 1
|
|
84
|
+
code base: The proportion of browser-bug-workarounds to real
|
|
85
|
+
application code was getting dangerously high. By building on top of a
|
|
86
|
+
few dodgy features, I put the system in a vulnerable position—any
|
|
87
|
+
incompatibility and bugginess in these features, I had to paper over
|
|
88
|
+
with my own code. Not only did I have to do some serious stunt-work to
|
|
89
|
+
get it to work on older browsers (as detailed in the
|
|
90
|
+
previous <a href="http://codemirror.net/story.html">story</a>), things
|
|
91
|
+
also kept breaking in newly released versions, requiring me to come up
|
|
92
|
+
with <em>new</em> scary hacks in order to keep up. This was starting
|
|
93
|
+
to lose its appeal.</p>
|
|
94
|
+
|
|
95
|
+
<h2 id="approach">General Approach</h2>
|
|
96
|
+
|
|
97
|
+
<p>What CodeMirror 2 does is try to sidestep most of the hairy hacks
|
|
98
|
+
that came up in version 1. I owe a lot to the
|
|
99
|
+
<a href="http://ace.ajax.org">ACE</a> editor for inspiration on how to
|
|
100
|
+
approach this.</p>
|
|
101
|
+
|
|
102
|
+
<p>I absolutely did not want to be completely reliant on key events to
|
|
103
|
+
generate my input. Every JavaScript programmer knows that key event
|
|
104
|
+
information is horrible and incomplete. Some people (most awesomely
|
|
105
|
+
Mihai Bazon with <a href="http://ymacs.org">Ymacs</a>) have been able
|
|
106
|
+
to build more or less functioning editors by directly reading key
|
|
107
|
+
events, but it takes a lot of work (the kind of never-ending, fragile
|
|
108
|
+
work I described earlier), and will never be able to properly support
|
|
109
|
+
things like multi-keystoke international character input.</p>
|
|
110
|
+
|
|
111
|
+
<p>So what I do is focus a hidden textarea, and let the browser
|
|
112
|
+
believe that the user is typing into that. What we show to the user is
|
|
113
|
+
a DOM structure we built to represent his document. If this is updated
|
|
114
|
+
quickly enough, and shows some kind of believable cursor, it feels
|
|
115
|
+
like a real text-input control.</p>
|
|
116
|
+
|
|
117
|
+
<p>Another big win is that this DOM representation does not have to
|
|
118
|
+
span the whole document. Some CodeMirror 1 users insisted that they
|
|
119
|
+
needed to put a 30 thousand line XML document into CodeMirror. Putting
|
|
120
|
+
all that into the DOM takes a while, especially since, for some
|
|
121
|
+
reason, an editable DOM tree is slower than a normal one on most
|
|
122
|
+
browsers. If we have full control over what we show, we must only
|
|
123
|
+
ensure that the visible part of the document has been added, and can
|
|
124
|
+
do the rest only when needed. (Fortunately, the <code>onscroll</code>
|
|
125
|
+
event works almost the same on all browsers, and lends itself well to
|
|
126
|
+
displaying things only as they are scrolled into view.)</p>
|
|
127
|
+
|
|
128
|
+
<h2 id="input">Input</h2>
|
|
129
|
+
|
|
130
|
+
<p>ACE uses its hidden textarea only as a text input shim, and does
|
|
131
|
+
all cursor movement and things like text deletion itself by directly
|
|
132
|
+
handling key events. CodeMirror's way is to let the browser do its
|
|
133
|
+
thing as much as possible, and not, for example, define its own set of
|
|
134
|
+
key bindings. One way to do this would have been to have the whole
|
|
135
|
+
document inside the hidden textarea, and after each key event update
|
|
136
|
+
the display DOM to reflect what's in that textarea.</p>
|
|
137
|
+
|
|
138
|
+
<p>That'd be simple, but it is not realistic. For even medium-sized
|
|
139
|
+
document the editor would be constantly munging huge strings, and get
|
|
140
|
+
terribly slow. What CodeMirror 2 does is put the current selection,
|
|
141
|
+
along with an extra line on the top and on the bottom, into the
|
|
142
|
+
textarea.</p>
|
|
143
|
+
|
|
144
|
+
<p>This means that the arrow keys (and their ctrl-variations), home,
|
|
145
|
+
end, etcetera, do not have to be handled specially. We just read the
|
|
146
|
+
cursor position in the textarea, and update our cursor to match it.
|
|
147
|
+
Also, copy and paste work pretty much for free, and people get their
|
|
148
|
+
native key bindings, without any special work on my part. For example,
|
|
149
|
+
I have emacs key bindings configured for Chrome and Firefox. There is
|
|
150
|
+
no way for a script to detect this.</p>
|
|
151
|
+
|
|
152
|
+
<p>Of course, since only a small part of the document sits in the
|
|
153
|
+
textarea, keys like page up and ctrl-end won't do the right thing.
|
|
154
|
+
CodeMirror is catching those events and handling them itself.</p>
|
|
155
|
+
|
|
156
|
+
<h2 id="selection">Selection</h2>
|
|
157
|
+
|
|
158
|
+
<p>Getting and setting the selection range of a textarea in modern
|
|
159
|
+
browsers is trivial—you just use the <code>selectionStart</code>
|
|
160
|
+
and <code>selectionEnd</code> properties. On IE you have to do some
|
|
161
|
+
insane stuff with temporary ranges and compensating for the fact that
|
|
162
|
+
moving the selection by a 'character' will treat \r\n as a single
|
|
163
|
+
character, but even there it is possible to build functions that
|
|
164
|
+
reliably set and get the selection range.</p>
|
|
165
|
+
|
|
166
|
+
<p>But consider this typical case: When I'm somewhere in my document,
|
|
167
|
+
press shift, and press the up arrow, something gets selected. Then, if
|
|
168
|
+
I, still holding shift, press the up arrow again, the top of my
|
|
169
|
+
selection is adjusted. The selection remembers where its <em>head</em>
|
|
170
|
+
and its <em>anchor</em> are, and moves the head when we shift-move.
|
|
171
|
+
This is a generally accepted property of selections, and done right by
|
|
172
|
+
every editing component built in the past twenty years.</p>
|
|
173
|
+
|
|
174
|
+
<p>But not something that the browser selection APIs expose.</p>
|
|
175
|
+
|
|
176
|
+
<p>Great. So when someone creates an 'upside-down' selection, the next
|
|
177
|
+
time CodeMirror has to update the textarea, it'll re-create the
|
|
178
|
+
selection as an 'upside-up' selection, with the anchor at the top, and
|
|
179
|
+
the next cursor motion will behave in an unexpected way—our second
|
|
180
|
+
up-arrow press in the example above will not do anything, since it is
|
|
181
|
+
interpreted in exactly the same way as the first.</p>
|
|
182
|
+
|
|
183
|
+
<p>No problem. We'll just, ehm, detect that the selection is
|
|
184
|
+
upside-down (you can tell by the way it was created), and then, when
|
|
185
|
+
an upside-down selection is present, and a cursor-moving key is
|
|
186
|
+
pressed in combination with shift, we quickly collapse the selection
|
|
187
|
+
in the textarea to its start, allow the key to take effect, and then
|
|
188
|
+
combine its new head with its old anchor to get the <em>real</em>
|
|
189
|
+
selection.</p>
|
|
190
|
+
|
|
191
|
+
<p>In short, scary hacks could not be avoided entirely in CodeMirror
|
|
192
|
+
2.</p>
|
|
193
|
+
|
|
194
|
+
<p>And, the observant reader might ask, how do you even know that a
|
|
195
|
+
key combo is a cursor-moving combo, if you claim you support any
|
|
196
|
+
native key bindings? Well, we don't, but we can learn. The editor
|
|
197
|
+
keeps a set known cursor-movement combos (initialized to the
|
|
198
|
+
predictable defaults), and updates this set when it observes that
|
|
199
|
+
pressing a certain key had (only) the effect of moving the cursor.
|
|
200
|
+
This, of course, doesn't work if the first time the key is used was
|
|
201
|
+
for extending an inverted selection, but it works most of the
|
|
202
|
+
time.</p>
|
|
203
|
+
|
|
204
|
+
<h2 id="update">Intelligent Updating</h2>
|
|
205
|
+
|
|
206
|
+
<p>One thing that always comes up when you have a complicated internal
|
|
207
|
+
state that's reflected in some user-visible external representation
|
|
208
|
+
(in this case, the displayed code and the textarea's content) is
|
|
209
|
+
keeping the two in sync. The naive way is to just update the display
|
|
210
|
+
every time you change your state, but this is not only error prone
|
|
211
|
+
(you'll forget), it also easily leads to duplicate work on big,
|
|
212
|
+
composite operations. Then you start passing around flags indicating
|
|
213
|
+
whether the display should be updated in an attempt to be efficient
|
|
214
|
+
again and, well, at that point you might as well give up completely.</p>
|
|
215
|
+
|
|
216
|
+
<p>I did go down that road, but then switched to a much simpler model:
|
|
217
|
+
simply keep track of all the things that have been changed during an
|
|
218
|
+
action, and then, only at the end, use this information to update the
|
|
219
|
+
user-visible display.</p>
|
|
220
|
+
|
|
221
|
+
<p>CodeMirror uses a concept of <em>operations</em>, which start by
|
|
222
|
+
calling a specific set-up function that clears the state and end by
|
|
223
|
+
calling another function that reads this state and does the required
|
|
224
|
+
updating. Most event handlers, and all the user-visible methods that
|
|
225
|
+
change state are wrapped like this. There's a method
|
|
226
|
+
called <code>operation</code> that accepts a function, and returns
|
|
227
|
+
another function that wraps the given function as an operation.</p>
|
|
228
|
+
|
|
229
|
+
<p>It's trivial to extend this (as CodeMirror does) to detect nesting,
|
|
230
|
+
and, when an operation is started inside an operation, simply
|
|
231
|
+
increment the nesting count, and only do the updating when this count
|
|
232
|
+
reaches zero again.</p>
|
|
233
|
+
|
|
234
|
+
<p>If we have a set of changed ranges and know the currently shown
|
|
235
|
+
range, we can (with some awkward code to deal with the fact that
|
|
236
|
+
changes can add and remove lines, so we're dealing with a changing
|
|
237
|
+
coordinate system) construct a map of the ranges that were left
|
|
238
|
+
intact. We can then compare this map with the part of the document
|
|
239
|
+
that's currently visible (based on scroll offset and editor height) to
|
|
240
|
+
determine whether something needs to be updated.</p>
|
|
241
|
+
|
|
242
|
+
<p>CodeMirror uses two update algorithms—a full refresh, where it just
|
|
243
|
+
discards the whole part of the DOM that contains the edited text and
|
|
244
|
+
rebuilds it, and a patch algorithm, where it uses the information
|
|
245
|
+
about changed and intact ranges to update only the out-of-date parts
|
|
246
|
+
of the DOM. When more than 30 percent (which is the current heuristic,
|
|
247
|
+
might change) of the lines need to be updated, the full refresh is
|
|
248
|
+
chosen (since it's faster to do than painstakingly finding and
|
|
249
|
+
updating all the changed lines), in the other case it does the
|
|
250
|
+
patching (so that, if you scroll a line or select another character,
|
|
251
|
+
the whole screen doesn't have to be re-rendered).</p>
|
|
252
|
+
|
|
253
|
+
<p>All updating uses <code>innerHTML</code> rather than direct DOM
|
|
254
|
+
manipulation, since that still seems to be by far the fastest way to
|
|
255
|
+
build documents. There's a per-line function that combines the
|
|
256
|
+
highlighting, <a href="manual.html#markText">marking</a>, and
|
|
257
|
+
selection info for that line into a snippet of HTML. The patch updater
|
|
258
|
+
uses this to reset individual lines, the refresh updater builds an
|
|
259
|
+
HTML chunk for the whole visible document at once, and then uses a
|
|
260
|
+
single <code>innerHTML</code> update to do the refresh.</p>
|
|
261
|
+
|
|
262
|
+
<h2 id="parse">Parsers can be Simple</h2>
|
|
263
|
+
|
|
264
|
+
<p>When I wrote CodeMirror 1, I
|
|
265
|
+
thought <a href="http://codemirror.net/story.html#parser">interruptable
|
|
266
|
+
parsers</a> were a hugely scary and complicated thing, and I used a
|
|
267
|
+
bunch of heavyweight abstractions to keep this supposed complexity
|
|
268
|
+
under control: parsers
|
|
269
|
+
were <a href="http://bob.pythonmac.org/archives/2005/07/06/iteration-in-javascript/">iterators</a>
|
|
270
|
+
that consumed input from another iterator, and used funny
|
|
271
|
+
closure-resetting tricks to copy and resume themselves.</p>
|
|
272
|
+
|
|
273
|
+
<p>This made for a rather nice system, in that parsers formed strictly
|
|
274
|
+
separate modules, and could be composed in predictable ways.
|
|
275
|
+
Unfortunately, it was quite slow (stacking three or four iterators on
|
|
276
|
+
top of each other), and extremely intimidating to people not used to a
|
|
277
|
+
functional programming style.</p>
|
|
278
|
+
|
|
279
|
+
<p>With a few small changes, however, we can keep all those
|
|
280
|
+
advantages, but simplify the API and make the whole thing less
|
|
281
|
+
indirect and inefficient. CodeMirror
|
|
282
|
+
2's <a href="manual.html#modeapi">mode API</a> uses explicit state
|
|
283
|
+
objects, and makes the parser/tokenizer a function that simply takes a
|
|
284
|
+
state and a character stream abstraction, advances the stream one
|
|
285
|
+
token, and returns the way the token should be styled. This state may
|
|
286
|
+
be copied, optionally in a mode-defined way, in order to be able to
|
|
287
|
+
continue a parse at a given point. Even someone who's never touched a
|
|
288
|
+
lambda in his life can understand this approach. Additionally, far
|
|
289
|
+
fewer objects are allocated in the course of parsing now.</p>
|
|
290
|
+
|
|
291
|
+
<p>The biggest speedup comes from the fact that the parsing no longer
|
|
292
|
+
has to touch the DOM though. In CodeMirror 1, on an older browser, you
|
|
293
|
+
could <em>see</em> the parser work its way through the document,
|
|
294
|
+
managing some twenty lines in each 50-millisecond time slice it got. It
|
|
295
|
+
was reading its input from the DOM, and updating the DOM as it went
|
|
296
|
+
along, which any experienced JavaScript programmer will immediately
|
|
297
|
+
spot as a recipe for slowness. In CodeMirror 2, the parser usually
|
|
298
|
+
finishes the whole document in a single 100-millisecond time slice—it
|
|
299
|
+
manages some 1500 lines during that time on Chrome. All it has to do
|
|
300
|
+
is munge strings, so there is no real reason for it to be slow
|
|
301
|
+
anymore.</p>
|
|
302
|
+
|
|
303
|
+
<h2 id="summary">What Gives?</h2>
|
|
304
|
+
|
|
305
|
+
<p>Given all this, what can you expect from CodeMirror 2? First, the
|
|
306
|
+
good:</p>
|
|
307
|
+
|
|
308
|
+
<ul>
|
|
309
|
+
|
|
310
|
+
<li><strong>Small.</strong> the base library is some 32k when minified
|
|
311
|
+
now, 12k when gzipped. It's smaller than its own logo.</li>
|
|
312
|
+
|
|
313
|
+
<li><strong>Lightweight.</strong> CodeMirror 2 initializes very
|
|
314
|
+
quickly, and does almost no work when it is not focused. This means
|
|
315
|
+
you can treat it almost like a textarea, have multiple instances on a
|
|
316
|
+
page without trouble.</li>
|
|
317
|
+
|
|
318
|
+
<li><strong>Huge document support.</strong> Since highlighting is
|
|
319
|
+
really fast, and no DOM structure is being built for non-visible
|
|
320
|
+
content, you don't have to worry about locking up your browser when a
|
|
321
|
+
user enters a megabyte-sized document.</li>
|
|
322
|
+
|
|
323
|
+
<li><strong>Extended API.</strong> Some things kept coming up in the
|
|
324
|
+
mailing list, such as marking pieces of text or lines, which were
|
|
325
|
+
extremely hard to do with CodeMirror 1. The new version has proper
|
|
326
|
+
support for these built in.</li>
|
|
327
|
+
|
|
328
|
+
<li><strong>Tab support.</strong> Tabs inside editable documents were,
|
|
329
|
+
for some reason, a no-go. At least six different people announced they
|
|
330
|
+
were going to add tab support to CodeMirror 1, none survived (I mean,
|
|
331
|
+
none delivered a working version). CodeMirror 2 no longer removes tabs
|
|
332
|
+
from your document.</li>
|
|
333
|
+
|
|
334
|
+
<li><strong>Sane styling.</strong> <code>iframe</code> nodes aren't
|
|
335
|
+
really known for respecting document flow. Now that an editor instance
|
|
336
|
+
is a plain <code>div</code> element, it is much easier to size it to
|
|
337
|
+
fit the surrounding elements. You don't even have to make it scroll if
|
|
338
|
+
you do not <a href="demo/resize.html">want to</a>.</li>
|
|
339
|
+
|
|
340
|
+
</ul>
|
|
341
|
+
|
|
342
|
+
<p>Then, the bad:</p>
|
|
343
|
+
|
|
344
|
+
<ul>
|
|
345
|
+
|
|
346
|
+
<li><strong>No line-wrapping.</strong> I'd have liked to get
|
|
347
|
+
line-wrapping to work, but it doesn't match the model I'm using very
|
|
348
|
+
well. It is important that cursor movement in the textarea matches
|
|
349
|
+
what you see on the screen, and it seems to be impossible to have the
|
|
350
|
+
lines wrapped the same in the textarea and the normal DOM.</li>
|
|
351
|
+
|
|
352
|
+
<li><strong>Some cursor flakiness.</strong> The textarea hack does not
|
|
353
|
+
really do justice to the complexity of cursor handling—a selection is
|
|
354
|
+
typically more than just an offset into a string. For example, if you
|
|
355
|
+
use the up and down arrow keys to move to a shorter line and then
|
|
356
|
+
back, you'll end up in your old position in most editor controls, but
|
|
357
|
+
CodeMirror 2 currently doesn't remember the 'real' cursor column in
|
|
358
|
+
this case. These can be worked around on a case-by-case basis, but
|
|
359
|
+
I haven't put much energy into that yet.</li>
|
|
360
|
+
|
|
361
|
+
<li><strong>Limited interaction with the editable panel.</strong>
|
|
362
|
+
Since the element you're looking at is not a real editable panel,
|
|
363
|
+
native browser behaviour for editable controls doesn't work
|
|
364
|
+
automatically. Through a lot of event glue code, I've managed to make
|
|
365
|
+
drag and drop work pretty well, have context menus work on most
|
|
366
|
+
browsers (except Opera). Middle-click paste on Firefox in Linux is
|
|
367
|
+
broken until someone finds a way to intercept it.</li>
|
|
368
|
+
|
|
369
|
+
</ul>
|
|
370
|
+
|
|
371
|
+
</div><div class="rightsmall blk">
|
|
372
|
+
|
|
373
|
+
<h2>Contents</h2>
|
|
374
|
+
|
|
375
|
+
<ul>
|
|
376
|
+
<li><a href="#intro">Introduction</a></li>
|
|
377
|
+
<li><a href="#approach">General Approach</a></li>
|
|
378
|
+
<li><a href="#input">Input</a></li>
|
|
379
|
+
<li><a href="#selection">Selection</a></li>
|
|
380
|
+
<li><a href="#update">Intelligent Updating</a></li>
|
|
381
|
+
<li><a href="#parse">Parsing</a></li>
|
|
382
|
+
<li><a href="#summary">What Gives?</a></li>
|
|
383
|
+
</ul>
|
|
384
|
+
|
|
385
|
+
</div></div>
|
|
386
|
+
|
|
387
|
+
<div style="height: 2em"> </div>
|
|
388
|
+
|
|
389
|
+
</body></html>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
.CodeMirror {
|
|
2
|
+
line-height: 1em;
|
|
3
|
+
font-family: monospace;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.CodeMirror-scroll {
|
|
7
|
+
overflow: auto;
|
|
8
|
+
height: 300px;
|
|
9
|
+
/* This is needed to prevent an IE[67] bug where the scrolled content
|
|
10
|
+
is visible outside of the scrolling box. */
|
|
11
|
+
position: relative;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.CodeMirror-gutter {
|
|
15
|
+
position: absolute; left: 0; top: 0;
|
|
16
|
+
background-color: #f7f7f7;
|
|
17
|
+
border-right: 1px solid #eee;
|
|
18
|
+
min-width: 2em;
|
|
19
|
+
height: 100%;
|
|
20
|
+
}
|
|
21
|
+
.CodeMirror-gutter-text {
|
|
22
|
+
color: #aaa;
|
|
23
|
+
text-align: right;
|
|
24
|
+
padding: .4em .2em .4em .4em;
|
|
25
|
+
}
|
|
26
|
+
.CodeMirror-lines {
|
|
27
|
+
padding: .4em;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.CodeMirror pre {
|
|
31
|
+
-moz-border-radius: 0;
|
|
32
|
+
-webkit-border-radius: 0;
|
|
33
|
+
-o-border-radius: 0;
|
|
34
|
+
border-radius: 0;
|
|
35
|
+
border-width: 0; margin: 0; padding: 0; background: transparent;
|
|
36
|
+
font-family: inherit;
|
|
37
|
+
font-size: inherit;
|
|
38
|
+
padding: 0; margin: 0;
|
|
39
|
+
white-space: pre;
|
|
40
|
+
word-wrap: normal;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.CodeMirror textarea {
|
|
44
|
+
font-family: inherit !important;
|
|
45
|
+
font-size: inherit !important;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.CodeMirror-cursor {
|
|
49
|
+
z-index: 10;
|
|
50
|
+
position: absolute;
|
|
51
|
+
visibility: hidden;
|
|
52
|
+
border-left: 1px solid black !important;
|
|
53
|
+
}
|
|
54
|
+
.CodeMirror-focused .CodeMirror-cursor {
|
|
55
|
+
visibility: visible;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
span.CodeMirror-selected {
|
|
59
|
+
background: #ccc !important;
|
|
60
|
+
color: HighlightText !important;
|
|
61
|
+
}
|
|
62
|
+
.CodeMirror-focused span.CodeMirror-selected {
|
|
63
|
+
background: Highlight !important;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.CodeMirror-matchingbracket {color: #0f0 !important;}
|
|
67
|
+
.CodeMirror-nonmatchingbracket {color: #f22 !important;}
|