puffer_pages 0.1.0 → 0.1.1

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.
Files changed (46) hide show
  1. data/.gitignore +1 -2
  2. data/.rvmrc +1 -0
  3. data/.travis.yml +6 -0
  4. data/CHANGELOG.md +12 -0
  5. data/Gemfile.lock +83 -78
  6. data/app/assets/javascripts/puffer/codemirror.js +1382 -818
  7. data/app/assets/javascripts/puffer/codemirror/htmlmixed.js +4 -5
  8. data/app/assets/javascripts/puffer/codemirror/javascript.js +12 -4
  9. data/app/assets/javascripts/puffer/codemirror/xml.js +35 -14
  10. data/app/assets/javascripts/puffer/liquid.js +15 -0
  11. data/app/assets/javascripts/puffer/puffer_pages.js +56 -6
  12. data/app/assets/stylesheets/puffer/codemirror.css +50 -14
  13. data/app/assets/stylesheets/puffer/codemirror/cobalt.css +1 -0
  14. data/app/assets/stylesheets/puffer/codemirror/eclipse.css +3 -2
  15. data/app/assets/stylesheets/puffer/codemirror/elegant.css +1 -0
  16. data/app/assets/stylesheets/puffer/codemirror/monokai.css +28 -0
  17. data/app/assets/stylesheets/puffer/codemirror/neat.css +1 -0
  18. data/app/assets/stylesheets/puffer/codemirror/night.css +1 -0
  19. data/app/assets/stylesheets/puffer/codemirror/rubyblue.css +21 -0
  20. data/app/assets/stylesheets/puffer/puffer_pages.css +59 -3
  21. data/app/components/codemirror/form.html.erb +16 -0
  22. data/app/components/codemirror_component.rb +9 -0
  23. data/app/components/page_parts/form.html.erb +13 -4
  24. data/app/controllers/pages_controller.rb +1 -10
  25. data/app/controllers/puffer_pages/layouts_base.rb +1 -1
  26. data/app/controllers/puffer_pages/pages_base.rb +1 -1
  27. data/app/controllers/puffer_pages/snippets_base.rb +1 -1
  28. data/app/models/puffer_pages/page.rb +4 -3
  29. data/app/views/layouts/puffer_pages_layout.html.erb +2 -1
  30. data/gemfiles/Gemfile-rails-3.1 +5 -0
  31. data/gemfiles/Gemfile-rails-3.1.lock +173 -0
  32. data/gemfiles/Gemfile-rails-3.2 +5 -0
  33. data/gemfiles/Gemfile-rails-3.2.lock +171 -0
  34. data/lib/puffer_pages.rb +5 -0
  35. data/lib/puffer_pages/extensions/controller.rb +12 -16
  36. data/lib/puffer_pages/extensions/mapper.rb +7 -13
  37. data/lib/puffer_pages/liquid/tags/page_attribute.rb +39 -0
  38. data/lib/puffer_pages/version.rb +1 -1
  39. data/puffer_pages.gemspec +7 -4
  40. data/spec/dummy/app/controllers/application_controller.rb +4 -0
  41. data/spec/lib/tags_spec.rb +25 -3
  42. data/spec/models/page_spec.rb +2 -3
  43. metadata +75 -43
  44. data/app/assets/stylesheets/puffer/codemirror/default.css +0 -19
  45. data/app/components/page_part_body/form.html.erb +0 -3
  46. data/app/components/page_part_body_component.rb +0 -3
data/.gitignore CHANGED
@@ -1,8 +1,7 @@
1
1
  .bundle/
2
- .rvmrc
3
2
  log/*.log
4
3
  pkg/
5
4
  spec/dummy/db/*.sqlite3
6
5
  spec/dummy/log/*.log
7
6
  spec/dummy/tmp/
8
- *.orig
7
+ *.orig
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.2@puffer_pages --create
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ rvm:
2
+ - 1.9.2
3
+ - 1.9.3
4
+ - ruby-head
5
+ #- rbx
6
+ #- rbx-2.0
data/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ ## 0.1.2 \[ In Development \] \[ Branch: master \]
2
+
3
+ ## 0.1.1
4
+
5
+ ### New features
6
+
7
+ * Updated codemirror to 2.2 version.
8
+ * Custom component renamed to Codemirror component.
9
+ * Added buttons panel to Codemirror editor.
10
+ * Added fullscreen feature to Codemirror.
11
+ * Added Liquid overlay to Codemirror.
12
+ * Rails 3.2 compatible.
data/Gemfile.lock CHANGED
@@ -1,146 +1,149 @@
1
1
  GIT
2
2
  remote: git://github.com/puffer/puffer.git
3
- revision: cdbcc66e5b205cd8f95a48e84568c6748640f349
3
+ revision: 719b80730fd723c7f665c95315a19edbb8148593
4
4
  specs:
5
- puffer (0.1.0)
5
+ puffer (0.1.1)
6
6
  kaminari
7
7
  orm_adapter
8
- rails (~> 3.1.0)
8
+ rails (~> 3.1)
9
9
 
10
10
  PATH
11
11
  remote: .
12
12
  specs:
13
- puffer_pages (0.1.0)
13
+ puffer_pages (0.1.1)
14
14
  liquid
15
15
  nested_set
16
16
  puffer
17
- rails (~> 3.1.0)
17
+ rails (~> 3.1)
18
18
 
19
19
  GEM
20
20
  remote: http://rubygems.org/
21
21
  specs:
22
- actionmailer (3.1.1)
23
- actionpack (= 3.1.1)
24
- mail (~> 2.3.0)
25
- actionpack (3.1.1)
26
- activemodel (= 3.1.1)
27
- activesupport (= 3.1.1)
22
+ actionmailer (3.2.1)
23
+ actionpack (= 3.2.1)
24
+ mail (~> 2.4.0)
25
+ actionpack (3.2.1)
26
+ activemodel (= 3.2.1)
27
+ activesupport (= 3.2.1)
28
28
  builder (~> 3.0.0)
29
29
  erubis (~> 2.7.0)
30
- i18n (~> 0.6)
31
- rack (~> 1.3.2)
30
+ journey (~> 1.0.1)
31
+ rack (~> 1.4.0)
32
32
  rack-cache (~> 1.1)
33
- rack-mount (~> 0.8.2)
34
33
  rack-test (~> 0.6.1)
35
- sprockets (~> 2.0.2)
36
- activemodel (3.1.1)
37
- activesupport (= 3.1.1)
34
+ sprockets (~> 2.1.2)
35
+ activemodel (3.2.1)
36
+ activesupport (= 3.2.1)
38
37
  builder (~> 3.0.0)
39
- i18n (~> 0.6)
40
- activerecord (3.1.1)
41
- activemodel (= 3.1.1)
42
- activesupport (= 3.1.1)
43
- arel (~> 2.2.1)
38
+ activerecord (3.2.1)
39
+ activemodel (= 3.2.1)
40
+ activesupport (= 3.2.1)
41
+ arel (~> 3.0.0)
44
42
  tzinfo (~> 0.3.29)
45
- activeresource (3.1.1)
46
- activemodel (= 3.1.1)
47
- activesupport (= 3.1.1)
48
- activesupport (3.1.1)
43
+ activeresource (3.2.1)
44
+ activemodel (= 3.2.1)
45
+ activesupport (= 3.2.1)
46
+ activesupport (3.2.1)
47
+ i18n (~> 0.6)
49
48
  multi_json (~> 1.0)
50
- arel (2.2.1)
49
+ arel (3.0.0)
50
+ bcrypt-ruby (3.0.1)
51
51
  builder (3.0.0)
52
- capybara (1.1.1)
52
+ capybara (1.1.2)
53
53
  mime-types (>= 1.16)
54
54
  nokogiri (>= 1.3.3)
55
55
  rack (>= 1.0.0)
56
56
  rack-test (>= 0.5.4)
57
57
  selenium-webdriver (~> 2.0)
58
58
  xpath (~> 0.1.4)
59
- childprocess (0.2.2)
59
+ childprocess (0.3.1)
60
60
  ffi (~> 1.0.6)
61
- database_cleaner (0.6.7)
61
+ database_cleaner (0.7.1)
62
62
  diff-lcs (1.1.3)
63
63
  erubis (2.7.0)
64
- fabrication (1.1.0)
65
- ffi (1.0.9)
64
+ fabrication (1.2.0)
65
+ ffi (1.0.11)
66
66
  forgery (0.5.0)
67
- guard (0.6.3)
67
+ guard (1.0.0)
68
+ ffi (>= 0.5.0)
68
69
  thor (~> 0.14.6)
69
- guard-rspec (0.4.5)
70
- guard (>= 0.4.0)
70
+ guard-rspec (0.6.0)
71
+ guard (>= 0.10.0)
71
72
  hike (1.2.1)
72
73
  i18n (0.6.0)
73
- json (1.6.1)
74
- json_pure (1.6.0)
75
- kaminari (0.12.4)
76
- rails (>= 3.0.0)
77
- libnotify (0.5.7)
74
+ journey (1.0.1)
75
+ json (1.6.5)
76
+ kaminari (0.13.0)
77
+ actionpack (>= 3.0.0)
78
+ activesupport (>= 3.0.0)
79
+ railties (>= 3.0.0)
80
+ libnotify (0.7.2)
78
81
  liquid (2.3.0)
79
- mail (2.3.0)
82
+ mail (2.4.1)
80
83
  i18n (>= 0.4.0)
81
84
  mime-types (~> 1.16)
82
85
  treetop (~> 1.4.8)
83
86
  mime-types (1.17.2)
84
- multi_json (1.0.3)
87
+ multi_json (1.0.4)
85
88
  mysql (2.8.1)
86
89
  nested_set (1.6.8)
87
90
  activerecord (>= 3.0.0)
88
91
  railties (>= 3.0.0)
89
92
  nokogiri (1.5.0)
90
- orm_adapter (0.0.5)
91
- pg (0.11.0)
93
+ orm_adapter (0.0.6)
94
+ pg (0.12.2)
92
95
  polyglot (0.3.3)
93
- rack (1.3.5)
96
+ rack (1.4.1)
94
97
  rack-cache (1.1)
95
98
  rack (>= 0.4)
96
- rack-mount (0.8.3)
97
- rack (>= 1.0.0)
98
99
  rack-ssl (1.3.2)
99
100
  rack
100
101
  rack-test (0.6.1)
101
102
  rack (>= 1.0)
102
- rails (3.1.1)
103
- actionmailer (= 3.1.1)
104
- actionpack (= 3.1.1)
105
- activerecord (= 3.1.1)
106
- activeresource (= 3.1.1)
107
- activesupport (= 3.1.1)
103
+ rails (3.2.1)
104
+ actionmailer (= 3.2.1)
105
+ actionpack (= 3.2.1)
106
+ activerecord (= 3.2.1)
107
+ activeresource (= 3.2.1)
108
+ activesupport (= 3.2.1)
108
109
  bundler (~> 1.0)
109
- railties (= 3.1.1)
110
- railties (3.1.1)
111
- actionpack (= 3.1.1)
112
- activesupport (= 3.1.1)
110
+ railties (= 3.2.1)
111
+ railties (3.2.1)
112
+ actionpack (= 3.2.1)
113
+ activesupport (= 3.2.1)
113
114
  rack-ssl (~> 1.3.2)
114
115
  rake (>= 0.8.7)
115
116
  rdoc (~> 3.4)
116
117
  thor (~> 0.14.6)
117
118
  rake (0.9.2.2)
118
- rdoc (3.11)
119
+ rb-inotify (0.8.8)
120
+ ffi (>= 0.5.0)
121
+ rdoc (3.12)
119
122
  json (~> 1.4)
120
- rspec (2.6.0)
121
- rspec-core (~> 2.6.0)
122
- rspec-expectations (~> 2.6.0)
123
- rspec-mocks (~> 2.6.0)
124
- rspec-core (2.6.4)
125
- rspec-expectations (2.6.0)
123
+ rspec (2.8.0)
124
+ rspec-core (~> 2.8.0)
125
+ rspec-expectations (~> 2.8.0)
126
+ rspec-mocks (~> 2.8.0)
127
+ rspec-core (2.8.0)
128
+ rspec-expectations (2.8.0)
126
129
  diff-lcs (~> 1.1.2)
127
- rspec-mocks (2.6.0)
128
- rspec-rails (2.6.1)
129
- actionpack (~> 3.0)
130
- activesupport (~> 3.0)
131
- railties (~> 3.0)
132
- rspec (~> 2.6.0)
133
- rubyzip (0.9.4)
134
- selenium-webdriver (2.5.0)
135
- childprocess (>= 0.2.1)
136
- ffi (>= 1.0.7)
137
- json_pure
130
+ rspec-mocks (2.8.0)
131
+ rspec-rails (2.8.1)
132
+ actionpack (>= 3.0)
133
+ activesupport (>= 3.0)
134
+ railties (>= 3.0)
135
+ rspec (~> 2.8.0)
136
+ rubyzip (0.9.5)
137
+ selenium-webdriver (2.18.0)
138
+ childprocess (>= 0.2.5)
139
+ ffi (~> 1.0.9)
140
+ multi_json (~> 1.0.4)
138
141
  rubyzip
139
- sprockets (2.0.3)
142
+ sprockets (2.1.2)
140
143
  hike (~> 1.2)
141
144
  rack (~> 1.0)
142
- tilt (!= 1.3.0, ~> 1.1)
143
- sqlite3 (1.3.4)
145
+ tilt (~> 1.1, != 1.3.0)
146
+ sqlite3 (1.3.5)
144
147
  sqlite3-ruby (1.3.3)
145
148
  sqlite3 (>= 1.3.3)
146
149
  thor (0.14.6)
@@ -156,6 +159,7 @@ PLATFORMS
156
159
  ruby
157
160
 
158
161
  DEPENDENCIES
162
+ bcrypt-ruby
159
163
  capybara (>= 0.4.0)
160
164
  database_cleaner
161
165
  fabrication
@@ -167,5 +171,6 @@ DEPENDENCIES
167
171
  pg
168
172
  puffer!
169
173
  puffer_pages!
174
+ rb-inotify
170
175
  rspec-rails
171
176
  sqlite3-ruby
@@ -1,3 +1,5 @@
1
+ // CodeMirror version 2.2
2
+ //
1
3
  // All functions that need access to the editor's state live inside
2
4
  // the CodeMirror function. Below that, at the bottom of the file,
3
5
  // some utilities are defined.
@@ -16,19 +18,19 @@ var CodeMirror = (function() {
16
18
  var targetDocument = options["document"];
17
19
  // The element in which the editor lives.
18
20
  var wrapper = targetDocument.createElement("div");
19
- wrapper.className = "CodeMirror";
21
+ wrapper.className = "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : "");
20
22
  // This mess creates the base DOM structure for the editor.
21
23
  wrapper.innerHTML =
22
- '<div style="overflow: hidden; position: relative; width: 1px; height: 0px;">' + // Wraps and hides input textarea
23
- '<textarea style="position: absolute; width: 2px;" wrap="off" ' +
24
+ '<div style="overflow: hidden; position: relative; width: 3px; height: 0px;">' + // Wraps and hides input textarea
25
+ '<textarea style="position: absolute; padding: 0; width: 1px;" wrap="off" ' +
24
26
  'autocorrect="off" autocapitalize="off"></textarea></div>' +
25
- '<div class="CodeMirror-scroll cm-s-' + options.theme + '">' +
27
+ '<div class="CodeMirror-scroll" tabindex="-1">' +
26
28
  '<div style="position: relative">' + // Set to the height of the text, causes scrolling
27
- '<div style="position: absolute; height: 0; width: 0; overflow: hidden;"></div>' +
28
29
  '<div style="position: relative">' + // Moved around its parent to cover visible view
29
30
  '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
30
31
  // Provides positioning relative to (visible) text origin
31
- '<div class="CodeMirror-lines"><div style="position: relative" draggable="true">' +
32
+ '<div class="CodeMirror-lines"><div style="position: relative">' +
33
+ '<div style="position: absolute; width: 100%; height: 0; overflow: hidden; visibility: hidden"></div>' +
32
34
  '<pre class="CodeMirror-cursor">&#160;</pre>' + // Absolutely positioned blinky cursor
33
35
  '<div></div>' + // This DIV contains the actual code
34
36
  '</div></div></div></div></div>';
@@ -36,51 +38,62 @@ var CodeMirror = (function() {
36
38
  // I've never seen more elegant code in my life.
37
39
  var inputDiv = wrapper.firstChild, input = inputDiv.firstChild,
38
40
  scroller = wrapper.lastChild, code = scroller.firstChild,
39
- measure = code.firstChild, mover = measure.nextSibling,
40
- gutter = mover.firstChild, gutterText = gutter.firstChild,
41
- lineSpace = gutter.nextSibling.firstChild,
42
- cursor = lineSpace.firstChild, lineDiv = cursor.nextSibling;
43
- if (options.tabindex != null) input.tabindex = options.tabindex;
41
+ mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild,
42
+ lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild,
43
+ cursor = measure.nextSibling, lineDiv = cursor.nextSibling;
44
+ themeChanged();
45
+ // Needed to hide big blue blinking cursor on Mobile Safari
46
+ if (/AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent)) input.style.width = "0px";
47
+ if (!webkit) lineSpace.draggable = true;
48
+ if (options.tabindex != null) input.tabIndex = options.tabindex;
44
49
  if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
45
50
 
51
+ // Check for problem with IE innerHTML not working when we have a
52
+ // P (or similar) parent node.
53
+ try { stringWidth("x"); }
54
+ catch (e) {
55
+ if (e.message.match(/runtime/i))
56
+ e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)");
57
+ throw e;
58
+ }
59
+
46
60
  // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.
47
61
  var poll = new Delayed(), highlight = new Delayed(), blinker;
48
62
 
49
- // mode holds a mode API object. lines an array of Line objects
50
- // (see Line constructor), work an array of lines that should be
51
- // parsed, and history the undo history (instance of History
52
- // constructor).
53
- var mode, lines = [new Line("")], work, history = new History(), focused;
63
+ // mode holds a mode API object. doc is the tree of Line objects,
64
+ // work an array of lines that should be parsed, and history the
65
+ // undo history (instance of History constructor).
66
+ var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused;
54
67
  loadMode();
55
68
  // The selection. These are always maintained to point at valid
56
69
  // positions. Inverted is used to remember that the user is
57
70
  // selecting bottom-to-top.
58
71
  var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false};
59
72
  // Selection-related flags. shiftSelecting obviously tracks
60
- // whether the user is holding shift. reducedSelection is a hack
61
- // to get around the fact that we can't create inverted
62
- // selections. See below.
63
- var shiftSelecting, reducedSelection, lastClick, lastDoubleClick;
73
+ // whether the user is holding shift.
74
+ var shiftSelecting, lastClick, lastDoubleClick, draggingText, overwrite = false;
64
75
  // Variables used by startOperation/endOperation to track what
65
76
  // happened during the operation.
66
- var updateInput, changes, textChanged, selectionChanged, leaveInputAlone, gutterDirty;
77
+ var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone,
78
+ gutterDirty, callbacks;
67
79
  // Current visible range (may be bigger than the view window).
68
- var showingFrom = 0, showingTo = 0, lastHeight = 0, curKeyId = null;
69
- // editing will hold an object describing the things we put in the
70
- // textarea, to help figure out whether something changed.
80
+ var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0;
71
81
  // bracketHighlighted is used to remember that a backet has been
72
82
  // marked.
73
- var editing, bracketHighlighted;
83
+ var bracketHighlighted;
74
84
  // Tracks the maximum line length so that the horizontal scrollbar
75
85
  // can be kept static when scrolling.
76
- var maxLine = "", maxWidth;
86
+ var maxLine = "", maxWidth, tabText = computeTabText();
77
87
 
78
88
  // Initialize the content.
79
89
  operation(function(){setValue(options.value || ""); updateInput = false;})();
90
+ var history = new History();
80
91
 
81
92
  // Register our event handlers.
82
93
  connect(scroller, "mousedown", operation(onMouseDown));
94
+ connect(scroller, "dblclick", operation(onDoubleClick));
83
95
  connect(lineSpace, "dragstart", onDragStart);
96
+ connect(lineSpace, "selectstart", e_preventDefault);
84
97
  // Gecko browsers fire contextmenu *after* opening the menu, at
85
98
  // which point we can't mess with it anymore. Context menu is
86
99
  // handled in onMouseDown for Gecko.
@@ -92,6 +105,7 @@ var CodeMirror = (function() {
92
105
  });
93
106
  connect(window, "resize", function() {updateDisplay(true);});
94
107
  connect(input, "keyup", operation(onKeyUp));
108
+ connect(input, "input", fastPoll);
95
109
  connect(input, "keydown", operation(onKeyDown));
96
110
  connect(input, "keypress", operation(onKeyPress));
97
111
  connect(input, "focus", onFocus);
@@ -101,16 +115,16 @@ var CodeMirror = (function() {
101
115
  connect(scroller, "dragover", e_stop);
102
116
  connect(scroller, "drop", operation(onDrop));
103
117
  connect(scroller, "paste", function(){focusInput(); fastPoll();});
104
- connect(input, "paste", function(){fastPoll();});
105
- connect(input, "cut", function(){fastPoll();});
106
-
107
- // IE throws unspecified error in certain cases, when
118
+ connect(input, "paste", fastPoll);
119
+ connect(input, "cut", operation(function(){replaceSelection("");}));
120
+
121
+ // IE throws unspecified error in certain cases, when
108
122
  // trying to access activeElement before onload
109
123
  var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { }
110
124
  if (hasFocus) setTimeout(onFocus, 20);
111
125
  else onBlur();
112
126
 
113
- function isLine(l) {return l >= 0 && l < lines.length;}
127
+ function isLine(l) {return l >= 0 && l < doc.size;}
114
128
  // The instance object that we'll return. Mostly calls out to
115
129
  // local functions in the CodeMirror function. Some do some extra
116
130
  // range checking and/or clipping. operation is used to wrap the
@@ -123,12 +137,15 @@ var CodeMirror = (function() {
123
137
  replaceSelection: operation(replaceSelection),
124
138
  focus: function(){focusInput(); onFocus(); fastPoll();},
125
139
  setOption: function(option, value) {
140
+ var oldVal = options[option];
126
141
  options[option] = value;
127
- if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber")
142
+ if (option == "mode" || option == "indentUnit") loadMode();
143
+ else if (option == "readOnly" && value) {onBlur(); input.blur();}
144
+ else if (option == "theme") themeChanged();
145
+ else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)();
146
+ else if (option == "tabSize") operation(tabsChanged)();
147
+ if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme")
128
148
  operation(gutterChanged)();
129
- else if (option == "mode" || option == "indentUnit") loadMode();
130
- else if (option == "readOnly" && value == "nocursor") input.blur();
131
- else if (option == "theme") scroller.className = scroller.className.replace(/cm-s-\w+/, "cm-s-" + value);
132
149
  },
133
150
  getOption: function(option) {return options[option];},
134
151
  undo: operation(undo),
@@ -136,14 +153,16 @@ var CodeMirror = (function() {
136
153
  indentLine: operation(function(n, dir) {
137
154
  if (isLine(n)) indentLine(n, dir == null ? "smart" : dir ? "add" : "subtract");
138
155
  }),
156
+ indentSelection: operation(indentSelected),
139
157
  historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
158
+ clearHistory: function() {history = new History();},
140
159
  matchBrackets: operation(function(){matchBrackets(true);}),
141
- getTokenAt: function(pos) {
160
+ getTokenAt: operation(function(pos) {
142
161
  pos = clipPos(pos);
143
- return lines[pos.line].getTokenAt(mode, getStateBefore(pos.line), pos.ch);
144
- },
162
+ return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), pos.ch);
163
+ }),
145
164
  getStateAfter: function(line) {
146
- line = clipLine(line == null ? lines.length - 1: line);
165
+ line = clipLine(line == null ? doc.size - 1: line);
147
166
  return getStateBefore(line + 1);
148
167
  },
149
168
  cursorCoords: function(start){
@@ -153,14 +172,23 @@ var CodeMirror = (function() {
153
172
  charCoords: function(pos){return pageCoords(clipPos(pos));},
154
173
  coordsChar: function(coords) {
155
174
  var off = eltOffset(lineSpace);
156
- var line = clipLine(Math.min(lines.length - 1, showingFrom + Math.floor((coords.y - off.top) / lineHeight())));
157
- return clipPos({line: line, ch: charFromX(clipLine(line), coords.x - off.left)});
175
+ return coordsChar(coords.x - off.left, coords.y - off.top);
158
176
  },
159
- getSearchCursor: function(query, pos, caseFold) {return new SearchCursor(query, pos, caseFold);},
160
- markText: operation(function(a, b, c){return operation(markText(a, b, c));}),
177
+ markText: operation(markText),
178
+ setBookmark: setBookmark,
161
179
  setMarker: operation(addGutterMarker),
162
180
  clearMarker: operation(removeGutterMarker),
163
181
  setLineClass: operation(setLineClass),
182
+ hideLine: operation(function(h) {return setLineHidden(h, true);}),
183
+ showLine: operation(function(h) {return setLineHidden(h, false);}),
184
+ onDeleteLine: function(line, f) {
185
+ if (typeof line == "number") {
186
+ if (!isLine(line)) return null;
187
+ line = getLine(line);
188
+ }
189
+ (line.handlers || (line.handlers = [])).push(f);
190
+ return line;
191
+ },
164
192
  lineInfo: lineInfo,
165
193
  addWidget: function(pos, node, scroll, vert, horiz) {
166
194
  pos = localCoords(clipPos(pos));
@@ -169,7 +197,7 @@ var CodeMirror = (function() {
169
197
  code.appendChild(node);
170
198
  if (vert == "over") top = pos.y;
171
199
  else if (vert == "near") {
172
- var vspace = Math.max(scroller.offsetHeight, lines.length * lineHeight()),
200
+ var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()),
173
201
  hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft();
174
202
  if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight)
175
203
  top = pos.y - node.offsetHeight;
@@ -190,20 +218,24 @@ var CodeMirror = (function() {
190
218
  scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);
191
219
  },
192
220
 
193
- lineCount: function() {return lines.length;},
221
+ lineCount: function() {return doc.size;},
222
+ clipPos: clipPos,
194
223
  getCursor: function(start) {
195
224
  if (start == null) start = sel.inverted;
196
225
  return copyPos(start ? sel.from : sel.to);
197
226
  },
198
227
  somethingSelected: function() {return !posEq(sel.from, sel.to);},
199
- setCursor: operation(function(line, ch) {
200
- if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch);
201
- else setCursor(line, ch);
228
+ setCursor: operation(function(line, ch, user) {
229
+ if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user);
230
+ else setCursor(line, ch, user);
231
+ }),
232
+ setSelection: operation(function(from, to, user) {
233
+ (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from));
202
234
  }),
203
- setSelection: operation(function(from, to) {setSelection(clipPos(from), clipPos(to || from));}),
204
- getLine: function(line) {if (isLine(line)) return lines[line].text;},
235
+ getLine: function(line) {if (isLine(line)) return getLine(line).text;},
236
+ getLineHandle: function(line) {if (isLine(line)) return getLine(line);},
205
237
  setLine: operation(function(line, text) {
206
- if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: lines[line].text.length});
238
+ if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
207
239
  }),
208
240
  removeLine: operation(function(line) {
209
241
  if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0}));
@@ -211,6 +243,32 @@ var CodeMirror = (function() {
211
243
  replaceRange: operation(replaceRange),
212
244
  getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));},
213
245
 
246
+ execCommand: function(cmd) {return commands[cmd](instance);},
247
+ // Stuff used by commands, probably not much use to outside code.
248
+ moveH: operation(moveH),
249
+ deleteH: operation(deleteH),
250
+ moveV: operation(moveV),
251
+ toggleOverwrite: function() {overwrite = !overwrite;},
252
+
253
+ posFromIndex: function(off) {
254
+ var lineNo = 0, ch;
255
+ doc.iter(0, doc.size, function(line) {
256
+ var sz = line.text.length + 1;
257
+ if (sz > off) { ch = off; return true; }
258
+ off -= sz;
259
+ ++lineNo;
260
+ });
261
+ return clipPos({line: lineNo, ch: ch});
262
+ },
263
+ indexFromPos: function (coords) {
264
+ if (coords.line < 0 || coords.ch < 0) return 0;
265
+ var index = coords.ch;
266
+ doc.iter(0, coords.line, function (line) {
267
+ index += line.text.length + 1;
268
+ });
269
+ return index;
270
+ },
271
+
214
272
  operation: function(f){return operation(f)();},
215
273
  refresh: function(){updateDisplay(true);},
216
274
  getInputField: function(){return input;},
@@ -219,27 +277,32 @@ var CodeMirror = (function() {
219
277
  getGutterElement: function(){return gutter;}
220
278
  };
221
279
 
280
+ function getLine(n) { return getLineAt(doc, n); }
281
+ function updateLineHeight(line, height) {
282
+ gutterDirty = true;
283
+ var diff = height - line.height;
284
+ for (var n = line; n; n = n.parent) n.height += diff;
285
+ }
286
+
222
287
  function setValue(code) {
223
- history = null;
224
288
  var top = {line: 0, ch: 0};
225
- updateLines(top, {line: lines.length - 1, ch: lines[lines.length-1].text.length},
289
+ updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length},
226
290
  splitLines(code), top, top);
227
- history = new History();
228
291
  updateInput = true;
229
292
  }
230
293
  function getValue(code) {
231
294
  var text = [];
232
- for (var i = 0, l = lines.length; i < l; ++i)
233
- text.push(lines[i].text);
295
+ doc.iter(0, doc.size, function(line) { text.push(line.text); });
234
296
  return text.join("\n");
235
297
  }
236
298
 
237
299
  function onMouseDown(e) {
300
+ setShift(e.shiftKey);
238
301
  // Check whether this is a click in a widget
239
302
  for (var n = e_target(e); n != wrapper; n = n.parentNode)
240
303
  if (n.parentNode == code && n != mover) return;
241
304
 
242
- // First, see if this is a click in the gutter
305
+ // See if this is a click in the gutter
243
306
  for (var n = e_target(e); n != wrapper; n = n.parentNode)
244
307
  if (n.parentNode == gutterText) {
245
308
  if (options.onGutterClick)
@@ -248,7 +311,7 @@ var CodeMirror = (function() {
248
311
  }
249
312
 
250
313
  var start = posFromMouse(e);
251
-
314
+
252
315
  switch (e_button(e)) {
253
316
  case 3:
254
317
  if (gecko && !mac) onContextMenu(e);
@@ -263,21 +326,34 @@ var CodeMirror = (function() {
263
326
  if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;}
264
327
 
265
328
  if (!focused) onFocus();
266
-
329
+
267
330
  var now = +new Date;
268
- if (lastDoubleClick > now - 400) {
331
+ if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
269
332
  e_preventDefault(e);
333
+ setTimeout(focusInput, 20);
270
334
  return selectLine(start.line);
271
- } else if (lastClick > now - 400) {
272
- lastDoubleClick = now;
335
+ } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
336
+ lastDoubleClick = {time: now, pos: start};
273
337
  e_preventDefault(e);
274
338
  return selectWordAt(start);
275
- } else { lastClick = now; }
339
+ } else { lastClick = {time: now, pos: start}; }
276
340
 
277
341
  var last = start, going;
278
342
  if (dragAndDrop && !posEq(sel.from, sel.to) &&
279
343
  !posLess(start, sel.from) && !posLess(sel.to, start)) {
280
344
  // Let the drag handler handle this.
345
+ if (webkit) lineSpace.draggable = true;
346
+ var up = connect(targetDocument, "mouseup", operation(function(e2) {
347
+ if (webkit) lineSpace.draggable = false;
348
+ draggingText = false;
349
+ up();
350
+ if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
351
+ e_preventDefault(e2);
352
+ setCursor(start.line, start.ch, true);
353
+ focusInput();
354
+ }
355
+ }), true);
356
+ draggingText = true;
281
357
  return;
282
358
  }
283
359
  e_preventDefault(e);
@@ -311,6 +387,15 @@ var CodeMirror = (function() {
311
387
  move(); up();
312
388
  }), true);
313
389
  }
390
+ function onDoubleClick(e) {
391
+ for (var n = e_target(e); n != wrapper; n = n.parentNode)
392
+ if (n.parentNode == gutterText) return e_preventDefault(e);
393
+ var start = posFromMouse(e);
394
+ if (!start) return;
395
+ lastDoubleClick = {time: +new Date, pos: start};
396
+ e_preventDefault(e);
397
+ selectWordAt(start);
398
+ }
314
399
  function onDrop(e) {
315
400
  e.preventDefault();
316
401
  var pos = posFromMouse(e, true), files = e.dataTransfer.files;
@@ -320,7 +405,13 @@ var CodeMirror = (function() {
320
405
  var reader = new FileReader;
321
406
  reader.onload = function() {
322
407
  text[i] = reader.result;
323
- if (++read == n) replaceRange(text.join(""), clipPos(pos), clipPos(pos));
408
+ if (++read == n) {
409
+ pos = clipPos(pos);
410
+ operation(function() {
411
+ var end = replaceRange(text.join(""), pos, pos);
412
+ setSelectionUser(pos, end);
413
+ })();
414
+ }
324
415
  };
325
416
  reader.readAsText(file);
326
417
  }
@@ -330,7 +421,13 @@ var CodeMirror = (function() {
330
421
  else {
331
422
  try {
332
423
  var text = e.dataTransfer.getData("Text");
333
- if (text) replaceRange(text, pos, pos);
424
+ if (text) {
425
+ var end = replaceRange(text, pos, pos);
426
+ var curFrom = sel.from, curTo = sel.to;
427
+ setSelectionUser(pos, end);
428
+ if (draggingText) replaceRange("", curFrom, curTo);
429
+ focusInput();
430
+ }
334
431
  }
335
432
  catch(e){}
336
433
  }
@@ -342,81 +439,76 @@ var CodeMirror = (function() {
342
439
  e.dataTransfer.setDragImage(escapeElement, 0, 0);
343
440
  e.dataTransfer.setData("Text", txt);
344
441
  }
442
+ function handleKeyBinding(e) {
443
+ var name = keyNames[e.keyCode], next = keyMap[options.keyMap].auto, bound, dropShift;
444
+ if (name == null || e.altGraphKey) {
445
+ if (next) options.keyMap = next;
446
+ return null;
447
+ }
448
+ if (e.altKey) name = "Alt-" + name;
449
+ if (e.ctrlKey) name = "Ctrl-" + name;
450
+ if (e.metaKey) name = "Cmd-" + name;
451
+ if (e.shiftKey && (bound = lookupKey("Shift-" + name, options.extraKeys, options.keyMap))) {
452
+ dropShift = true;
453
+ } else {
454
+ bound = lookupKey(name, options.extraKeys, options.keyMap);
455
+ }
456
+ if (typeof bound == "string") {
457
+ if (commands.propertyIsEnumerable(bound)) bound = commands[bound];
458
+ else bound = null;
459
+ }
460
+ if (next && (bound || !isModifierKey(e))) options.keyMap = next;
461
+ if (!bound) return false;
462
+ if (dropShift) {
463
+ var prevShift = shiftSelecting;
464
+ shiftSelecting = null;
465
+ bound(instance);
466
+ shiftSelecting = prevShift;
467
+ } else bound(instance);
468
+ e_preventDefault(e);
469
+ return true;
470
+ }
471
+ var lastStoppedKey = null;
345
472
  function onKeyDown(e) {
346
473
  if (!focused) onFocus();
347
-
348
474
  var code = e.keyCode;
349
475
  // IE does strange things with escape.
350
476
  if (ie && code == 27) { e.returnValue = false; }
351
- // Tries to detect ctrl on non-mac, cmd on mac.
352
- var mod = (mac ? e.metaKey : e.ctrlKey) && !e.altKey, anyMod = e.ctrlKey || e.altKey || e.metaKey;
353
- if (code == 16 || e.shiftKey) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
354
- else shiftSelecting = null;
477
+ setShift(code == 16 || e.shiftKey);
355
478
  // First give onKeyEvent option a chance to handle this.
356
479
  if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
357
-
358
- if (code == 33 || code == 34) {scrollPage(code == 34); return e_preventDefault(e);} // page up/down
359
- if (mod && ((code == 36 || code == 35) || // ctrl-home/end
360
- mac && (code == 38 || code == 40))) { // cmd-up/down
361
- scrollEnd(code == 36 || code == 38); return e_preventDefault(e);
362
- }
363
- if (mod && code == 65) {selectAll(); return e_preventDefault(e);} // ctrl-a
364
- if (!options.readOnly) {
365
- if (!anyMod && code == 13) {return;} // enter
366
- if (!anyMod && code == 9 && handleTab(e.shiftKey)) return e_preventDefault(e); // tab
367
- if (mod && code == 90) {undo(); return e_preventDefault(e);} // ctrl-z
368
- if (mod && ((e.shiftKey && code == 90) || code == 89)) {redo(); return e_preventDefault(e);} // ctrl-shift-z, ctrl-y
369
- }
370
- if (code == 36) { if (options.smartHome) { smartHome(); return e_preventDefault(e); } }
371
-
372
- // Key id to use in the movementKeys map. We also pass it to
373
- // fastPoll in order to 'self learn'. We need this because
374
- // reducedSelection, the hack where we collapse the selection to
375
- // its start when it is inverted and a movement key is pressed
376
- // (and later restore it again), shouldn't be used for
377
- // non-movement keys.
378
- curKeyId = (mod ? "c" : "") + (e.altKey ? "a" : "") + code;
379
- if (sel.inverted && movementKeys[curKeyId] === true) {
380
- var range = selRange(input);
381
- if (range) {
382
- reducedSelection = {anchor: range.start};
383
- setSelRange(input, range.start, range.start);
384
- }
480
+ var handled = handleKeyBinding(e);
481
+ if (window.opera) {
482
+ lastStoppedKey = handled ? e.keyCode : null;
483
+ // Opera has no cut event... we try to at least catch the key combo
484
+ if (!handled && (mac ? e.metaKey : e.ctrlKey) && e.keyCode == 88)
485
+ replaceSelection("");
385
486
  }
386
- // Don't save the key as a movementkey unless it had a modifier
387
- if (!mod && !e.altKey) curKeyId = null;
388
- fastPoll(curKeyId);
389
- }
390
- function onKeyUp(e) {
391
- if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
392
- if (reducedSelection) {
393
- reducedSelection = null;
394
- updateInput = true;
395
- }
396
- if (e.keyCode == 16) shiftSelecting = null;
397
487
  }
398
488
  function onKeyPress(e) {
489
+ if (window.opera && e.keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
399
490
  if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
491
+ if (window.opera && !e.which && handleKeyBinding(e)) return;
400
492
  if (options.electricChars && mode.electricChars) {
401
493
  var ch = String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode);
402
494
  if (mode.electricChars.indexOf(ch) > -1)
403
- setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 50);
495
+ setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75);
404
496
  }
405
- var code = e.keyCode;
406
- // Re-stop tab and enter. Necessary on some browsers.
407
- if (code == 13) {if (!options.readOnly) handleEnter(); e_preventDefault(e);}
408
- else if (!e.ctrlKey && !e.altKey && !e.metaKey && code == 9 && options.tabMode != "default") e_preventDefault(e);
409
- else fastPoll(curKeyId);
497
+ fastPoll();
498
+ }
499
+ function onKeyUp(e) {
500
+ if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
501
+ if (e.keyCode == 16) shiftSelecting = null;
410
502
  }
411
503
 
412
504
  function onFocus() {
413
- if (options.readOnly == "nocursor") return;
505
+ if (options.readOnly) return;
414
506
  if (!focused) {
415
507
  if (options.onFocus) options.onFocus(instance);
416
508
  focused = true;
417
509
  if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
418
510
  wrapper.className += " CodeMirror-focused";
419
- if (!leaveInputAlone) prepareInput();
511
+ if (!leaveInputAlone) resetInput(true);
420
512
  }
421
513
  slowPoll();
422
514
  restartBlink();
@@ -436,7 +528,7 @@ var CodeMirror = (function() {
436
528
  function updateLines(from, to, newText, selFrom, selTo) {
437
529
  if (history) {
438
530
  var old = [];
439
- for (var i = from.line, e = to.line + 1; i < e; ++i) old.push(lines[i].text);
531
+ doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); });
440
532
  history.addChange(from.line, newText.length, old);
441
533
  while (history.done.length > options.undoDepth) history.done.shift();
442
534
  }
@@ -446,11 +538,11 @@ var CodeMirror = (function() {
446
538
  var change = from.pop();
447
539
  if (change) {
448
540
  var replaced = [], end = change.start + change.added;
449
- for (var i = change.start; i < end; ++i) replaced.push(lines[i].text);
541
+ doc.iter(change.start, end, function(line) { replaced.push(line.text); });
450
542
  to.push({start: change.start, added: change.old.length, old: replaced});
451
543
  var pos = clipPos({line: change.start + change.old.length - 1,
452
544
  ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])});
453
- updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: lines[end-1].text.length}, change.old, pos, pos);
545
+ updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos);
454
546
  updateInput = true;
455
547
  }
456
548
  }
@@ -459,51 +551,77 @@ var CodeMirror = (function() {
459
551
 
460
552
  function updateLinesNoUndo(from, to, newText, selFrom, selTo) {
461
553
  var recomputeMaxLength = false, maxLineLength = maxLine.length;
462
- for (var i = from.line; i <= to.line; ++i) {
463
- if (lines[i].text.length == maxLineLength) {recomputeMaxLength = true; break;}
464
- }
554
+ if (!options.lineWrapping)
555
+ doc.iter(from.line, to.line, function(line) {
556
+ if (line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}
557
+ });
558
+ if (from.line != to.line || newText.length > 1) gutterDirty = true;
465
559
 
466
- var nlines = to.line - from.line, firstLine = lines[from.line], lastLine = lines[to.line];
560
+ var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);
467
561
  // First adjust the line structure, taking some care to leave highlighting intact.
468
- if (firstLine == lastLine) {
562
+ if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "") {
563
+ // This is a whole-line replace. Treated specially to make
564
+ // sure line objects move the way they are supposed to.
565
+ var added = [], prevLine = null;
566
+ if (from.line) {
567
+ prevLine = getLine(from.line - 1);
568
+ prevLine.fixMarkEnds(lastLine);
569
+ } else lastLine.fixMarkStarts();
570
+ for (var i = 0, e = newText.length - 1; i < e; ++i)
571
+ added.push(Line.inheritMarks(newText[i], prevLine));
572
+ if (nlines) doc.remove(from.line, nlines, callbacks);
573
+ if (added.length) doc.insert(from.line, added);
574
+ } else if (firstLine == lastLine) {
469
575
  if (newText.length == 1)
470
576
  firstLine.replace(from.ch, to.ch, newText[0]);
471
577
  else {
472
578
  lastLine = firstLine.split(to.ch, newText[newText.length-1]);
473
- var spliceargs = [from.line + 1, nlines];
474
- firstLine.replace(from.ch, firstLine.text.length, newText[0]);
475
- for (var i = 1, e = newText.length - 1; i < e; ++i) spliceargs.push(new Line(newText[i]));
476
- spliceargs.push(lastLine);
477
- lines.splice.apply(lines, spliceargs);
579
+ firstLine.replace(from.ch, null, newText[0]);
580
+ firstLine.fixMarkEnds(lastLine);
581
+ var added = [];
582
+ for (var i = 1, e = newText.length - 1; i < e; ++i)
583
+ added.push(Line.inheritMarks(newText[i], firstLine));
584
+ added.push(lastLine);
585
+ doc.insert(from.line + 1, added);
478
586
  }
479
- }
480
- else if (newText.length == 1) {
481
- firstLine.replace(from.ch, firstLine.text.length, newText[0] + lastLine.text.slice(to.ch));
482
- lines.splice(from.line + 1, nlines);
483
- }
484
- else {
485
- var spliceargs = [from.line + 1, nlines - 1];
486
- firstLine.replace(from.ch, firstLine.text.length, newText[0]);
487
- lastLine.replace(0, to.ch, newText[newText.length-1]);
488
- for (var i = 1, e = newText.length - 1; i < e; ++i) spliceargs.push(new Line(newText[i]));
489
- lines.splice.apply(lines, spliceargs);
490
- }
491
-
492
-
493
- for (var i = from.line, e = i + newText.length; i < e; ++i) {
494
- var l = lines[i].text;
495
- if (l.length > maxLineLength) {
496
- maxLine = l; maxLineLength = l.length; maxWidth = null;
497
- recomputeMaxLength = false;
498
- }
499
- }
500
- if (recomputeMaxLength) {
501
- maxLineLength = 0; maxLine = ""; maxWidth = null;
502
- for (var i = 0, e = lines.length; i < e; ++i) {
503
- var l = lines[i].text;
587
+ } else if (newText.length == 1) {
588
+ firstLine.replace(from.ch, null, newText[0]);
589
+ lastLine.replace(null, to.ch, "");
590
+ firstLine.append(lastLine);
591
+ doc.remove(from.line + 1, nlines, callbacks);
592
+ } else {
593
+ var added = [];
594
+ firstLine.replace(from.ch, null, newText[0]);
595
+ lastLine.replace(null, to.ch, newText[newText.length-1]);
596
+ firstLine.fixMarkEnds(lastLine);
597
+ for (var i = 1, e = newText.length - 1; i < e; ++i)
598
+ added.push(Line.inheritMarks(newText[i], firstLine));
599
+ if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks);
600
+ doc.insert(from.line + 1, added);
601
+ }
602
+ if (options.lineWrapping) {
603
+ var perLine = scroller.clientWidth / charWidth() - 3;
604
+ doc.iter(from.line, from.line + newText.length, function(line) {
605
+ if (line.hidden) return;
606
+ var guess = Math.ceil(line.text.length / perLine) || 1;
607
+ if (guess != line.height) updateLineHeight(line, guess);
608
+ });
609
+ } else {
610
+ doc.iter(from.line, i + newText.length, function(line) {
611
+ var l = line.text;
504
612
  if (l.length > maxLineLength) {
505
- maxLineLength = l.length; maxLine = l;
613
+ maxLine = l; maxLineLength = l.length; maxWidth = null;
614
+ recomputeMaxLength = false;
506
615
  }
616
+ });
617
+ if (recomputeMaxLength) {
618
+ maxLineLength = 0; maxLine = ""; maxWidth = null;
619
+ doc.iter(0, doc.size, function(line) {
620
+ var l = line.text;
621
+ if (l.length > maxLineLength) {
622
+ maxLineLength = l.length; maxLine = l;
623
+ }
624
+ });
507
625
  }
508
626
  }
509
627
 
@@ -515,24 +633,25 @@ var CodeMirror = (function() {
515
633
  if (task < from.line) newWork.push(task);
516
634
  else if (task > to.line) newWork.push(task + lendiff);
517
635
  }
518
- if (newText.length < 5) {
519
- highlightLines(from.line, from.line + newText.length);
520
- newWork.push(from.line + newText.length);
521
- } else {
522
- newWork.push(from.line);
523
- }
636
+ var hlEnd = from.line + Math.min(newText.length, 500);
637
+ highlightLines(from.line, hlEnd);
638
+ newWork.push(hlEnd);
524
639
  work = newWork;
525
640
  startWorker(100);
526
641
  // Remember that these lines changed, for updating the display
527
642
  changes.push({from: from.line, to: to.line + 1, diff: lendiff});
528
- textChanged = {from: from, to: to, text: newText};
643
+ var changeObj = {from: from, to: to, text: newText};
644
+ if (textChanged) {
645
+ for (var cur = textChanged; cur.next; cur = cur.next) {}
646
+ cur.next = changeObj;
647
+ } else textChanged = changeObj;
529
648
 
530
649
  // Update the selection
531
650
  function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;}
532
651
  setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line));
533
652
 
534
653
  // Make sure the scroll-size div has the correct height.
535
- code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px";
654
+ code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px";
536
655
  }
537
656
 
538
657
  function replaceRange(code, from, to) {
@@ -570,10 +689,10 @@ var CodeMirror = (function() {
570
689
 
571
690
  function getRange(from, to) {
572
691
  var l1 = from.line, l2 = to.line;
573
- if (l1 == l2) return lines[l1].text.slice(from.ch, to.ch);
574
- var code = [lines[l1].text.slice(from.ch)];
575
- for (var i = l1 + 1; i < l2; ++i) code.push(lines[i].text);
576
- code.push(lines[l2].text.slice(0, to.ch));
692
+ if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch);
693
+ var code = [getLine(l1).text.slice(from.ch)];
694
+ doc.iter(l1 + 1, l2, function(line) { code.push(line.text); });
695
+ code.push(getLine(l2).text.slice(0, to.ch));
577
696
  return code.join("\n");
578
697
  }
579
698
  function getSelection() {
@@ -583,131 +702,74 @@ var CodeMirror = (function() {
583
702
  var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll
584
703
  function slowPoll() {
585
704
  if (pollingFast) return;
586
- poll.set(2000, function() {
705
+ poll.set(options.pollInterval, function() {
587
706
  startOperation();
588
707
  readInput();
589
708
  if (focused) slowPoll();
590
709
  endOperation();
591
710
  });
592
711
  }
593
- function fastPoll(keyId) {
712
+ function fastPoll() {
594
713
  var missed = false;
595
714
  pollingFast = true;
596
715
  function p() {
597
716
  startOperation();
598
717
  var changed = readInput();
599
- if (changed && keyId) {
600
- if (changed == "moved" && movementKeys[keyId] == null) movementKeys[keyId] = true;
601
- if (changed == "changed") movementKeys[keyId] = false;
602
- }
603
- if (!changed && !missed) {missed = true; poll.set(80, p);}
718
+ if (!changed && !missed) {missed = true; poll.set(60, p);}
604
719
  else {pollingFast = false; slowPoll();}
605
720
  endOperation();
606
721
  }
607
722
  poll.set(20, p);
608
723
  }
609
724
 
610
- // Inspects the textarea, compares its state (content, selection)
611
- // to the data in the editing variable, and updates the editor
612
- // content or cursor if something changed.
725
+ // Previnput is a hack to work with IME. If we reset the textarea
726
+ // on every change, that breaks IME. So we look for changes
727
+ // compared to the previous content instead. (Modern browsers have
728
+ // events that indicate IME taking place, but these are not widely
729
+ // supported or compatible enough yet to rely on.)
730
+ var prevInput = "";
613
731
  function readInput() {
614
- if (leaveInputAlone || !focused) return;
615
- var changed = false, text = input.value, sr = selRange(input);
616
- if (!sr) return false;
617
- var changed = editing.text != text, rs = reducedSelection;
618
- var moved = changed || sr.start != editing.start || sr.end != (rs ? editing.start : editing.end);
619
- if (!moved && !rs) return false;
620
- if (changed) {
621
- shiftSelecting = reducedSelection = null;
622
- if (options.readOnly) {updateInput = true; return "changed";}
623
- }
624
-
625
- // Compute selection start and end based on start/end offsets in textarea
626
- function computeOffset(n, startLine) {
627
- var pos = 0;
628
- for (;;) {
629
- var found = text.indexOf("\n", pos);
630
- if (found == -1 || (text.charAt(found-1) == "\r" ? found - 1 : found) >= n)
631
- return {line: startLine, ch: n - pos};
632
- ++startLine;
633
- pos = found + 1;
634
- }
635
- }
636
- var from = computeOffset(sr.start, editing.from),
637
- to = computeOffset(sr.end, editing.from);
638
- // Here we have to take the reducedSelection hack into account,
639
- // so that you can, for example, press shift-up at the start of
640
- // your selection and have the right thing happen.
641
- if (rs) {
642
- var head = sr.start == rs.anchor ? to : from;
643
- var tail = shiftSelecting ? sel.to : sr.start == rs.anchor ? from : to;
644
- if (sel.inverted = posLess(head, tail)) { from = head; to = tail; }
645
- else { reducedSelection = null; from = tail; to = head; }
646
- }
647
-
648
- // In some cases (cursor on same line as before), we don't have
649
- // to update the textarea content at all.
650
- if (from.line == to.line && from.line == sel.from.line && from.line == sel.to.line && !shiftSelecting)
651
- updateInput = false;
652
-
653
- // Magic mess to extract precise edited range from the changed
654
- // string.
655
- if (changed) {
656
- var start = 0, end = text.length, len = Math.min(end, editing.text.length);
657
- var c, line = editing.from, nl = -1;
658
- while (start < len && (c = text.charAt(start)) == editing.text.charAt(start)) {
659
- ++start;
660
- if (c == "\n") {line++; nl = start;}
661
- }
662
- var ch = nl > -1 ? start - nl : start, endline = editing.to - 1, edend = editing.text.length;
663
- for (;;) {
664
- c = editing.text.charAt(edend);
665
- if (text.charAt(end) != c) {++end; ++edend; break;}
666
- if (c == "\n") endline--;
667
- if (edend <= start || end <= start) break;
668
- --end; --edend;
669
- }
670
- var nl = editing.text.lastIndexOf("\n", edend - 1), endch = nl == -1 ? edend : edend - nl - 1;
671
- updateLines({line: line, ch: ch}, {line: endline, ch: endch}, splitLines(text.slice(start, end)), from, to);
672
- if (line != endline || from.line != line) updateInput = true;
673
- }
674
- else setSelection(from, to);
675
-
676
- editing.text = text; editing.start = sr.start; editing.end = sr.end;
677
- return changed ? "changed" : moved ? "moved" : false;
732
+ if (leaveInputAlone || !focused || hasSelection(input)) return false;
733
+ var text = input.value;
734
+ if (text == prevInput) return false;
735
+ shiftSelecting = null;
736
+ var same = 0, l = Math.min(prevInput.length, text.length);
737
+ while (same < l && prevInput[same] == text[same]) ++same;
738
+ if (same < prevInput.length)
739
+ sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)};
740
+ else if (overwrite && posEq(sel.from, sel.to))
741
+ sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))};
742
+ replaceSelection(text.slice(same), "end");
743
+ prevInput = text;
744
+ return true;
678
745
  }
679
-
680
- // Set the textarea content and selection range to match the
681
- // editor state.
682
- function prepareInput() {
683
- var text = [];
684
- var from = Math.max(0, sel.from.line - 1), to = Math.min(lines.length, sel.to.line + 2);
685
- for (var i = from; i < to; ++i) text.push(lines[i].text);
686
- text = input.value = text.join(lineSep);
687
- var startch = sel.from.ch, endch = sel.to.ch;
688
- for (var i = from; i < sel.from.line; ++i)
689
- startch += lineSep.length + lines[i].text.length;
690
- for (var i = from; i < sel.to.line; ++i)
691
- endch += lineSep.length + lines[i].text.length;
692
- editing = {text: text, from: from, to: to, start: startch, end: endch};
693
- setSelRange(input, startch, reducedSelection ? startch : endch);
746
+ function resetInput(user) {
747
+ if (!posEq(sel.from, sel.to)) {
748
+ prevInput = "";
749
+ input.value = getSelection();
750
+ input.select();
751
+ } else if (user) prevInput = input.value = "";
694
752
  }
753
+
695
754
  function focusInput() {
696
- if (options.readOnly != "nocursor") input.focus();
755
+ if (!options.readOnly) input.focus();
697
756
  }
698
757
 
699
758
  function scrollEditorIntoView() {
700
759
  if (!cursor.getBoundingClientRect) return;
701
760
  var rect = cursor.getBoundingClientRect();
702
- var winH = window.innerHeight || document.body.offsetHeight || document.documentElement.offsetHeight;
761
+ // IE returns bogus coordinates when the instance sits inside of an iframe and the cursor is hidden
762
+ if (ie && rect.top == rect.bottom) return;
763
+ var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
703
764
  if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView();
704
765
  }
705
766
  function scrollCursorIntoView() {
706
767
  var cursor = localCoords(sel.inverted ? sel.from : sel.to);
707
- return scrollIntoView(cursor.x, cursor.y, cursor.x, cursor.yBot);
768
+ var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x;
769
+ return scrollIntoView(x, cursor.y, x, cursor.yBot);
708
770
  }
709
771
  function scrollIntoView(x1, y1, x2, y2) {
710
- var pl = paddingLeft(), pt = paddingTop(), lh = lineHeight();
772
+ var pl = paddingLeft(), pt = paddingTop(), lh = textHeight();
711
773
  y1 += pt; y2 += pt; x1 += pl; x2 += pl;
712
774
  var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true;
713
775
  if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;}
@@ -720,7 +782,7 @@ var CodeMirror = (function() {
720
782
  scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw);
721
783
  scrolled = true;
722
784
  }
723
- else if (x2 > screenw + screenleft) {
785
+ else if (x2 > screenw + screenleft - 3) {
724
786
  scroller.scrollLeft = x2 + 10 - screenw;
725
787
  scrolled = true;
726
788
  if (x2 > code.clientWidth) result = false;
@@ -730,32 +792,107 @@ var CodeMirror = (function() {
730
792
  }
731
793
 
732
794
  function visibleLines() {
733
- var lh = lineHeight(), top = scroller.scrollTop - paddingTop();
734
- return {from: Math.min(lines.length, Math.max(0, Math.floor(top / lh))),
735
- to: Math.min(lines.length, Math.ceil((top + scroller.clientHeight) / lh))};
795
+ var lh = textHeight(), top = scroller.scrollTop - paddingTop();
796
+ var from_height = Math.max(0, Math.floor(top / lh));
797
+ var to_height = Math.ceil((top + scroller.clientHeight) / lh);
798
+ return {from: lineAtHeight(doc, from_height),
799
+ to: lineAtHeight(doc, to_height)};
736
800
  }
737
801
  // Uses a set of changes plus the current scroll position to
738
802
  // determine which DOM updates have to be made, and makes the
739
803
  // updates.
740
- function updateDisplay(changes) {
804
+ function updateDisplay(changes, suppressCallback) {
741
805
  if (!scroller.clientWidth) {
742
- showingFrom = showingTo = 0;
806
+ showingFrom = showingTo = displayOffset = 0;
743
807
  return;
744
808
  }
745
- // First create a range of theoretically intact lines, and punch
746
- // holes in that using the change info.
747
- var intact = changes === true ? [] : [{from: showingFrom, to: showingTo, domStart: 0}];
809
+ // Compute the new visible window
810
+ var visible = visibleLines();
811
+ // Bail out if the visible area is already rendered and nothing changed.
812
+ if (changes !== true && changes.length == 0 && visible.from >= showingFrom && visible.to <= showingTo) return;
813
+ var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100);
814
+ if (showingFrom < from && from - showingFrom < 20) from = showingFrom;
815
+ if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo);
816
+
817
+ // Create a range of theoretically intact lines, and punch holes
818
+ // in that using the change info.
819
+ var intact = changes === true ? [] :
820
+ computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes);
821
+ // Clip off the parts that won't be visible
822
+ var intactLines = 0;
823
+ for (var i = 0; i < intact.length; ++i) {
824
+ var range = intact[i];
825
+ if (range.from < from) {range.domStart += (from - range.from); range.from = from;}
826
+ if (range.to > to) range.to = to;
827
+ if (range.from >= range.to) intact.splice(i--, 1);
828
+ else intactLines += range.to - range.from;
829
+ }
830
+ if (intactLines == to - from) return;
831
+ intact.sort(function(a, b) {return a.domStart - b.domStart;});
832
+
833
+ var th = textHeight(), gutterDisplay = gutter.style.display;
834
+ lineDiv.style.display = gutter.style.display = "none";
835
+ patchDisplay(from, to, intact);
836
+ lineDiv.style.display = "";
837
+
838
+ // Position the mover div to align with the lines it's supposed
839
+ // to be showing (which will cover the visible display)
840
+ var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th;
841
+ // This is just a bogus formula that detects when the editor is
842
+ // resized or the font size changes.
843
+ if (different) lastSizeC = scroller.clientHeight + th;
844
+ showingFrom = from; showingTo = to;
845
+ displayOffset = heightAtLine(doc, from);
846
+ mover.style.top = (displayOffset * th) + "px";
847
+ code.style.height = (doc.height * th + 2 * paddingTop()) + "px";
848
+
849
+ // Since this is all rather error prone, it is honoured with the
850
+ // only assertion in the whole file.
851
+ if (lineDiv.childNodes.length != showingTo - showingFrom)
852
+ throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) +
853
+ " nodes=" + lineDiv.childNodes.length);
854
+
855
+ if (options.lineWrapping) {
856
+ maxWidth = scroller.clientWidth;
857
+ var curNode = lineDiv.firstChild;
858
+ doc.iter(showingFrom, showingTo, function(line) {
859
+ if (!line.hidden) {
860
+ var height = Math.round(curNode.offsetHeight / th) || 1;
861
+ if (line.height != height) {updateLineHeight(line, height); gutterDirty = true;}
862
+ }
863
+ curNode = curNode.nextSibling;
864
+ });
865
+ } else {
866
+ if (maxWidth == null) maxWidth = stringWidth(maxLine);
867
+ if (maxWidth > scroller.clientWidth) {
868
+ lineSpace.style.width = maxWidth + "px";
869
+ // Needed to prevent odd wrapping/hiding of widgets placed in here.
870
+ code.style.width = "";
871
+ code.style.width = scroller.scrollWidth + "px";
872
+ } else {
873
+ lineSpace.style.width = code.style.width = "";
874
+ }
875
+ }
876
+ gutter.style.display = gutterDisplay;
877
+ if (different || gutterDirty) updateGutter();
878
+ updateCursor();
879
+ if (!suppressCallback && options.onUpdate) options.onUpdate(instance);
880
+ return true;
881
+ }
882
+
883
+ function computeIntact(intact, changes) {
748
884
  for (var i = 0, l = changes.length || 0; i < l; ++i) {
749
885
  var change = changes[i], intact2 = [], diff = change.diff || 0;
750
886
  for (var j = 0, l2 = intact.length; j < l2; ++j) {
751
887
  var range = intact[j];
752
- if (change.to <= range.from)
753
- intact2.push({from: range.from + diff, to: range.to + diff, domStart: range.domStart});
754
- else if (range.to <= change.from)
888
+ if (change.to <= range.from && change.diff)
889
+ intact2.push({from: range.from + diff, to: range.to + diff,
890
+ domStart: range.domStart});
891
+ else if (change.to <= range.from || change.from >= range.to)
755
892
  intact2.push(range);
756
893
  else {
757
894
  if (change.from > range.from)
758
- intact2.push({from: range.from, to: change.from, domStart: range.domStart})
895
+ intact2.push({from: range.from, to: change.from, domStart: range.domStart});
759
896
  if (change.to < range.to)
760
897
  intact2.push({from: change.to + diff, to: range.to + diff,
761
898
  domStart: range.domStart + (change.to - range.from)});
@@ -763,148 +900,75 @@ var CodeMirror = (function() {
763
900
  }
764
901
  intact = intact2;
765
902
  }
903
+ return intact;
904
+ }
766
905
 
767
- // Then, determine which lines we'd want to see, and which
768
- // updates have to be made to get there.
769
- var visible = visibleLines();
770
- var from = Math.min(showingFrom, Math.max(visible.from - 3, 0)),
771
- to = Math.min(lines.length, Math.max(showingTo, visible.to + 3)),
772
- updates = [], domPos = 0, domEnd = showingTo - showingFrom, pos = from, changedLines = 0;
773
-
774
- for (var i = 0, l = intact.length; i < l; ++i) {
775
- var range = intact[i];
776
- if (range.to <= from) continue;
777
- if (range.from >= to) break;
778
- if (range.domStart > domPos || range.from > pos) {
779
- updates.push({from: pos, to: range.from, domSize: range.domStart - domPos, domStart: domPos});
780
- changedLines += range.from - pos;
906
+ function patchDisplay(from, to, intact) {
907
+ // The first pass removes the DOM nodes that aren't intact.
908
+ if (!intact.length) lineDiv.innerHTML = "";
909
+ else {
910
+ function killNode(node) {
911
+ var tmp = node.nextSibling;
912
+ node.parentNode.removeChild(node);
913
+ return tmp;
781
914
  }
782
- pos = range.to;
783
- domPos = range.domStart + (range.to - range.from);
784
- }
785
- if (domPos != domEnd || pos != to) {
786
- changedLines += Math.abs(to - pos);
787
- updates.push({from: pos, to: to, domSize: domEnd - domPos, domStart: domPos});
788
- if (to - pos != domEnd - domPos) gutterDirty = true;
789
- }
790
-
791
- if (!updates.length) return;
792
- lineDiv.style.display = "none";
793
- // If more than 30% of the screen needs update, just do a full
794
- // redraw (which is quicker than patching)
795
- if (changedLines > (visible.to - visible.from) * .3)
796
- refreshDisplay(from = Math.max(visible.from - 10, 0), to = Math.min(visible.to + 7, lines.length));
797
- // Otherwise, only update the stuff that needs updating.
798
- else
799
- patchDisplay(updates);
800
- lineDiv.style.display = "";
801
-
802
- // Position the mover div to align with the lines it's supposed
803
- // to be showing (which will cover the visible display)
804
- var different = from != showingFrom || to != showingTo || lastHeight != scroller.clientHeight;
805
- showingFrom = from; showingTo = to;
806
- mover.style.top = (from * lineHeight()) + "px";
807
- if (different) {
808
- lastHeight = scroller.clientHeight;
809
- code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px";
810
- }
811
- if (different || gutterDirty) updateGutter();
812
-
813
- if (maxWidth == null) maxWidth = stringWidth(maxLine);
814
- if (maxWidth > scroller.clientWidth) {
815
- lineSpace.style.width = maxWidth + "px";
816
- // Needed to prevent odd wrapping/hiding of widgets placed in here.
817
- code.style.width = "";
818
- code.style.width = scroller.scrollWidth + "px";
819
- } else {
820
- lineSpace.style.width = code.style.width = "";
915
+ var domPos = 0, curNode = lineDiv.firstChild, n;
916
+ for (var i = 0; i < intact.length; ++i) {
917
+ var cur = intact[i];
918
+ while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;}
919
+ for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;}
920
+ }
921
+ while (curNode) curNode = killNode(curNode);
821
922
  }
822
-
823
- // Since this is all rather error prone, it is honoured with the
824
- // only assertion in the whole file.
825
- if (lineDiv.childNodes.length != showingTo - showingFrom)
826
- throw new Error("BAD PATCH! " + JSON.stringify(updates) + " size=" + (showingTo - showingFrom) +
827
- " nodes=" + lineDiv.childNodes.length);
828
- updateCursor();
829
- }
830
-
831
- function refreshDisplay(from, to) {
832
- var html = [], start = {line: from, ch: 0}, inSel = posLess(sel.from, start) && !posLess(sel.to, start);
833
- for (var i = from; i < to; ++i) {
923
+ // This pass fills in the lines that actually changed.
924
+ var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from;
925
+ var sfrom = sel.from.line, sto = sel.to.line, inSel = sfrom < from && sto >= from;
926
+ var scratch = targetDocument.createElement("div"), newElt;
927
+ doc.iter(from, to, function(line) {
834
928
  var ch1 = null, ch2 = null;
835
929
  if (inSel) {
836
930
  ch1 = 0;
837
- if (sel.to.line == i) {inSel = false; ch2 = sel.to.ch;}
838
- }
839
- else if (sel.from.line == i) {
840
- if (sel.to.line == i) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
931
+ if (sto == j) {inSel = false; ch2 = sel.to.ch;}
932
+ } else if (sfrom == j) {
933
+ if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
841
934
  else {inSel = true; ch1 = sel.from.ch;}
842
935
  }
843
- html.push(lines[i].getHTML(ch1, ch2, true));
844
- }
845
- lineDiv.innerHTML = html.join("");
846
- }
847
- function patchDisplay(updates) {
848
- // Slightly different algorithm for IE (badInnerHTML), since
849
- // there .innerHTML on PRE nodes is dumb, and discards
850
- // whitespace.
851
- var sfrom = sel.from.line, sto = sel.to.line, off = 0,
852
- scratch = badInnerHTML && targetDocument.createElement("div");
853
- for (var i = 0, e = updates.length; i < e; ++i) {
854
- var rec = updates[i];
855
- var extra = (rec.to - rec.from) - rec.domSize;
856
- var nodeAfter = lineDiv.childNodes[rec.domStart + rec.domSize + off] || null;
857
- if (badInnerHTML)
858
- for (var j = Math.max(-extra, rec.domSize); j > 0; --j)
859
- lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
860
- else if (extra) {
861
- for (var j = Math.max(0, extra); j > 0; --j)
862
- lineDiv.insertBefore(targetDocument.createElement("pre"), nodeAfter);
863
- for (var j = Math.max(0, -extra); j > 0; --j)
864
- lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
865
- }
866
- var node = lineDiv.childNodes[rec.domStart + off], inSel = sfrom < rec.from && sto >= rec.from;
867
- for (var j = rec.from; j < rec.to; ++j) {
868
- var ch1 = null, ch2 = null;
869
- if (inSel) {
870
- ch1 = 0;
871
- if (sto == j) {inSel = false; ch2 = sel.to.ch;}
872
- }
873
- else if (sfrom == j) {
874
- if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
875
- else {inSel = true; ch1 = sel.from.ch;}
876
- }
877
- if (badInnerHTML) {
878
- scratch.innerHTML = lines[j].getHTML(ch1, ch2, true);
879
- lineDiv.insertBefore(scratch.firstChild, nodeAfter);
880
- }
881
- else {
882
- node.innerHTML = lines[j].getHTML(ch1, ch2, false);
883
- node.className = lines[j].className || "";
884
- node = node.nextSibling;
885
- }
936
+ if (nextIntact && nextIntact.to == j) nextIntact = intact.shift();
937
+ if (!nextIntact || nextIntact.from > j) {
938
+ if (line.hidden) scratch.innerHTML = "<pre></pre>";
939
+ else scratch.innerHTML = line.getHTML(ch1, ch2, true, tabText);
940
+ lineDiv.insertBefore(scratch.firstChild, curNode);
941
+ } else {
942
+ curNode = curNode.nextSibling;
886
943
  }
887
- off += extra;
888
- }
944
+ ++j;
945
+ });
889
946
  }
890
947
 
891
948
  function updateGutter() {
892
949
  if (!options.gutter && !options.lineNumbers) return;
893
950
  var hText = mover.offsetHeight, hEditor = scroller.clientHeight;
894
951
  gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
895
- var html = [];
896
- for (var i = showingFrom; i < Math.max(showingTo, showingFrom + 1); ++i) {
897
- var marker = lines[i].gutterMarker;
898
- var text = options.lineNumbers ? i + options.firstLineNumber : null;
899
- if (marker && marker.text)
900
- text = marker.text.replace("%N%", text != null ? text : "");
901
- else if (text == null)
902
- text = "\u00a0";
903
- html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text, "</pre>");
904
- }
952
+ var html = [], i = showingFrom;
953
+ doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) {
954
+ if (line.hidden) {
955
+ html.push("<pre></pre>");
956
+ } else {
957
+ var marker = line.gutterMarker;
958
+ var text = options.lineNumbers ? i + options.firstLineNumber : null;
959
+ if (marker && marker.text)
960
+ text = marker.text.replace("%N%", text != null ? text : "");
961
+ else if (text == null)
962
+ text = "\u00a0";
963
+ html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text);
964
+ for (var j = 1; j < line.height; ++j) html.push("<br/>&#160;");
965
+ html.push("</pre>");
966
+ }
967
+ ++i;
968
+ });
905
969
  gutter.style.display = "none";
906
970
  gutterText.innerHTML = html.join("");
907
- var minwidth = String(lines.length).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = "";
971
+ var minwidth = String(doc.size).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = "";
908
972
  while (val.length + pad.length < minwidth) pad += "\u00a0";
909
973
  if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild);
910
974
  gutter.style.display = "";
@@ -912,19 +976,23 @@ var CodeMirror = (function() {
912
976
  gutterDirty = false;
913
977
  }
914
978
  function updateCursor() {
915
- var head = sel.inverted ? sel.from : sel.to, lh = lineHeight();
916
- var x = charX(head.line, head.ch);
917
- var top = head.line * lh - scroller.scrollTop;
918
- inputDiv.style.top = Math.max(Math.min(top, scroller.offsetHeight), 0) + "px";
919
- inputDiv.style.left = (x - scroller.scrollLeft) + "px";
979
+ var head = sel.inverted ? sel.from : sel.to, lh = textHeight();
980
+ var pos = localCoords(head, true);
981
+ var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv);
982
+ inputDiv.style.top = (pos.y + lineOff.top - wrapOff.top) + "px";
983
+ inputDiv.style.left = (pos.x + lineOff.left - wrapOff.left) + "px";
920
984
  if (posEq(sel.from, sel.to)) {
921
- cursor.style.top = (head.line - showingFrom) * lh + "px";
922
- cursor.style.left = x + "px";
985
+ cursor.style.top = pos.y + "px";
986
+ cursor.style.left = (options.lineWrapping ? Math.min(pos.x, lineSpace.offsetWidth) : pos.x) + "px";
923
987
  cursor.style.display = "";
924
988
  }
925
989
  else cursor.style.display = "none";
926
990
  }
927
991
 
992
+ function setShift(val) {
993
+ if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
994
+ else shiftSelecting = null;
995
+ }
928
996
  function setSelectionUser(from, to) {
929
997
  var sh = shiftSelecting && clipPos(shiftSelecting);
930
998
  if (sh) {
@@ -932,14 +1000,21 @@ var CodeMirror = (function() {
932
1000
  else if (posLess(to, sh)) to = sh;
933
1001
  }
934
1002
  setSelection(from, to);
1003
+ userSelChange = true;
935
1004
  }
936
1005
  // Update the selection. Last two args are only used by
937
1006
  // updateLines, since they have to be expressed in the line
938
1007
  // numbers before the update.
939
1008
  function setSelection(from, to, oldFrom, oldTo) {
1009
+ goalColumn = null;
1010
+ if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
940
1011
  if (posEq(sel.from, from) && posEq(sel.to, to)) return;
941
1012
  if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
942
1013
 
1014
+ // Skip over hidden lines.
1015
+ if (from.line != oldFrom) from = skipHidden(from, oldFrom, sel.from.ch);
1016
+ if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch);
1017
+
943
1018
  if (posEq(from, to)) sel.inverted = false;
944
1019
  else if (posEq(from, sel.to)) sel.inverted = false;
945
1020
  else if (posEq(to, sel.from)) sel.inverted = true;
@@ -947,7 +1022,6 @@ var CodeMirror = (function() {
947
1022
  // Some ugly logic used to only mark the lines that actually did
948
1023
  // see a change in selection as changed, rather than the whole
949
1024
  // selected range.
950
- if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
951
1025
  if (posEq(from, to)) {
952
1026
  if (!posEq(sel.from, sel.to))
953
1027
  changes.push({from: oldFrom, to: oldTo + 1});
@@ -972,90 +1046,120 @@ var CodeMirror = (function() {
972
1046
  sel.from = from; sel.to = to;
973
1047
  selectionChanged = true;
974
1048
  }
1049
+ function skipHidden(pos, oldLine, oldCh) {
1050
+ function getNonHidden(dir) {
1051
+ var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1;
1052
+ while (lNo != end) {
1053
+ var line = getLine(lNo);
1054
+ if (!line.hidden) {
1055
+ var ch = pos.ch;
1056
+ if (ch > oldCh || ch > line.text.length) ch = line.text.length;
1057
+ return {line: lNo, ch: ch};
1058
+ }
1059
+ lNo += dir;
1060
+ }
1061
+ }
1062
+ var line = getLine(pos.line);
1063
+ if (!line.hidden) return pos;
1064
+ if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1);
1065
+ else return getNonHidden(-1) || getNonHidden(1);
1066
+ }
975
1067
  function setCursor(line, ch, user) {
976
1068
  var pos = clipPos({line: line, ch: ch || 0});
977
1069
  (user ? setSelectionUser : setSelection)(pos, pos);
978
1070
  }
979
1071
 
980
- function clipLine(n) {return Math.max(0, Math.min(n, lines.length-1));}
1072
+ function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));}
981
1073
  function clipPos(pos) {
982
1074
  if (pos.line < 0) return {line: 0, ch: 0};
983
- if (pos.line >= lines.length) return {line: lines.length-1, ch: lines[lines.length-1].text.length};
984
- var ch = pos.ch, linelen = lines[pos.line].text.length;
1075
+ if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length};
1076
+ var ch = pos.ch, linelen = getLine(pos.line).text.length;
985
1077
  if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};
986
1078
  else if (ch < 0) return {line: pos.line, ch: 0};
987
1079
  else return pos;
988
1080
  }
989
1081
 
990
- function scrollPage(down) {
991
- var linesPerPage = Math.floor(scroller.clientHeight / lineHeight()), head = sel.inverted ? sel.from : sel.to;
992
- setCursor(head.line + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1)), head.ch, true);
1082
+ function findPosH(dir, unit) {
1083
+ var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch;
1084
+ var lineObj = getLine(line);
1085
+ function findNextLine() {
1086
+ for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) {
1087
+ var lo = getLine(l);
1088
+ if (!lo.hidden) { line = l; lineObj = lo; return true; }
1089
+ }
1090
+ }
1091
+ function moveOnce(boundToLine) {
1092
+ if (ch == (dir < 0 ? 0 : lineObj.text.length)) {
1093
+ if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0;
1094
+ else return false;
1095
+ } else ch += dir;
1096
+ return true;
1097
+ }
1098
+ if (unit == "char") moveOnce();
1099
+ else if (unit == "column") moveOnce(true);
1100
+ else if (unit == "word") {
1101
+ var sawWord = false;
1102
+ for (;;) {
1103
+ if (dir < 0) if (!moveOnce()) break;
1104
+ if (isWordChar(lineObj.text.charAt(ch))) sawWord = true;
1105
+ else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;}
1106
+ if (dir > 0) if (!moveOnce()) break;
1107
+ }
1108
+ }
1109
+ return {line: line, ch: ch};
1110
+ }
1111
+ function moveH(dir, unit) {
1112
+ var pos = dir < 0 ? sel.from : sel.to;
1113
+ if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit);
1114
+ setCursor(pos.line, pos.ch, true);
993
1115
  }
994
- function scrollEnd(top) {
995
- var pos = top ? {line: 0, ch: 0} : {line: lines.length - 1, ch: lines[lines.length-1].text.length};
996
- setSelectionUser(pos, pos);
1116
+ function deleteH(dir, unit) {
1117
+ if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to);
1118
+ else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to);
1119
+ else replaceRange("", sel.from, findPosH(dir, unit));
1120
+ userSelChange = true;
997
1121
  }
998
- function selectAll() {
999
- var endLine = lines.length - 1;
1000
- setSelection({line: 0, ch: 0}, {line: endLine, ch: lines[endLine].text.length});
1122
+ var goalColumn = null;
1123
+ function moveV(dir, unit) {
1124
+ var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true);
1125
+ if (goalColumn != null) pos.x = goalColumn;
1126
+ if (unit == "page") dist = scroller.clientHeight;
1127
+ else if (unit == "line") dist = textHeight();
1128
+ var target = coordsChar(pos.x, pos.y + dist * dir + 2);
1129
+ setCursor(target.line, target.ch, true);
1130
+ goalColumn = pos.x;
1001
1131
  }
1132
+
1002
1133
  function selectWordAt(pos) {
1003
- var line = lines[pos.line].text;
1134
+ var line = getLine(pos.line).text;
1004
1135
  var start = pos.ch, end = pos.ch;
1005
- while (start > 0 && /\w/.test(line.charAt(start - 1))) --start;
1006
- while (end < line.length && /\w/.test(line.charAt(end))) ++end;
1136
+ while (start > 0 && isWordChar(line.charAt(start - 1))) --start;
1137
+ while (end < line.length && isWordChar(line.charAt(end))) ++end;
1007
1138
  setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end});
1008
1139
  }
1009
1140
  function selectLine(line) {
1010
- setSelectionUser({line: line, ch: 0}, {line: line, ch: lines[line].text.length});
1141
+ setSelectionUser({line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
1011
1142
  }
1012
- function handleEnter() {
1013
- replaceSelection("\n", "end");
1014
- if (options.enterMode != "flat")
1015
- indentLine(sel.from.line, options.enterMode == "keep" ? "prev" : "smart");
1016
- }
1017
- function handleTab(shift) {
1018
- function indentSelected(mode) {
1019
- if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);
1020
- var e = sel.to.line - (sel.to.ch ? 0 : 1);
1021
- for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);
1022
- }
1023
- shiftSelecting = null;
1024
- switch (options.tabMode) {
1025
- case "default":
1026
- return false;
1027
- case "indent":
1028
- indentSelected("smart");
1029
- break;
1030
- case "classic":
1031
- if (posEq(sel.from, sel.to)) {
1032
- if (shift) indentLine(sel.from.line, "smart");
1033
- else replaceSelection("\t", "end");
1034
- break;
1035
- }
1036
- case "shift":
1037
- indentSelected(shift ? "subtract" : "add");
1038
- break;
1039
- }
1040
- return true;
1041
- }
1042
- function smartHome() {
1043
- var firstNonWS = Math.max(0, lines[sel.from.line].text.search(/\S/));
1044
- setCursor(sel.from.line, sel.from.ch <= firstNonWS && sel.from.ch ? 0 : firstNonWS, true);
1143
+ function indentSelected(mode) {
1144
+ if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);
1145
+ var e = sel.to.line - (sel.to.ch ? 0 : 1);
1146
+ for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);
1045
1147
  }
1046
1148
 
1047
1149
  function indentLine(n, how) {
1150
+ if (!how) how = "add";
1048
1151
  if (how == "smart") {
1049
1152
  if (!mode.indent) how = "prev";
1050
1153
  else var state = getStateBefore(n);
1051
1154
  }
1052
1155
 
1053
- var line = lines[n], curSpace = line.indentation(), curSpaceString = line.text.match(/^\s*/)[0], indentation;
1156
+ var line = getLine(n), curSpace = line.indentation(options.tabSize),
1157
+ curSpaceString = line.text.match(/^\s*/)[0], indentation;
1054
1158
  if (how == "prev") {
1055
- if (n) indentation = lines[n-1].indentation();
1159
+ if (n) indentation = getLine(n-1).indentation(options.tabSize);
1056
1160
  else indentation = 0;
1057
1161
  }
1058
- else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length));
1162
+ else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text);
1059
1163
  else if (how == "add") indentation = curSpace + options.indentUnit;
1060
1164
  else if (how == "subtract") indentation = curSpace - options.indentUnit;
1061
1165
  indentation = Math.max(0, indentation);
@@ -1068,7 +1172,7 @@ var CodeMirror = (function() {
1068
1172
  else {
1069
1173
  var indentString = "", pos = 0;
1070
1174
  if (options.indentWithTabs)
1071
- for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";}
1175
+ for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";}
1072
1176
  while (pos < indentation) {++pos; indentString += " ";}
1073
1177
  }
1074
1178
 
@@ -1077,8 +1181,7 @@ var CodeMirror = (function() {
1077
1181
 
1078
1182
  function loadMode() {
1079
1183
  mode = CodeMirror.getMode(options, options.mode);
1080
- for (var i = 0, l = lines.length; i < l; ++i)
1081
- lines[i].stateAfter = null;
1184
+ doc.iter(0, doc.size, function(line) { line.stateAfter = null; });
1082
1185
  work = [0];
1083
1186
  startWorker();
1084
1187
  }
@@ -1088,76 +1191,153 @@ var CodeMirror = (function() {
1088
1191
  if (visible) gutterDirty = true;
1089
1192
  else lineDiv.parentNode.style.marginLeft = 0;
1090
1193
  }
1194
+ function wrappingChanged(from, to) {
1195
+ if (options.lineWrapping) {
1196
+ wrapper.className += " CodeMirror-wrap";
1197
+ var perLine = scroller.clientWidth / charWidth() - 3;
1198
+ doc.iter(0, doc.size, function(line) {
1199
+ if (line.hidden) return;
1200
+ var guess = Math.ceil(line.text.length / perLine) || 1;
1201
+ if (guess != 1) updateLineHeight(line, guess);
1202
+ });
1203
+ lineSpace.style.width = code.style.width = "";
1204
+ } else {
1205
+ wrapper.className = wrapper.className.replace(" CodeMirror-wrap", "");
1206
+ maxWidth = null; maxLine = "";
1207
+ doc.iter(0, doc.size, function(line) {
1208
+ if (line.height != 1 && !line.hidden) updateLineHeight(line, 1);
1209
+ if (line.text.length > maxLine.length) maxLine = line.text;
1210
+ });
1211
+ }
1212
+ changes.push({from: 0, to: doc.size});
1213
+ }
1214
+ function computeTabText() {
1215
+ for (var str = '<span class="cm-tab">', i = 0; i < options.tabSize; ++i) str += " ";
1216
+ return str + "</span>";
1217
+ }
1218
+ function tabsChanged() {
1219
+ tabText = computeTabText();
1220
+ updateDisplay(true);
1221
+ }
1222
+ function themeChanged() {
1223
+ scroller.className = scroller.className.replace(/\s*cm-s-\w+/g, "") +
1224
+ options.theme.replace(/(^|\s)\s*/g, " cm-s-");
1225
+ }
1226
+
1227
+ function TextMarker() { this.set = []; }
1228
+ TextMarker.prototype.clear = operation(function() {
1229
+ var min = Infinity, max = -Infinity;
1230
+ for (var i = 0, e = this.set.length; i < e; ++i) {
1231
+ var line = this.set[i], mk = line.marked;
1232
+ if (!mk || !line.parent) continue;
1233
+ var lineN = lineNo(line);
1234
+ min = Math.min(min, lineN); max = Math.max(max, lineN);
1235
+ for (var j = 0; j < mk.length; ++j)
1236
+ if (mk[j].set == this.set) mk.splice(j--, 1);
1237
+ }
1238
+ if (min != Infinity)
1239
+ changes.push({from: min, to: max + 1});
1240
+ });
1241
+ TextMarker.prototype.find = function() {
1242
+ var from, to;
1243
+ for (var i = 0, e = this.set.length; i < e; ++i) {
1244
+ var line = this.set[i], mk = line.marked;
1245
+ for (var j = 0; j < mk.length; ++j) {
1246
+ var mark = mk[j];
1247
+ if (mark.set == this.set) {
1248
+ if (mark.from != null || mark.to != null) {
1249
+ var found = lineNo(line);
1250
+ if (found != null) {
1251
+ if (mark.from != null) from = {line: found, ch: mark.from};
1252
+ if (mark.to != null) to = {line: found, ch: mark.to};
1253
+ }
1254
+ }
1255
+ }
1256
+ }
1257
+ }
1258
+ return {from: from, to: to};
1259
+ };
1091
1260
 
1092
1261
  function markText(from, to, className) {
1093
1262
  from = clipPos(from); to = clipPos(to);
1094
- var accum = [];
1263
+ var tm = new TextMarker();
1095
1264
  function add(line, from, to, className) {
1096
- var line = lines[line], mark = line.addMark(from, to, className);
1097
- mark.line = line;
1098
- accum.push(mark);
1265
+ getLine(line).addMark(new MarkedText(from, to, className, tm.set));
1099
1266
  }
1100
1267
  if (from.line == to.line) add(from.line, from.ch, to.ch, className);
1101
1268
  else {
1102
1269
  add(from.line, from.ch, null, className);
1103
1270
  for (var i = from.line + 1, e = to.line; i < e; ++i)
1104
- add(i, 0, null, className);
1105
- add(to.line, 0, to.ch, className);
1271
+ add(i, null, null, className);
1272
+ add(to.line, null, to.ch, className);
1106
1273
  }
1107
1274
  changes.push({from: from.line, to: to.line + 1});
1108
- return function() {
1109
- var start, end;
1110
- for (var i = 0; i < accum.length; ++i) {
1111
- var mark = accum[i], found = indexOf(lines, mark.line);
1112
- mark.line.removeMark(mark);
1113
- if (found > -1) {
1114
- if (start == null) start = found;
1115
- end = found;
1116
- }
1117
- }
1118
- if (start != null) changes.push({from: start, to: end + 1});
1119
- };
1275
+ return tm;
1276
+ }
1277
+
1278
+ function setBookmark(pos) {
1279
+ pos = clipPos(pos);
1280
+ var bm = new Bookmark(pos.ch);
1281
+ getLine(pos.line).addMark(bm);
1282
+ return bm;
1120
1283
  }
1121
1284
 
1122
1285
  function addGutterMarker(line, text, className) {
1123
- if (typeof line == "number") line = lines[clipLine(line)];
1286
+ if (typeof line == "number") line = getLine(clipLine(line));
1124
1287
  line.gutterMarker = {text: text, style: className};
1125
1288
  gutterDirty = true;
1126
1289
  return line;
1127
1290
  }
1128
1291
  function removeGutterMarker(line) {
1129
- if (typeof line == "number") line = lines[clipLine(line)];
1292
+ if (typeof line == "number") line = getLine(clipLine(line));
1130
1293
  line.gutterMarker = null;
1131
1294
  gutterDirty = true;
1132
1295
  }
1133
- function setLineClass(line, className) {
1134
- if (typeof line == "number") {
1135
- var no = line;
1136
- line = lines[clipLine(line)];
1137
- }
1138
- else {
1139
- var no = indexOf(lines, line);
1140
- if (no == -1) return null;
1141
- }
1142
- if (line.className != className) {
1143
- line.className = className;
1144
- changes.push({from: no, to: no + 1});
1145
- }
1296
+
1297
+ function changeLine(handle, op) {
1298
+ var no = handle, line = handle;
1299
+ if (typeof handle == "number") line = getLine(clipLine(handle));
1300
+ else no = lineNo(handle);
1301
+ if (no == null) return null;
1302
+ if (op(line, no)) changes.push({from: no, to: no + 1});
1303
+ else return null;
1146
1304
  return line;
1147
1305
  }
1306
+ function setLineClass(handle, className) {
1307
+ return changeLine(handle, function(line) {
1308
+ if (line.className != className) {
1309
+ line.className = className;
1310
+ return true;
1311
+ }
1312
+ });
1313
+ }
1314
+ function setLineHidden(handle, hidden) {
1315
+ return changeLine(handle, function(line, no) {
1316
+ if (line.hidden != hidden) {
1317
+ line.hidden = hidden;
1318
+ updateLineHeight(line, hidden ? 0 : 1);
1319
+ if (hidden && (sel.from.line == no || sel.to.line == no))
1320
+ setSelection(skipHidden(sel.from, sel.from.line, sel.from.ch),
1321
+ skipHidden(sel.to, sel.to.line, sel.to.ch));
1322
+ return (gutterDirty = true);
1323
+ }
1324
+ });
1325
+ }
1148
1326
 
1149
1327
  function lineInfo(line) {
1150
1328
  if (typeof line == "number") {
1329
+ if (!isLine(line)) return null;
1151
1330
  var n = line;
1152
- line = lines[line];
1331
+ line = getLine(line);
1153
1332
  if (!line) return null;
1154
1333
  }
1155
1334
  else {
1156
- var n = indexOf(lines, line);
1157
- if (n == -1) return null;
1335
+ var n = lineNo(line);
1336
+ if (n == null) return null;
1158
1337
  }
1159
1338
  var marker = line.gutterMarker;
1160
- return {line: n, text: line.text, markerText: marker && marker.text, markerClass: marker && marker.style};
1339
+ return {line: n, handle: line, text: line.text, markerText: marker && marker.text,
1340
+ markerClass: marker && marker.style, lineClass: line.className};
1161
1341
  }
1162
1342
 
1163
1343
  function stringWidth(str) {
@@ -1167,21 +1347,16 @@ var CodeMirror = (function() {
1167
1347
  }
1168
1348
  // These are used to go from pixel positions to character
1169
1349
  // positions, taking varying character widths into account.
1170
- function charX(line, pos) {
1171
- if (pos == 0) return 0;
1172
- measure.innerHTML = "<pre><span>" + lines[line].getHTML(null, null, false, pos) + "</span></pre>";
1173
- return measure.firstChild.firstChild.offsetWidth;
1174
- }
1175
1350
  function charFromX(line, x) {
1176
1351
  if (x <= 0) return 0;
1177
- var lineObj = lines[line], text = lineObj.text;
1352
+ var lineObj = getLine(line), text = lineObj.text;
1178
1353
  function getX(len) {
1179
- measure.innerHTML = "<pre><span>" + lineObj.getHTML(null, null, false, len) + "</span></pre>";
1354
+ measure.innerHTML = "<pre><span>" + lineObj.getHTML(null, null, false, tabText, len) + "</span></pre>";
1180
1355
  return measure.firstChild.firstChild.offsetWidth;
1181
1356
  }
1182
1357
  var from = 0, fromX = 0, to = text.length, toX;
1183
1358
  // Guess a suitable upper bound for our search.
1184
- var estimated = Math.min(to, Math.ceil(x / stringWidth("x")));
1359
+ var estimated = Math.min(to, Math.ceil(x / charWidth()));
1185
1360
  for (;;) {
1186
1361
  var estX = getX(estimated);
1187
1362
  if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
@@ -1200,20 +1375,100 @@ var CodeMirror = (function() {
1200
1375
  }
1201
1376
  }
1202
1377
 
1378
+ var tempId = Math.floor(Math.random() * 0xffffff).toString(16);
1379
+ function measureLine(line, ch) {
1380
+ var extra = "";
1381
+ // Include extra text at the end to make sure the measured line is wrapped in the right way.
1382
+ if (options.lineWrapping) {
1383
+ var end = line.text.indexOf(" ", ch + 2);
1384
+ extra = htmlEscape(line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0)));
1385
+ }
1386
+ measure.innerHTML = "<pre>" + line.getHTML(null, null, false, tabText, ch) +
1387
+ '<span id="CodeMirror-temp-' + tempId + '">' + htmlEscape(line.text.charAt(ch) || " ") + "</span>" +
1388
+ extra + "</pre>";
1389
+ var elt = document.getElementById("CodeMirror-temp-" + tempId);
1390
+ var top = elt.offsetTop, left = elt.offsetLeft;
1391
+ // Older IEs report zero offsets for spans directly after a wrap
1392
+ if (ie && ch && top == 0 && left == 0) {
1393
+ var backup = document.createElement("span");
1394
+ backup.innerHTML = "x";
1395
+ elt.parentNode.insertBefore(backup, elt.nextSibling);
1396
+ top = backup.offsetTop;
1397
+ }
1398
+ return {top: top, left: left};
1399
+ }
1203
1400
  function localCoords(pos, inLineWrap) {
1204
- var lh = lineHeight(), line = pos.line - (inLineWrap ? showingFrom : 0);
1205
- return {x: charX(pos.line, pos.ch), y: line * lh, yBot: (line + 1) * lh};
1401
+ var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0));
1402
+ if (pos.ch == 0) x = 0;
1403
+ else {
1404
+ var sp = measureLine(getLine(pos.line), pos.ch);
1405
+ x = sp.left;
1406
+ if (options.lineWrapping) y += Math.max(0, sp.top);
1407
+ }
1408
+ return {x: x, y: y, yBot: y + lh};
1409
+ }
1410
+ // Coords must be lineSpace-local
1411
+ function coordsChar(x, y) {
1412
+ if (y < 0) y = 0;
1413
+ var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th);
1414
+ var lineNo = lineAtHeight(doc, heightPos);
1415
+ if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length};
1416
+ var lineObj = getLine(lineNo), text = lineObj.text;
1417
+ var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0;
1418
+ if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0};
1419
+ function getX(len) {
1420
+ var sp = measureLine(lineObj, len);
1421
+ if (tw) {
1422
+ var off = Math.round(sp.top / th);
1423
+ return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth);
1424
+ }
1425
+ return sp.left;
1426
+ }
1427
+ var from = 0, fromX = 0, to = text.length, toX;
1428
+ // Guess a suitable upper bound for our search.
1429
+ var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw));
1430
+ for (;;) {
1431
+ var estX = getX(estimated);
1432
+ if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
1433
+ else {toX = estX; to = estimated; break;}
1434
+ }
1435
+ if (x > toX) return {line: lineNo, ch: to};
1436
+ // Try to guess a suitable lower bound as well.
1437
+ estimated = Math.floor(to * 0.8); estX = getX(estimated);
1438
+ if (estX < x) {from = estimated; fromX = estX;}
1439
+ // Do a binary search between these bounds.
1440
+ for (;;) {
1441
+ if (to - from <= 1) return {line: lineNo, ch: (toX - x > x - fromX) ? from : to};
1442
+ var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
1443
+ if (middleX > x) {to = middle; toX = middleX;}
1444
+ else {from = middle; fromX = middleX;}
1445
+ }
1206
1446
  }
1207
1447
  function pageCoords(pos) {
1208
1448
  var local = localCoords(pos, true), off = eltOffset(lineSpace);
1209
1449
  return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot};
1210
1450
  }
1211
1451
 
1212
- function lineHeight() {
1213
- var nlines = lineDiv.childNodes.length;
1214
- if (nlines) return (lineDiv.offsetHeight / nlines) || 1;
1215
- measure.innerHTML = "<pre>x</pre>";
1216
- return measure.firstChild.offsetHeight || 1;
1452
+ var cachedHeight, cachedHeightFor, measureText;
1453
+ function textHeight() {
1454
+ if (measureText == null) {
1455
+ measureText = "<pre>";
1456
+ for (var i = 0; i < 49; ++i) measureText += "x<br/>";
1457
+ measureText += "x</pre>";
1458
+ }
1459
+ var offsetHeight = lineDiv.clientHeight;
1460
+ if (offsetHeight == cachedHeightFor) return cachedHeight;
1461
+ cachedHeightFor = offsetHeight;
1462
+ measure.innerHTML = measureText;
1463
+ cachedHeight = measure.firstChild.offsetHeight / 50 || 1;
1464
+ measure.innerHTML = "";
1465
+ return cachedHeight;
1466
+ }
1467
+ var cachedWidth, cachedWidthFor = 0;
1468
+ function charWidth() {
1469
+ if (scroller.clientWidth == cachedWidthFor) return cachedWidth;
1470
+ cachedWidthFor = scroller.clientWidth;
1471
+ return (cachedWidth = stringWidth("x"));
1217
1472
  }
1218
1473
  function paddingTop() {return lineSpace.offsetTop;}
1219
1474
  function paddingLeft() {return lineSpace.offsetLeft;}
@@ -1228,8 +1483,7 @@ var CodeMirror = (function() {
1228
1483
  if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight))
1229
1484
  return null;
1230
1485
  var offL = eltOffset(lineSpace, true);
1231
- var line = showingFrom + Math.floor((y - offL.top) / lineHeight());
1232
- return clipPos({line: line, ch: charFromX(clipLine(line), x - offL.left)});
1486
+ return coordsChar(x - offL.left, y - offL.top);
1233
1487
  }
1234
1488
  function onContextMenu(e) {
1235
1489
  var pos = posFromMouse(e);
@@ -1245,17 +1499,17 @@ var CodeMirror = (function() {
1245
1499
  leaveInputAlone = true;
1246
1500
  var val = input.value = getSelection();
1247
1501
  focusInput();
1248
- setSelRange(input, 0, input.value.length);
1502
+ input.select();
1249
1503
  function rehide() {
1250
1504
  var newVal = splitLines(input.value).join("\n");
1251
1505
  if (newVal != val) operation(replaceSelection)(newVal, "end");
1252
1506
  inputDiv.style.position = "relative";
1253
1507
  input.style.cssText = oldCSS;
1254
1508
  leaveInputAlone = false;
1255
- prepareInput();
1509
+ resetInput(true);
1256
1510
  slowPoll();
1257
1511
  }
1258
-
1512
+
1259
1513
  if (gecko) {
1260
1514
  e_stop(e);
1261
1515
  var mouseup = connect(window, "mouseup", function() {
@@ -1280,7 +1534,7 @@ var CodeMirror = (function() {
1280
1534
 
1281
1535
  var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
1282
1536
  function matchBrackets(autoclear) {
1283
- var head = sel.inverted ? sel.from : sel.to, line = lines[head.line], pos = head.ch - 1;
1537
+ var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1;
1284
1538
  var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
1285
1539
  if (!match) return;
1286
1540
  var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles;
@@ -1304,18 +1558,16 @@ var CodeMirror = (function() {
1304
1558
  }
1305
1559
  }
1306
1560
  }
1307
- for (var i = head.line, e = forward ? Math.min(i + 100, lines.length) : Math.max(-1, i - 100); i != e; i+=d) {
1308
- var line = lines[i], first = i == head.line;
1561
+ for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) {
1562
+ var line = getLine(i), first = i == head.line;
1309
1563
  var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);
1310
1564
  if (found) break;
1311
1565
  }
1312
1566
  if (!found) found = {pos: null, match: false};
1313
1567
  var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
1314
1568
  var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),
1315
- two = found.pos != null
1316
- ? markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style)
1317
- : function() {};
1318
- var clear = operation(function(){one(); two();});
1569
+ two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);
1570
+ var clear = operation(function(){one.clear(); two && two.clear();});
1319
1571
  if (autoclear) setTimeout(clear, 800);
1320
1572
  else bracketHighlighted = clear;
1321
1573
  }
@@ -1329,9 +1581,9 @@ var CodeMirror = (function() {
1329
1581
  var minindent, minline;
1330
1582
  for (var search = n, lim = n - 40; search > lim; --search) {
1331
1583
  if (search == 0) return 0;
1332
- var line = lines[search-1];
1584
+ var line = getLine(search-1);
1333
1585
  if (line.stateAfter) return search;
1334
- var indented = line.indentation();
1586
+ var indented = line.indentation(options.tabSize);
1335
1587
  if (minline == null || minindent > indented) {
1336
1588
  minline = search - 1;
1337
1589
  minindent = indented;
@@ -1340,55 +1592,58 @@ var CodeMirror = (function() {
1340
1592
  return minline;
1341
1593
  }
1342
1594
  function getStateBefore(n) {
1343
- var start = findStartLine(n), state = start && lines[start-1].stateAfter;
1595
+ var start = findStartLine(n), state = start && getLine(start-1).stateAfter;
1344
1596
  if (!state) state = startState(mode);
1345
1597
  else state = copyState(mode, state);
1346
- for (var i = start; i < n; ++i) {
1347
- var line = lines[i];
1348
- line.highlight(mode, state);
1598
+ doc.iter(start, n, function(line) {
1599
+ line.highlight(mode, state, options.tabSize);
1349
1600
  line.stateAfter = copyState(mode, state);
1350
- }
1351
- if (n < lines.length && !lines[n].stateAfter) work.push(n);
1601
+ });
1602
+ if (start < n) changes.push({from: start, to: n});
1603
+ if (n < doc.size && !getLine(n).stateAfter) work.push(n);
1352
1604
  return state;
1353
1605
  }
1354
1606
  function highlightLines(start, end) {
1355
1607
  var state = getStateBefore(start);
1356
- for (var i = start; i < end; ++i) {
1357
- var line = lines[i];
1358
- line.highlight(mode, state);
1608
+ doc.iter(start, end, function(line) {
1609
+ line.highlight(mode, state, options.tabSize);
1359
1610
  line.stateAfter = copyState(mode, state);
1360
- }
1611
+ });
1361
1612
  }
1362
1613
  function highlightWorker() {
1363
1614
  var end = +new Date + options.workTime;
1364
1615
  var foundWork = work.length;
1365
1616
  while (work.length) {
1366
- if (!lines[showingFrom].stateAfter) var task = showingFrom;
1617
+ if (!getLine(showingFrom).stateAfter) var task = showingFrom;
1367
1618
  else var task = work.pop();
1368
- if (task >= lines.length) continue;
1369
- var start = findStartLine(task), state = start && lines[start-1].stateAfter;
1619
+ if (task >= doc.size) continue;
1620
+ var start = findStartLine(task), state = start && getLine(start-1).stateAfter;
1370
1621
  if (state) state = copyState(mode, state);
1371
1622
  else state = startState(mode);
1372
1623
 
1373
- var unchanged = 0, compare = mode.compareStates, realChange = false;
1374
- for (var i = start, l = lines.length; i < l; ++i) {
1375
- var line = lines[i], hadState = line.stateAfter;
1624
+ var unchanged = 0, compare = mode.compareStates, realChange = false,
1625
+ i = start, bail = false;
1626
+ doc.iter(i, doc.size, function(line) {
1627
+ var hadState = line.stateAfter;
1376
1628
  if (+new Date > end) {
1377
1629
  work.push(i);
1378
1630
  startWorker(options.workDelay);
1379
1631
  if (realChange) changes.push({from: task, to: i + 1});
1380
- return;
1632
+ return (bail = true);
1381
1633
  }
1382
- var changed = line.highlight(mode, state);
1634
+ var changed = line.highlight(mode, state, options.tabSize);
1383
1635
  if (changed) realChange = true;
1384
1636
  line.stateAfter = copyState(mode, state);
1385
1637
  if (compare) {
1386
- if (hadState && compare(hadState, state)) break;
1638
+ if (hadState && compare(hadState, state)) return true;
1387
1639
  } else {
1388
1640
  if (changed !== false || !hadState) unchanged = 0;
1389
- else if (++unchanged > 3) break;
1641
+ else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, "") == mode.indent(state, "")))
1642
+ return true;
1390
1643
  }
1391
- }
1644
+ ++i;
1645
+ });
1646
+ if (bail) return;
1392
1647
  if (realChange) changes.push({from: task, to: i + 1});
1393
1648
  }
1394
1649
  if (foundWork && options.onHighlightComplete)
@@ -1404,12 +1659,13 @@ var CodeMirror = (function() {
1404
1659
  // be awkward, slow, and error-prone), but instead updates are
1405
1660
  // batched and then all combined and executed at once.
1406
1661
  function startOperation() {
1407
- updateInput = null; changes = []; textChanged = selectionChanged = false;
1662
+ updateInput = userSelChange = textChanged = null;
1663
+ changes = []; selectionChanged = false; callbacks = [];
1408
1664
  }
1409
1665
  function endOperation() {
1410
- var reScroll = false;
1666
+ var reScroll = false, updated;
1411
1667
  if (selectionChanged) reScroll = !scrollCursorIntoView();
1412
- if (changes.length) updateDisplay(changes);
1668
+ if (changes.length) updated = updateDisplay(changes, true);
1413
1669
  else {
1414
1670
  if (selectionChanged) updateCursor();
1415
1671
  if (gutterDirty) updateGutter();
@@ -1417,22 +1673,22 @@ var CodeMirror = (function() {
1417
1673
  if (reScroll) scrollCursorIntoView();
1418
1674
  if (selectionChanged) {scrollEditorIntoView(); restartBlink();}
1419
1675
 
1420
- // updateInput can be set to a boolean value to force/prevent an
1421
- // update.
1422
1676
  if (focused && !leaveInputAlone &&
1423
1677
  (updateInput === true || (updateInput !== false && selectionChanged)))
1424
- prepareInput();
1678
+ resetInput(userSelChange);
1425
1679
 
1426
1680
  if (selectionChanged && options.matchBrackets)
1427
1681
  setTimeout(operation(function() {
1428
1682
  if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;}
1429
- matchBrackets(false);
1683
+ if (posEq(sel.from, sel.to)) matchBrackets(false);
1430
1684
  }), 20);
1431
- var tc = textChanged; // textChanged can be reset by cursoractivity callback
1685
+ var tc = textChanged, cbs = callbacks; // these can be reset by callbacks
1432
1686
  if (selectionChanged && options.onCursorActivity)
1433
1687
  options.onCursorActivity(instance);
1434
1688
  if (tc && options.onChange && instance)
1435
1689
  options.onChange(instance, tc);
1690
+ for (var i = 0; i < cbs.length; ++i) cbs[i](instance);
1691
+ if (updated && options.onUpdate) options.onUpdate(instance);
1436
1692
  }
1437
1693
  var nestedOperation = 0;
1438
1694
  function operation(f) {
@@ -1444,120 +1700,6 @@ var CodeMirror = (function() {
1444
1700
  };
1445
1701
  }
1446
1702
 
1447
- function SearchCursor(query, pos, caseFold) {
1448
- this.atOccurrence = false;
1449
- if (caseFold == null) caseFold = typeof query == "string" && query == query.toLowerCase();
1450
-
1451
- if (pos && typeof pos == "object") pos = clipPos(pos);
1452
- else pos = {line: 0, ch: 0};
1453
- this.pos = {from: pos, to: pos};
1454
-
1455
- // The matches method is filled in based on the type of query.
1456
- // It takes a position and a direction, and returns an object
1457
- // describing the next occurrence of the query, or null if no
1458
- // more matches were found.
1459
- if (typeof query != "string") // Regexp match
1460
- this.matches = function(reverse, pos) {
1461
- if (reverse) {
1462
- var line = lines[pos.line].text.slice(0, pos.ch), match = line.match(query), start = 0;
1463
- while (match) {
1464
- var ind = line.indexOf(match[0]);
1465
- start += ind;
1466
- line = line.slice(ind + 1);
1467
- var newmatch = line.match(query);
1468
- if (newmatch) match = newmatch;
1469
- else break;
1470
- start++;
1471
- }
1472
- }
1473
- else {
1474
- var line = lines[pos.line].text.slice(pos.ch), match = line.match(query),
1475
- start = match && pos.ch + line.indexOf(match[0]);
1476
- }
1477
- if (match)
1478
- return {from: {line: pos.line, ch: start},
1479
- to: {line: pos.line, ch: start + match[0].length},
1480
- match: match};
1481
- };
1482
- else { // String query
1483
- if (caseFold) query = query.toLowerCase();
1484
- var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
1485
- var target = query.split("\n");
1486
- // Different methods for single-line and multi-line queries
1487
- if (target.length == 1)
1488
- this.matches = function(reverse, pos) {
1489
- var line = fold(lines[pos.line].text), len = query.length, match;
1490
- if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
1491
- : (match = line.indexOf(query, pos.ch)) != -1)
1492
- return {from: {line: pos.line, ch: match},
1493
- to: {line: pos.line, ch: match + len}};
1494
- };
1495
- else
1496
- this.matches = function(reverse, pos) {
1497
- var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(lines[ln].text);
1498
- var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
1499
- if (reverse ? offsetA >= pos.ch || offsetA != match.length
1500
- : offsetA <= pos.ch || offsetA != line.length - match.length)
1501
- return;
1502
- for (;;) {
1503
- if (reverse ? !ln : ln == lines.length - 1) return;
1504
- line = fold(lines[ln += reverse ? -1 : 1].text);
1505
- match = target[reverse ? --idx : ++idx];
1506
- if (idx > 0 && idx < target.length - 1) {
1507
- if (line != match) return;
1508
- else continue;
1509
- }
1510
- var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length);
1511
- if (reverse ? offsetB != line.length - match.length : offsetB != match.length)
1512
- return;
1513
- var start = {line: pos.line, ch: offsetA}, end = {line: ln, ch: offsetB};
1514
- return {from: reverse ? end : start, to: reverse ? start : end};
1515
- }
1516
- };
1517
- }
1518
- }
1519
-
1520
- SearchCursor.prototype = {
1521
- findNext: function() {return this.find(false);},
1522
- findPrevious: function() {return this.find(true);},
1523
-
1524
- find: function(reverse) {
1525
- var self = this, pos = clipPos(reverse ? this.pos.from : this.pos.to);
1526
- function savePosAndFail(line) {
1527
- var pos = {line: line, ch: 0};
1528
- self.pos = {from: pos, to: pos};
1529
- self.atOccurrence = false;
1530
- return false;
1531
- }
1532
-
1533
- for (;;) {
1534
- if (this.pos = this.matches(reverse, pos)) {
1535
- this.atOccurrence = true;
1536
- return this.pos.match || true;
1537
- }
1538
- if (reverse) {
1539
- if (!pos.line) return savePosAndFail(0);
1540
- pos = {line: pos.line-1, ch: lines[pos.line-1].text.length};
1541
- }
1542
- else {
1543
- if (pos.line == lines.length - 1) return savePosAndFail(lines.length);
1544
- pos = {line: pos.line+1, ch: 0};
1545
- }
1546
- }
1547
- },
1548
-
1549
- from: function() {if (this.atOccurrence) return copyPos(this.pos.from);},
1550
- to: function() {if (this.atOccurrence) return copyPos(this.pos.to);},
1551
-
1552
- replace: function(newText) {
1553
- var self = this;
1554
- if (this.atOccurrence)
1555
- operation(function() {
1556
- self.pos.to = replaceRange(newText, self.pos.from, self.pos.to);
1557
- })();
1558
- }
1559
- };
1560
-
1561
1703
  for (var ext in extensions)
1562
1704
  if (extensions.propertyIsEnumerable(ext) &&
1563
1705
  !instance.propertyIsEnumerable(ext))
@@ -1572,29 +1714,35 @@ var CodeMirror = (function() {
1572
1714
  theme: "default",
1573
1715
  indentUnit: 2,
1574
1716
  indentWithTabs: false,
1575
- tabMode: "classic",
1576
- enterMode: "indent",
1717
+ tabSize: 4,
1718
+ keyMap: "default",
1719
+ extraKeys: null,
1577
1720
  electricChars: true,
1578
1721
  onKeyEvent: null,
1722
+ lineWrapping: false,
1579
1723
  lineNumbers: false,
1580
1724
  gutter: false,
1581
1725
  fixedGutter: false,
1582
1726
  firstLineNumber: 1,
1583
1727
  readOnly: false,
1584
- smartHome: true,
1585
1728
  onChange: null,
1586
1729
  onCursorActivity: null,
1587
1730
  onGutterClick: null,
1588
1731
  onHighlightComplete: null,
1732
+ onUpdate: null,
1589
1733
  onFocus: null, onBlur: null, onScroll: null,
1590
1734
  matchBrackets: false,
1591
1735
  workTime: 100,
1592
1736
  workDelay: 200,
1737
+ pollInterval: 100,
1593
1738
  undoDepth: 40,
1594
1739
  tabindex: null,
1595
1740
  document: window.document
1596
1741
  };
1597
1742
 
1743
+ var mac = /Mac/.test(navigator.platform);
1744
+ var win = /Win/.test(navigator.platform);
1745
+
1598
1746
  // Known modes, by name and by MIME
1599
1747
  var modes = {}, mimeModes = {};
1600
1748
  CodeMirror.defineMode = function(name, mode) {
@@ -1631,11 +1779,114 @@ var CodeMirror = (function() {
1631
1779
  return list;
1632
1780
  };
1633
1781
 
1634
- var extensions = {};
1782
+ var extensions = CodeMirror.extensions = {};
1635
1783
  CodeMirror.defineExtension = function(name, func) {
1636
1784
  extensions[name] = func;
1637
1785
  };
1638
1786
 
1787
+ var commands = CodeMirror.commands = {
1788
+ selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});},
1789
+ killLine: function(cm) {
1790
+ var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
1791
+ if (!sel && cm.getLine(from.line).length == from.ch) cm.replaceRange("", from, {line: from.line + 1, ch: 0});
1792
+ else cm.replaceRange("", from, sel ? to : {line: from.line});
1793
+ },
1794
+ deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange("", {line: l, ch: 0}, {line: l});},
1795
+ undo: function(cm) {cm.undo();},
1796
+ redo: function(cm) {cm.redo();},
1797
+ goDocStart: function(cm) {cm.setCursor(0, 0, true);},
1798
+ goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);},
1799
+ goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);},
1800
+ goLineStartSmart: function(cm) {
1801
+ var cur = cm.getCursor();
1802
+ var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/));
1803
+ cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true);
1804
+ },
1805
+ goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);},
1806
+ goLineUp: function(cm) {cm.moveV(-1, "line");},
1807
+ goLineDown: function(cm) {cm.moveV(1, "line");},
1808
+ goPageUp: function(cm) {cm.moveV(-1, "page");},
1809
+ goPageDown: function(cm) {cm.moveV(1, "page");},
1810
+ goCharLeft: function(cm) {cm.moveH(-1, "char");},
1811
+ goCharRight: function(cm) {cm.moveH(1, "char");},
1812
+ goColumnLeft: function(cm) {cm.moveH(-1, "column");},
1813
+ goColumnRight: function(cm) {cm.moveH(1, "column");},
1814
+ goWordLeft: function(cm) {cm.moveH(-1, "word");},
1815
+ goWordRight: function(cm) {cm.moveH(1, "word");},
1816
+ delCharLeft: function(cm) {cm.deleteH(-1, "char");},
1817
+ delCharRight: function(cm) {cm.deleteH(1, "char");},
1818
+ delWordLeft: function(cm) {cm.deleteH(-1, "word");},
1819
+ delWordRight: function(cm) {cm.deleteH(1, "word");},
1820
+ indentAuto: function(cm) {cm.indentSelection("smart");},
1821
+ indentMore: function(cm) {cm.indentSelection("add");},
1822
+ indentLess: function(cm) {cm.indentSelection("subtract");},
1823
+ insertTab: function(cm) {cm.replaceSelection("\t", "end");},
1824
+ transposeChars: function(cm) {
1825
+ var cur = cm.getCursor(), line = cm.getLine(cur.line);
1826
+ if (cur.ch > 0 && cur.ch < line.length - 1)
1827
+ cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
1828
+ {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1});
1829
+ },
1830
+ newlineAndIndent: function(cm) {
1831
+ cm.replaceSelection("\n", "end");
1832
+ cm.indentLine(cm.getCursor().line);
1833
+ },
1834
+ toggleOverwrite: function(cm) {cm.toggleOverwrite();}
1835
+ };
1836
+
1837
+ var keyMap = CodeMirror.keyMap = {};
1838
+ keyMap.basic = {
1839
+ "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
1840
+ "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
1841
+ "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "indentMore", "Shift-Tab": "indentLess",
1842
+ "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
1843
+ };
1844
+ // Note that the save and find-related commands aren't defined by
1845
+ // default. Unknown commands are simply ignored.
1846
+ keyMap.pcDefault = {
1847
+ "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
1848
+ "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
1849
+ "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
1850
+ "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find",
1851
+ "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
1852
+ fallthrough: "basic"
1853
+ };
1854
+ keyMap.macDefault = {
1855
+ "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
1856
+ "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft",
1857
+ "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft",
1858
+ "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find",
1859
+ "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
1860
+ fallthrough: ["basic", "emacsy"]
1861
+ };
1862
+ keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
1863
+ keyMap.emacsy = {
1864
+ "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
1865
+ "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
1866
+ "Ctrl-V": "goPageUp", "Shift-Ctrl-V": "goPageDown", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft",
1867
+ "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
1868
+ };
1869
+
1870
+ function lookupKey(name, extraMap, map) {
1871
+ function lookup(name, map, ft) {
1872
+ var found = map[name];
1873
+ if (found != null) return found;
1874
+ if (ft == null) ft = map.fallthrough;
1875
+ if (ft == null) return map.catchall;
1876
+ if (typeof ft == "string") return lookup(name, keyMap[ft]);
1877
+ for (var i = 0, e = ft.length; i < e; ++i) {
1878
+ found = lookup(name, keyMap[ft[i]]);
1879
+ if (found != null) return found;
1880
+ }
1881
+ return null;
1882
+ }
1883
+ return extraMap ? lookup(name, extraMap, map) : lookup(name, keyMap[map]);
1884
+ }
1885
+ function isModifierKey(event) {
1886
+ var name = keyNames[event.keyCode];
1887
+ return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
1888
+ }
1889
+
1639
1890
  CodeMirror.fromTextArea = function(textarea, options) {
1640
1891
  if (!options) options = {};
1641
1892
  options.value = textarea.value;
@@ -1663,6 +1914,7 @@ var CodeMirror = (function() {
1663
1914
  textarea.parentNode.insertBefore(node, textarea.nextSibling);
1664
1915
  }, options);
1665
1916
  instance.save = save;
1917
+ instance.getTextArea = function() { return textarea; };
1666
1918
  instance.toTextArea = function() {
1667
1919
  save();
1668
1920
  textarea.parentNode.removeChild(instance.getWrapperElement());
@@ -1689,16 +1941,17 @@ var CodeMirror = (function() {
1689
1941
  }
1690
1942
  return nstate;
1691
1943
  }
1692
- CodeMirror.startState = startState;
1944
+ CodeMirror.copyState = copyState;
1693
1945
  function startState(mode, a1, a2) {
1694
1946
  return mode.startState ? mode.startState(a1, a2) : true;
1695
1947
  }
1696
- CodeMirror.copyState = copyState;
1948
+ CodeMirror.startState = startState;
1697
1949
 
1698
1950
  // The character stream used by a mode's parser.
1699
- function StringStream(string) {
1951
+ function StringStream(string, tabSize) {
1700
1952
  this.pos = this.start = 0;
1701
1953
  this.string = string;
1954
+ this.tabSize = tabSize || 8;
1702
1955
  }
1703
1956
  StringStream.prototype = {
1704
1957
  eol: function() {return this.pos >= this.string.length;},
@@ -1730,8 +1983,8 @@ var CodeMirror = (function() {
1730
1983
  if (found > -1) {this.pos = found; return true;}
1731
1984
  },
1732
1985
  backUp: function(n) {this.pos -= n;},
1733
- column: function() {return countColumn(this.string, this.start);},
1734
- indentation: function() {return countColumn(this.string);},
1986
+ column: function() {return countColumn(this.string, this.start, this.tabSize);},
1987
+ indentation: function() {return countColumn(this.string, null, this.tabSize);},
1735
1988
  match: function(pattern, consume, caseInsensitive) {
1736
1989
  if (typeof pattern == "string") {
1737
1990
  function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
@@ -1750,18 +2003,95 @@ var CodeMirror = (function() {
1750
2003
  };
1751
2004
  CodeMirror.StringStream = StringStream;
1752
2005
 
2006
+ function MarkedText(from, to, className, set) {
2007
+ this.from = from; this.to = to; this.style = className; this.set = set;
2008
+ }
2009
+ MarkedText.prototype = {
2010
+ attach: function(line) { this.set.push(line); },
2011
+ detach: function(line) {
2012
+ var ix = indexOf(this.set, line);
2013
+ if (ix > -1) this.set.splice(ix, 1);
2014
+ },
2015
+ split: function(pos, lenBefore) {
2016
+ if (this.to <= pos && this.to != null) return null;
2017
+ var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore;
2018
+ var to = this.to == null ? null : this.to - pos + lenBefore;
2019
+ return new MarkedText(from, to, this.style, this.set);
2020
+ },
2021
+ dup: function() { return new MarkedText(null, null, this.style, this.set); },
2022
+ clipTo: function(fromOpen, from, toOpen, to, diff) {
2023
+ if (this.from != null && this.from >= from)
2024
+ this.from = Math.max(to, this.from) + diff;
2025
+ if (this.to != null && this.to > from)
2026
+ this.to = to < this.to ? this.to + diff : from;
2027
+ if (fromOpen && to > this.from && (to < this.to || this.to == null))
2028
+ this.from = null;
2029
+ if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null))
2030
+ this.to = null;
2031
+ },
2032
+ isDead: function() { return this.from != null && this.to != null && this.from >= this.to; },
2033
+ sameSet: function(x) { return this.set == x.set; }
2034
+ };
2035
+
2036
+ function Bookmark(pos) {
2037
+ this.from = pos; this.to = pos; this.line = null;
2038
+ }
2039
+ Bookmark.prototype = {
2040
+ attach: function(line) { this.line = line; },
2041
+ detach: function(line) { if (this.line == line) this.line = null; },
2042
+ split: function(pos, lenBefore) {
2043
+ if (pos < this.from) {
2044
+ this.from = this.to = (this.from - pos) + lenBefore;
2045
+ return this;
2046
+ }
2047
+ },
2048
+ isDead: function() { return this.from > this.to; },
2049
+ clipTo: function(fromOpen, from, toOpen, to, diff) {
2050
+ if ((fromOpen || from < this.from) && (toOpen || to > this.to)) {
2051
+ this.from = 0; this.to = -1;
2052
+ } else if (this.from > from) {
2053
+ this.from = this.to = Math.max(to, this.from) + diff;
2054
+ }
2055
+ },
2056
+ sameSet: function(x) { return false; },
2057
+ find: function() {
2058
+ if (!this.line || !this.line.parent) return null;
2059
+ return {line: lineNo(this.line), ch: this.from};
2060
+ },
2061
+ clear: function() {
2062
+ if (this.line) {
2063
+ var found = indexOf(this.line.marked, this);
2064
+ if (found != -1) this.line.marked.splice(found, 1);
2065
+ this.line = null;
2066
+ }
2067
+ }
2068
+ };
2069
+
1753
2070
  // Line objects. These hold state related to a line, including
1754
2071
  // highlighting info (the styles array).
1755
2072
  function Line(text, styles) {
1756
2073
  this.styles = styles || [text, null];
1757
- this.stateAfter = null;
1758
2074
  this.text = text;
1759
- this.marked = this.gutterMarker = this.className = null;
2075
+ this.height = 1;
2076
+ this.marked = this.gutterMarker = this.className = this.handlers = null;
2077
+ this.stateAfter = this.parent = this.hidden = null;
2078
+ }
2079
+ Line.inheritMarks = function(text, orig) {
2080
+ var ln = new Line(text), mk = orig && orig.marked;
2081
+ if (mk) {
2082
+ for (var i = 0; i < mk.length; ++i) {
2083
+ if (mk[i].to == null && mk[i].style) {
2084
+ var newmk = ln.marked || (ln.marked = []), mark = mk[i];
2085
+ var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln);
2086
+ }
2087
+ }
2088
+ }
2089
+ return ln;
1760
2090
  }
1761
2091
  Line.prototype = {
1762
2092
  // Replace a piece of a line, keeping the styles around it intact.
1763
- replace: function(from, to, text) {
1764
- var st = [], mk = this.marked;
2093
+ replace: function(from, to_, text) {
2094
+ var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_;
1765
2095
  copyStyles(0, from, this.styles, st);
1766
2096
  if (text) st.push(text, null);
1767
2097
  copyStyles(to, this.text.length, this.styles, st);
@@ -1769,40 +2099,91 @@ var CodeMirror = (function() {
1769
2099
  this.text = this.text.slice(0, from) + text + this.text.slice(to);
1770
2100
  this.stateAfter = null;
1771
2101
  if (mk) {
1772
- var diff = text.length - (to - from), end = this.text.length;
1773
- function fix(n) {return n <= Math.min(to, to + diff) ? n : n + diff;}
1774
- for (var i = 0; i < mk.length; ++i) {
1775
- var mark = mk[i], del = false;
1776
- if (mark.from >= end) del = true;
1777
- else {mark.from = fix(mark.from); if (mark.to != null) mark.to = fix(mark.to);}
1778
- if (del || mark.from >= mark.to) {mk.splice(i, 1); i--;}
2102
+ var diff = text.length - (to - from);
2103
+ for (var i = 0, mark = mk[i]; i < mk.length; ++i) {
2104
+ mark.clipTo(from == null, from || 0, to_ == null, to, diff);
2105
+ if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);}
1779
2106
  }
1780
2107
  }
1781
2108
  },
1782
- // Split a line in two, again keeping styles intact.
2109
+ // Split a part off a line, keeping styles and markers intact.
1783
2110
  split: function(pos, textBefore) {
1784
- var st = [textBefore, null];
2111
+ var st = [textBefore, null], mk = this.marked;
1785
2112
  copyStyles(pos, this.text.length, this.styles, st);
1786
- return new Line(textBefore + this.text.slice(pos), st);
2113
+ var taken = new Line(textBefore + this.text.slice(pos), st);
2114
+ if (mk) {
2115
+ for (var i = 0; i < mk.length; ++i) {
2116
+ var mark = mk[i];
2117
+ var newmark = mark.split(pos, textBefore.length);
2118
+ if (newmark) {
2119
+ if (!taken.marked) taken.marked = [];
2120
+ taken.marked.push(newmark); newmark.attach(taken);
2121
+ }
2122
+ }
2123
+ }
2124
+ return taken;
1787
2125
  },
1788
- addMark: function(from, to, style) {
1789
- var mk = this.marked, mark = {from: from, to: to, style: style};
1790
- if (this.marked == null) this.marked = [];
1791
- this.marked.push(mark);
1792
- this.marked.sort(function(a, b){return a.from - b.from;});
1793
- return mark;
2126
+ append: function(line) {
2127
+ var mylen = this.text.length, mk = line.marked, mymk = this.marked;
2128
+ this.text += line.text;
2129
+ copyStyles(0, line.text.length, line.styles, this.styles);
2130
+ if (mymk) {
2131
+ for (var i = 0; i < mymk.length; ++i)
2132
+ if (mymk[i].to == null) mymk[i].to = mylen;
2133
+ }
2134
+ if (mk && mk.length) {
2135
+ if (!mymk) this.marked = mymk = [];
2136
+ outer: for (var i = 0; i < mk.length; ++i) {
2137
+ var mark = mk[i];
2138
+ if (!mark.from) {
2139
+ for (var j = 0; j < mymk.length; ++j) {
2140
+ var mymark = mymk[j];
2141
+ if (mymark.to == mylen && mymark.sameSet(mark)) {
2142
+ mymark.to = mark.to == null ? null : mark.to + mylen;
2143
+ if (mymark.isDead()) {
2144
+ mymark.detach(this);
2145
+ mk.splice(i--, 1);
2146
+ }
2147
+ continue outer;
2148
+ }
2149
+ }
2150
+ }
2151
+ mymk.push(mark);
2152
+ mark.attach(this);
2153
+ mark.from += mylen;
2154
+ if (mark.to != null) mark.to += mylen;
2155
+ }
2156
+ }
2157
+ },
2158
+ fixMarkEnds: function(other) {
2159
+ var mk = this.marked, omk = other.marked;
2160
+ if (!mk) return;
2161
+ for (var i = 0; i < mk.length; ++i) {
2162
+ var mark = mk[i], close = mark.to == null;
2163
+ if (close && omk) {
2164
+ for (var j = 0; j < omk.length; ++j)
2165
+ if (omk[j].sameSet(mark)) {close = false; break;}
2166
+ }
2167
+ if (close) mark.to = this.text.length;
2168
+ }
1794
2169
  },
1795
- removeMark: function(mark) {
2170
+ fixMarkStarts: function() {
1796
2171
  var mk = this.marked;
1797
2172
  if (!mk) return;
1798
2173
  for (var i = 0; i < mk.length; ++i)
1799
- if (mk[i] == mark) {mk.splice(i, 1); break;}
2174
+ if (mk[i].from == null) mk[i].from = 0;
2175
+ },
2176
+ addMark: function(mark) {
2177
+ mark.attach(this);
2178
+ if (this.marked == null) this.marked = [];
2179
+ this.marked.push(mark);
2180
+ this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);});
1800
2181
  },
1801
2182
  // Run the given mode's parser over a line, update the styles
1802
2183
  // array, which contains alternating fragments of text and CSS
1803
2184
  // classes.
1804
- highlight: function(mode, state) {
1805
- var stream = new StringStream(this.text), st = this.styles, pos = 0;
2185
+ highlight: function(mode, state, tabSize) {
2186
+ var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0;
1806
2187
  var changed = false, curWord = st[0], prevWord;
1807
2188
  if (this.text == "" && mode.blankLine) mode.blankLine(state);
1808
2189
  while (!stream.eol()) {
@@ -1843,17 +2224,20 @@ var CodeMirror = (function() {
1843
2224
  className: style || null,
1844
2225
  state: state};
1845
2226
  },
1846
- indentation: function() {return countColumn(this.text);},
2227
+ indentation: function(tabSize) {return countColumn(this.text, null, tabSize);},
1847
2228
  // Produces an HTML fragment for the line, taking selection,
1848
2229
  // marking, and highlighting into account.
1849
- getHTML: function(sfrom, sto, includePre, endAt) {
1850
- var html = [];
2230
+ getHTML: function(sfrom, sto, includePre, tabText, endAt) {
2231
+ var html = [], first = true;
1851
2232
  if (includePre)
1852
2233
  html.push(this.className ? '<pre class="' + this.className + '">': "<pre>");
1853
2234
  function span(text, style) {
1854
2235
  if (!text) return;
1855
- if (style) html.push('<span class="', style, '">', htmlEscape(text), "</span>");
1856
- else html.push(htmlEscape(text));
2236
+ // Work around a bug where, in some compat modes, IE ignores leading spaces
2237
+ if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1);
2238
+ first = false;
2239
+ if (style) html.push('<span class="', style, '">', htmlEscape(text).replace(/\t/g, tabText), "</span>");
2240
+ else html.push(htmlEscape(text).replace(/\t/g, tabText));
1857
2241
  }
1858
2242
  var st = this.styles, allText = this.text, marked = this.marked;
1859
2243
  if (sfrom == sto) sfrom = null;
@@ -1864,10 +2248,10 @@ var CodeMirror = (function() {
1864
2248
  span(" ", sfrom != null && sto == null ? "CodeMirror-selected" : null);
1865
2249
  else if (!marked && sfrom == null)
1866
2250
  for (var i = 0, ch = 0; ch < len; i+=2) {
1867
- var str = st[i], l = str.length;
2251
+ var str = st[i], style = st[i+1], l = str.length;
1868
2252
  if (ch + l > len) str = str.slice(0, len - ch);
1869
2253
  ch += l;
1870
- span(str, "cm-" + st[i+1]);
2254
+ span(str, style && "cm-" + style);
1871
2255
  }
1872
2256
  else {
1873
2257
  var pos = 0, i = 0, text = "", style, sg = 0;
@@ -1911,6 +2295,11 @@ var CodeMirror = (function() {
1911
2295
  }
1912
2296
  if (includePre) html.push("</pre>");
1913
2297
  return html.join("");
2298
+ },
2299
+ cleanUp: function() {
2300
+ this.parent = null;
2301
+ if (this.marked)
2302
+ for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this);
1914
2303
  }
1915
2304
  };
1916
2305
  // Utility used by replace and split above
@@ -1929,6 +2318,193 @@ var CodeMirror = (function() {
1929
2318
  }
1930
2319
  }
1931
2320
 
2321
+ // Data structure that holds the sequence of lines.
2322
+ function LeafChunk(lines) {
2323
+ this.lines = lines;
2324
+ this.parent = null;
2325
+ for (var i = 0, e = lines.length, height = 0; i < e; ++i) {
2326
+ lines[i].parent = this;
2327
+ height += lines[i].height;
2328
+ }
2329
+ this.height = height;
2330
+ }
2331
+ LeafChunk.prototype = {
2332
+ chunkSize: function() { return this.lines.length; },
2333
+ remove: function(at, n, callbacks) {
2334
+ for (var i = at, e = at + n; i < e; ++i) {
2335
+ var line = this.lines[i];
2336
+ this.height -= line.height;
2337
+ line.cleanUp();
2338
+ if (line.handlers)
2339
+ for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]);
2340
+ }
2341
+ this.lines.splice(at, n);
2342
+ },
2343
+ collapse: function(lines) {
2344
+ lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
2345
+ },
2346
+ insertHeight: function(at, lines, height) {
2347
+ this.height += height;
2348
+ this.lines.splice.apply(this.lines, [at, 0].concat(lines));
2349
+ for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
2350
+ },
2351
+ iterN: function(at, n, op) {
2352
+ for (var e = at + n; at < e; ++at)
2353
+ if (op(this.lines[at])) return true;
2354
+ }
2355
+ };
2356
+ function BranchChunk(children) {
2357
+ this.children = children;
2358
+ var size = 0, height = 0;
2359
+ for (var i = 0, e = children.length; i < e; ++i) {
2360
+ var ch = children[i];
2361
+ size += ch.chunkSize(); height += ch.height;
2362
+ ch.parent = this;
2363
+ }
2364
+ this.size = size;
2365
+ this.height = height;
2366
+ this.parent = null;
2367
+ }
2368
+ BranchChunk.prototype = {
2369
+ chunkSize: function() { return this.size; },
2370
+ remove: function(at, n, callbacks) {
2371
+ this.size -= n;
2372
+ for (var i = 0; i < this.children.length; ++i) {
2373
+ var child = this.children[i], sz = child.chunkSize();
2374
+ if (at < sz) {
2375
+ var rm = Math.min(n, sz - at), oldHeight = child.height;
2376
+ child.remove(at, rm, callbacks);
2377
+ this.height -= oldHeight - child.height;
2378
+ if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
2379
+ if ((n -= rm) == 0) break;
2380
+ at = 0;
2381
+ } else at -= sz;
2382
+ }
2383
+ if (this.size - n < 25) {
2384
+ var lines = [];
2385
+ this.collapse(lines);
2386
+ this.children = [new LeafChunk(lines)];
2387
+ }
2388
+ },
2389
+ collapse: function(lines) {
2390
+ for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
2391
+ },
2392
+ insert: function(at, lines) {
2393
+ var height = 0;
2394
+ for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
2395
+ this.insertHeight(at, lines, height);
2396
+ },
2397
+ insertHeight: function(at, lines, height) {
2398
+ this.size += lines.length;
2399
+ this.height += height;
2400
+ for (var i = 0, e = this.children.length; i < e; ++i) {
2401
+ var child = this.children[i], sz = child.chunkSize();
2402
+ if (at <= sz) {
2403
+ child.insertHeight(at, lines, height);
2404
+ if (child.lines && child.lines.length > 50) {
2405
+ while (child.lines.length > 50) {
2406
+ var spilled = child.lines.splice(child.lines.length - 25, 25);
2407
+ var newleaf = new LeafChunk(spilled);
2408
+ child.height -= newleaf.height;
2409
+ this.children.splice(i + 1, 0, newleaf);
2410
+ newleaf.parent = this;
2411
+ }
2412
+ this.maybeSpill();
2413
+ }
2414
+ break;
2415
+ }
2416
+ at -= sz;
2417
+ }
2418
+ },
2419
+ maybeSpill: function() {
2420
+ if (this.children.length <= 10) return;
2421
+ var me = this;
2422
+ do {
2423
+ var spilled = me.children.splice(me.children.length - 5, 5);
2424
+ var sibling = new BranchChunk(spilled);
2425
+ if (!me.parent) { // Become the parent node
2426
+ var copy = new BranchChunk(me.children);
2427
+ copy.parent = me;
2428
+ me.children = [copy, sibling];
2429
+ me = copy;
2430
+ } else {
2431
+ me.size -= sibling.size;
2432
+ me.height -= sibling.height;
2433
+ var myIndex = indexOf(me.parent.children, me);
2434
+ me.parent.children.splice(myIndex + 1, 0, sibling);
2435
+ }
2436
+ sibling.parent = me.parent;
2437
+ } while (me.children.length > 10);
2438
+ me.parent.maybeSpill();
2439
+ },
2440
+ iter: function(from, to, op) { this.iterN(from, to - from, op); },
2441
+ iterN: function(at, n, op) {
2442
+ for (var i = 0, e = this.children.length; i < e; ++i) {
2443
+ var child = this.children[i], sz = child.chunkSize();
2444
+ if (at < sz) {
2445
+ var used = Math.min(n, sz - at);
2446
+ if (child.iterN(at, used, op)) return true;
2447
+ if ((n -= used) == 0) break;
2448
+ at = 0;
2449
+ } else at -= sz;
2450
+ }
2451
+ }
2452
+ };
2453
+
2454
+ function getLineAt(chunk, n) {
2455
+ while (!chunk.lines) {
2456
+ for (var i = 0;; ++i) {
2457
+ var child = chunk.children[i], sz = child.chunkSize();
2458
+ if (n < sz) { chunk = child; break; }
2459
+ n -= sz;
2460
+ }
2461
+ }
2462
+ return chunk.lines[n];
2463
+ }
2464
+ function lineNo(line) {
2465
+ if (line.parent == null) return null;
2466
+ var cur = line.parent, no = indexOf(cur.lines, line);
2467
+ for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
2468
+ for (var i = 0, e = chunk.children.length; ; ++i) {
2469
+ if (chunk.children[i] == cur) break;
2470
+ no += chunk.children[i].chunkSize();
2471
+ }
2472
+ }
2473
+ return no;
2474
+ }
2475
+ function lineAtHeight(chunk, h) {
2476
+ var n = 0;
2477
+ outer: do {
2478
+ for (var i = 0, e = chunk.children.length; i < e; ++i) {
2479
+ var child = chunk.children[i], ch = child.height;
2480
+ if (h < ch) { chunk = child; continue outer; }
2481
+ h -= ch;
2482
+ n += child.chunkSize();
2483
+ }
2484
+ return n;
2485
+ } while (!chunk.lines);
2486
+ for (var i = 0, e = chunk.lines.length; i < e; ++i) {
2487
+ var line = chunk.lines[i], lh = line.height;
2488
+ if (h < lh) break;
2489
+ h -= lh;
2490
+ }
2491
+ return n + i;
2492
+ }
2493
+ function heightAtLine(chunk, n) {
2494
+ var h = 0;
2495
+ outer: do {
2496
+ for (var i = 0, e = chunk.children.length; i < e; ++i) {
2497
+ var child = chunk.children[i], sz = child.chunkSize();
2498
+ if (n < sz) { chunk = child; continue outer; }
2499
+ n -= sz;
2500
+ h += child.height;
2501
+ }
2502
+ return h;
2503
+ } while (!chunk.lines);
2504
+ for (var i = 0; i < n; ++i) h += chunk.lines[i].height;
2505
+ return h;
2506
+ }
2507
+
1932
2508
  // The history object 'chunks' changes that are made close together
1933
2509
  // and at almost the same time into bigger undoable units.
1934
2510
  function History() {
@@ -1978,6 +2554,10 @@ var CodeMirror = (function() {
1978
2554
  else e.cancelBubble = true;
1979
2555
  }
1980
2556
  function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
2557
+ CodeMirror.e_stop = e_stop;
2558
+ CodeMirror.e_preventDefault = e_preventDefault;
2559
+ CodeMirror.e_stopPropagation = e_stopPropagation;
2560
+
1981
2561
  function e_target(e) {return e.target || e.srcElement;}
1982
2562
  function e_button(e) {
1983
2563
  if (e.which) return e.which;
@@ -1989,39 +2569,33 @@ var CodeMirror = (function() {
1989
2569
  // Event handler registration. If disconnect is true, it'll return a
1990
2570
  // function that unregisters the handler.
1991
2571
  function connect(node, type, handler, disconnect) {
1992
- function wrapHandler(event) {handler(event || window.event);}
1993
2572
  if (typeof node.addEventListener == "function") {
1994
- node.addEventListener(type, wrapHandler, false);
1995
- if (disconnect) return function() {node.removeEventListener(type, wrapHandler, false);};
2573
+ node.addEventListener(type, handler, false);
2574
+ if (disconnect) return function() {node.removeEventListener(type, handler, false);};
1996
2575
  }
1997
2576
  else {
2577
+ var wrapHandler = function(event) {handler(event || window.event);};
1998
2578
  node.attachEvent("on" + type, wrapHandler);
1999
2579
  if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);};
2000
2580
  }
2001
2581
  }
2582
+ CodeMirror.connect = connect;
2002
2583
 
2003
2584
  function Delayed() {this.id = null;}
2004
2585
  Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
2005
2586
 
2006
- // Some IE versions don't preserve whitespace when setting the
2007
- // innerHTML of a PRE tag.
2008
- var badInnerHTML = (function() {
2009
- var pre = document.createElement("pre");
2010
- pre.innerHTML = " "; return !pre.innerHTML;
2011
- })();
2012
-
2013
2587
  // Detect drag-and-drop
2014
- var dragAndDrop = (function() {
2588
+ var dragAndDrop = function() {
2015
2589
  // IE8 has ondragstart and ondrop properties, but doesn't seem to
2016
2590
  // actually support ondragstart the way it's supposed to work.
2017
2591
  if (/MSIE [1-8]\b/.test(navigator.userAgent)) return false;
2018
2592
  var div = document.createElement('div');
2019
- return "ondragstart" in div && "ondrop" in div;
2020
- })();
2593
+ return "draggable" in div;
2594
+ }();
2021
2595
 
2022
2596
  var gecko = /gecko\/\d{7}/i.test(navigator.userAgent);
2023
2597
  var ie = /MSIE \d/.test(navigator.userAgent);
2024
- var safari = /Apple Computer/.test(navigator.vendor);
2598
+ var webkit = /WebKit\//.test(navigator.userAgent);
2025
2599
 
2026
2600
  var lineSep = "\n";
2027
2601
  // Feature-detect whether newlines in textareas are converted to \r\n
@@ -2031,15 +2605,9 @@ var CodeMirror = (function() {
2031
2605
  if (te.value.indexOf("\r") > -1) lineSep = "\r\n";
2032
2606
  }());
2033
2607
 
2034
- var tabSize = 8;
2035
- var mac = /Mac/.test(navigator.platform);
2036
- var movementKeys = {};
2037
- for (var i = 35; i <= 40; ++i)
2038
- movementKeys[i] = movementKeys["c" + i] = true;
2039
-
2040
2608
  // Counts the column offset in a string, taking tabs into account.
2041
2609
  // Used mostly to find indentation.
2042
- function countColumn(string, end) {
2610
+ function countColumn(string, end, tabSize) {
2043
2611
  if (end == null) {
2044
2612
  end = string.search(/[^\s\u00a0]/);
2045
2613
  if (end == -1) end = string.length;
@@ -2055,21 +2623,44 @@ var CodeMirror = (function() {
2055
2623
  if (elt.currentStyle) return elt.currentStyle;
2056
2624
  return window.getComputedStyle(elt, null);
2057
2625
  }
2626
+
2058
2627
  // Find the position of an element by following the offsetParent chain.
2059
2628
  // If screen==true, it returns screen (rather than page) coordinates.
2060
2629
  function eltOffset(node, screen) {
2061
- var doc = node.ownerDocument.body;
2062
- var x = 0, y = 0, skipDoc = false;
2630
+ var bod = node.ownerDocument.body;
2631
+ var x = 0, y = 0, skipBody = false;
2063
2632
  for (var n = node; n; n = n.offsetParent) {
2064
- x += n.offsetLeft; y += n.offsetTop;
2633
+ var ol = n.offsetLeft, ot = n.offsetTop;
2634
+ // Firefox reports weird inverted offsets when the body has a border.
2635
+ if (n == bod) { x += Math.abs(ol); y += Math.abs(ot); }
2636
+ else { x += ol, y += ot; }
2065
2637
  if (screen && computedStyle(n).position == "fixed")
2066
- skipDoc = true;
2638
+ skipBody = true;
2067
2639
  }
2068
- var e = screen && !skipDoc ? null : doc;
2640
+ var e = screen && !skipBody ? null : bod;
2069
2641
  for (var n = node.parentNode; n != e; n = n.parentNode)
2070
2642
  if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;}
2071
2643
  return {left: x, top: y};
2072
2644
  }
2645
+ // Use the faster and saner getBoundingClientRect method when possible.
2646
+ if (document.documentElement.getBoundingClientRect != null) eltOffset = function(node, screen) {
2647
+ // Take the parts of bounding client rect that we are interested in so we are able to edit if need be,
2648
+ // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page)
2649
+ try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; }
2650
+ catch(e) { box = {top: 0, left: 0}; }
2651
+ if (!screen) {
2652
+ // Get the toplevel scroll, working around browser differences.
2653
+ if (window.pageYOffset == null) {
2654
+ var t = document.documentElement || document.body.parentNode;
2655
+ if (t.scrollTop == null) t = document.body;
2656
+ box.top += t.scrollTop; box.left += t.scrollLeft;
2657
+ } else {
2658
+ box.top += window.pageYOffset; box.left += window.pageXOffset;
2659
+ }
2660
+ }
2661
+ return box;
2662
+ };
2663
+
2073
2664
  // Get a node's text content.
2074
2665
  function eltText(node) {
2075
2666
  return node.textContent || node.innerText || node.nodeValue || "";
@@ -2080,11 +2671,25 @@ var CodeMirror = (function() {
2080
2671
  function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
2081
2672
  function copyPos(x) {return {line: x.line, ch: x.ch};}
2082
2673
 
2083
- var escapeElement = document.createElement("div");
2674
+ var escapeElement = document.createElement("pre");
2084
2675
  function htmlEscape(str) {
2085
- escapeElement.innerText = escapeElement.textContent = str;
2676
+ escapeElement.textContent = str;
2086
2677
  return escapeElement.innerHTML;
2087
2678
  }
2679
+ // Recent (late 2011) Opera betas insert bogus newlines at the start
2680
+ // of the textContent, so we strip those.
2681
+ if (htmlEscape("a") == "\na")
2682
+ htmlEscape = function(str) {
2683
+ escapeElement.textContent = str;
2684
+ return escapeElement.innerHTML.slice(1);
2685
+ };
2686
+ // Some IEs don't preserve tabs through innerHTML
2687
+ else if (htmlEscape("\t") != "\t")
2688
+ htmlEscape = function(str) {
2689
+ escapeElement.innerHTML = "";
2690
+ escapeElement.appendChild(document.createTextNode(str));
2691
+ return escapeElement.innerHTML;
2692
+ };
2088
2693
  CodeMirror.htmlEscape = htmlEscape;
2089
2694
 
2090
2695
  // Used to position the cursor after an undo/redo by finding the
@@ -2103,95 +2708,54 @@ var CodeMirror = (function() {
2103
2708
  if (collection[i] == elt) return i;
2104
2709
  return -1;
2105
2710
  }
2711
+ function isWordChar(ch) {
2712
+ return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase();
2713
+ }
2106
2714
 
2107
2715
  // See if "".split is the broken IE version, if so, provide an
2108
2716
  // alternative way to split lines.
2109
- var splitLines, selRange, setSelRange;
2110
- if ("\n\nb".split(/\n/).length != 3)
2111
- splitLines = function(string) {
2112
- var pos = 0, nl, result = [];
2113
- while ((nl = string.indexOf("\n", pos)) > -1) {
2114
- result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl));
2115
- pos = nl + 1;
2116
- }
2117
- result.push(string.slice(pos));
2118
- return result;
2119
- };
2120
- else
2121
- splitLines = function(string){return string.split(/\r?\n/);};
2717
+ var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
2718
+ var pos = 0, nl, result = [];
2719
+ while ((nl = string.indexOf("\n", pos)) > -1) {
2720
+ result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl));
2721
+ pos = nl + 1;
2722
+ }
2723
+ result.push(string.slice(pos));
2724
+ return result;
2725
+ } : function(string){return string.split(/\r?\n/);};
2122
2726
  CodeMirror.splitLines = splitLines;
2123
2727
 
2124
- // Sane model of finding and setting the selection in a textarea
2125
- if (window.getSelection) {
2126
- selRange = function(te) {
2127
- try {return {start: te.selectionStart, end: te.selectionEnd};}
2128
- catch(e) {return null;}
2129
- };
2130
- if (safari)
2131
- // On Safari, selection set with setSelectionRange are in a sort
2132
- // of limbo wrt their anchor. If you press shift-left in them,
2133
- // the anchor is put at the end, and the selection expanded to
2134
- // the left. If you press shift-right, the anchor ends up at the
2135
- // front. This is not what CodeMirror wants, so it does a
2136
- // spurious modify() call to get out of limbo.
2137
- setSelRange = function(te, start, end) {
2138
- if (start == end)
2139
- te.setSelectionRange(start, end);
2140
- else {
2141
- te.setSelectionRange(start, end - 1);
2142
- window.getSelection().modify("extend", "forward", "character");
2143
- }
2144
- };
2145
- else
2146
- setSelRange = function(te, start, end) {
2147
- try {te.setSelectionRange(start, end);}
2148
- catch(e) {} // Fails on Firefox when textarea isn't part of the document
2149
- };
2150
- }
2151
- // IE model. Don't ask.
2152
- else {
2153
- selRange = function(te) {
2154
- try {var range = te.ownerDocument.selection.createRange();}
2155
- catch(e) {return null;}
2156
- if (!range || range.parentElement() != te) return null;
2157
- var val = te.value, len = val.length, localRange = te.createTextRange();
2158
- localRange.moveToBookmark(range.getBookmark());
2159
- var endRange = te.createTextRange();
2160
- endRange.collapse(false);
2161
-
2162
- if (localRange.compareEndPoints("StartToEnd", endRange) > -1)
2163
- return {start: len, end: len};
2164
-
2165
- var start = -localRange.moveStart("character", -len);
2166
- for (var i = val.indexOf("\r"); i > -1 && i < start; i = val.indexOf("\r", i+1), start++) {}
2167
-
2168
- if (localRange.compareEndPoints("EndToEnd", endRange) > -1)
2169
- return {start: start, end: len};
2170
-
2171
- var end = -localRange.moveEnd("character", -len);
2172
- for (var i = val.indexOf("\r"); i > -1 && i < end; i = val.indexOf("\r", i+1), end++) {}
2173
- return {start: start, end: end};
2174
- };
2175
- setSelRange = function(te, start, end) {
2176
- var range = te.createTextRange();
2177
- range.collapse(true);
2178
- var endrange = range.duplicate();
2179
- var newlines = 0, txt = te.value;
2180
- for (var pos = txt.indexOf("\n"); pos > -1 && pos < start; pos = txt.indexOf("\n", pos + 1))
2181
- ++newlines;
2182
- range.move("character", start - newlines);
2183
- for (; pos > -1 && pos < end; pos = txt.indexOf("\n", pos + 1))
2184
- ++newlines;
2185
- endrange.move("character", end - newlines);
2186
- range.setEndPoint("EndToEnd", endrange);
2187
- range.select();
2188
- };
2189
- }
2728
+ var hasSelection = window.getSelection ? function(te) {
2729
+ try { return te.selectionStart != te.selectionEnd; }
2730
+ catch(e) { return false; }
2731
+ } : function(te) {
2732
+ try {var range = te.ownerDocument.selection.createRange();}
2733
+ catch(e) {}
2734
+ if (!range || range.parentElement() != te) return false;
2735
+ return range.compareEndPoints("StartToEnd", range) != 0;
2736
+ };
2190
2737
 
2191
2738
  CodeMirror.defineMode("null", function() {
2192
2739
  return {token: function(stream) {stream.skipToEnd();}};
2193
2740
  });
2194
2741
  CodeMirror.defineMIME("text/plain", "null");
2195
2742
 
2743
+ var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
2744
+ 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
2745
+ 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
2746
+ 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 186: ";", 187: "=", 188: ",",
2747
+ 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'", 63276: "PageUp",
2748
+ 63277: "PageDown", 63275: "End", 63273: "Home", 63234: "Left", 63232: "Up", 63235: "Right",
2749
+ 63233: "Down", 63302: "Insert", 63272: "Delete"};
2750
+ CodeMirror.keyNames = keyNames;
2751
+ (function() {
2752
+ // Number keys
2753
+ for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i);
2754
+ // Alphabetic keys
2755
+ for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
2756
+ // Function keys
2757
+ for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
2758
+ })();
2759
+
2196
2760
  return CodeMirror;
2197
2761
  })();