luca 0.8.599 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (149) hide show
  1. data/.gitignore +3 -0
  2. data/.rvmrc +1 -0
  3. data/CHANGELOG +51 -2
  4. data/README.md +10 -247
  5. data/ROADMAP +6 -2
  6. data/app.rb +16 -2
  7. data/assets/javascripts/dependencies/bootstrap.min.js +7 -1
  8. data/assets/javascripts/dependencies/codemirror-coffeescript.js +347 -0
  9. data/assets/javascripts/dependencies/codemirror-css.js +124 -0
  10. data/assets/javascripts/dependencies/codemirror-html.js +410 -0
  11. data/assets/javascripts/dependencies/codemirror-javascript.js +361 -0
  12. data/assets/javascripts/dependencies/codemirror-less.js +232 -0
  13. data/assets/javascripts/dependencies/codemirror-vim.js +500 -0
  14. data/assets/javascripts/dependencies/codemirror.js +3076 -0
  15. data/assets/javascripts/dependencies.coffee +0 -1
  16. data/assets/javascripts/luca-ui-base.coffee +10 -3
  17. data/assets/javascripts/luca-ui-bootstrap.js +1 -0
  18. data/assets/javascripts/luca-ui-development-tools.coffee +9 -0
  19. data/assets/javascripts/luca-ui.coffee +6 -1
  20. data/assets/javascripts/sandbox/application.coffee +51 -0
  21. data/assets/javascripts/sandbox/router.coffee +14 -0
  22. data/assets/javascripts/sandbox/templates/main.luca +33 -0
  23. data/assets/javascripts/sandbox/templates/sandbox/navigation.luca +1 -0
  24. data/assets/javascripts/sandbox/templates/sandbox.luca +1 -0
  25. data/assets/javascripts/sandbox/views/top_navigation.coffee +4 -0
  26. data/assets/javascripts/sandbox.coffee +2 -2
  27. data/assets/stylesheets/bootstrap.min.css +395 -297
  28. data/assets/stylesheets/codemirror-blackboard.css +25 -0
  29. data/assets/stylesheets/codemirror-monokai.css +33 -0
  30. data/assets/stylesheets/codemirror.css +126 -0
  31. data/assets/stylesheets/luca-ui-bootstrap.css +0 -1
  32. data/assets/stylesheets/luca-ui-development-tools.css +5 -0
  33. data/assets/stylesheets/sandbox/sandbox.scss +1 -3
  34. data/assets/stylesheets/themes/amelia-bootstrap.css +826 -0
  35. data/assets/stylesheets/themes/slate-bootstrap.css +797 -0
  36. data/assets/stylesheets/themes/superhero-bootstrap.css +830 -0
  37. data/lib/luca/code_browser.rb +55 -0
  38. data/lib/luca/rails/version.rb +1 -1
  39. data/lib/luca/rails.rb +1 -0
  40. data/spec/components/fields/checkbox_array_spec.coffee +46 -0
  41. data/spec/components/form_view_spec.coffee +10 -4
  42. data/spec/containers/card_view_spec.coffee +7 -0
  43. data/spec/core/collection_spec.coffee +58 -4
  44. data/spec/core/container_spec.coffee +6 -6
  45. data/spec/core/view_spec.coffee +93 -7
  46. data/spec/framework_spec.coffee +15 -12
  47. data/src/components/application.coffee +126 -18
  48. data/src/components/base_toolbar.coffee +2 -2
  49. data/src/components/collection_loader_view.coffee +1 -2
  50. data/src/components/collection_view.coffee +77 -0
  51. data/src/components/controller.coffee +1 -4
  52. data/src/components/fields/button_field.coffee +1 -1
  53. data/src/components/fields/checkbox_array.coffee +2 -2
  54. data/src/components/fields/checkbox_field.coffee +3 -1
  55. data/src/components/fields/file_upload_field.coffee +1 -1
  56. data/src/components/fields/hidden_field.coffee +1 -1
  57. data/src/components/fields/select_field.coffee +1 -1
  58. data/src/components/fields/text_area_field.coffee +1 -1
  59. data/src/components/fields/text_field.coffee +10 -6
  60. data/src/components/fields/type_ahead_field.coffee +18 -5
  61. data/src/components/form_button_toolbar.coffee +1 -2
  62. data/src/components/form_view.coffee +44 -62
  63. data/src/components/grid_view.coffee +27 -20
  64. data/src/components/load_mask.coffee +3 -0
  65. data/src/components/nav_bar.coffee +26 -0
  66. data/src/components/record_manager.coffee +1 -3
  67. data/src/components/router.coffee +1 -1
  68. data/src/components/template.coffee +3 -15
  69. data/src/components/toolbar_dialog.coffee +25 -0
  70. data/src/containers/card_view.coffee +22 -23
  71. data/src/containers/column_view.coffee +1 -6
  72. data/src/containers/modal_view.coffee +20 -71
  73. data/src/containers/panel_toolbar.coffee +156 -0
  74. data/src/containers/panel_view.coffee +1 -1
  75. data/src/containers/split_view.coffee +1 -3
  76. data/src/containers/tab_view.coffee +29 -29
  77. data/src/containers/viewport.coffee +38 -3
  78. data/src/core/collection.coffee +80 -48
  79. data/src/core/container.coffee +153 -72
  80. data/src/core/core.coffee +181 -0
  81. data/src/core/field.coffee +4 -2
  82. data/src/core/model.coffee +1 -1
  83. data/src/core/observer.coffee +3 -3
  84. data/src/core/panel.coffee +143 -0
  85. data/src/core/registry.coffee +104 -0
  86. data/src/core/util.coffee +82 -0
  87. data/src/core/view.coffee +158 -85
  88. data/src/framework.coffee +112 -178
  89. data/src/index.coffee +0 -255
  90. data/src/managers/collection_manager.coffee +1 -0
  91. data/src/samples/definition.coffee +49 -0
  92. data/src/stylesheets/base.scss +0 -78
  93. data/src/stylesheets/components/form_view.scss +8 -3
  94. data/src/stylesheets/components/grid_view.scss +3 -7
  95. data/src/stylesheets/components/load_mask.scss +14 -0
  96. data/src/stylesheets/components/toolbar.scss +0 -15
  97. data/src/stylesheets/containers/container.scss +14 -2
  98. data/src/stylesheets/containers/panels.scss +23 -0
  99. data/src/stylesheets/tools/class_browser.scss +32 -0
  100. data/src/stylesheets/tools/code_editor.scss +24 -0
  101. data/src/stylesheets/tools/component_tester.scss +8 -0
  102. data/src/stylesheets/tools/console.scss +26 -0
  103. data/src/templates/components/collection_loader_view.luca +1 -1
  104. data/src/templates/components/form_view.luca +2 -13
  105. data/src/templates/components/grid_view.luca +0 -2
  106. data/src/templates/components/load_mask.luca +3 -0
  107. data/src/templates/components/nav_bar.luca +2 -0
  108. data/src/templates/containers/tab_view.luca +1 -0
  109. data/src/templates/fields/text_field.luca +4 -1
  110. data/src/tools/class_browser.coffee +39 -0
  111. data/src/tools/code_editor.coffee +258 -0
  112. data/src/tools/code_mirror_field.coffee +57 -0
  113. data/src/tools/coffee_script_editor.coffee +60 -0
  114. data/src/tools/collection_inspector.coffee +4 -0
  115. data/src/tools/component_tester.coffee +472 -0
  116. data/src/tools/components/class_browser_detail.coffee +10 -0
  117. data/src/tools/components/class_browser_list.coffee +74 -0
  118. data/src/tools/console.coffee +147 -0
  119. data/src/tools/development_console.coffee +147 -0
  120. data/src/tools/models/components.coffee +63 -0
  121. data/src/tools/templates/component_tester/help.luca +14 -0
  122. data/vendor/assets/javascripts/luca-ui-base.js +1389 -611
  123. data/vendor/assets/javascripts/luca-ui-bootstrap.js +9 -0
  124. data/vendor/assets/javascripts/luca-ui-development-tools.js +18719 -0
  125. data/vendor/assets/javascripts/luca-ui-spec.js +2065 -878
  126. data/vendor/assets/javascripts/luca-ui.js +1759 -852
  127. data/vendor/assets/javascripts/luca-ui.min.js +3 -3
  128. data/vendor/assets/stylesheets/luca-ui-bootstrap.css +494 -440
  129. data/vendor/assets/stylesheets/luca-ui-development-tools.css +224 -0
  130. data/vendor/assets/stylesheets/luca-ui-spec.css +99 -140
  131. data/vendor/assets/stylesheets/luca-ui.css +99 -140
  132. data/views/index.erb +6 -3
  133. metadata +60 -18
  134. data/assets/javascripts/dependencies/jquery-console.js +0 -649
  135. data/assets/javascripts/development-console.coffee +0 -2
  136. data/assets/javascripts/sandbox/sandbox.coffee +0 -16
  137. data/assets/javascripts/sandbox/templates/features/collection_helpers.luca +0 -33
  138. data/assets/javascripts/sandbox/templates/features/form_demo_code.luca +0 -48
  139. data/assets/javascripts/sandbox/templates/features/grid_demo_code.luca +0 -24
  140. data/assets/javascripts/sandbox/templates/features/introduction.luca +0 -11
  141. data/assets/javascripts/sandbox/templates/features/view_helpers.luca +0 -43
  142. data/assets/javascripts/sandbox/templates/navigation.luca +0 -8
  143. data/assets/javascripts/sandbox/views/form_demo.coffee +0 -47
  144. data/assets/javascripts/sandbox/views/grid_demo.coffee +0 -23
  145. data/assets/javascripts/sandbox/views/pages/collection_events_sample.coffee +0 -1
  146. data/assets/javascripts/sandbox/views/pages/pages_controller.coffee +0 -38
  147. data/src/components/collection_inspector.coffee +0 -2
  148. data/src/components/development_console.coffee +0 -59
  149. data/src/stylesheets/components/development_console.scss +0 -47
@@ -0,0 +1,500 @@
1
+ // Supported keybindings:
2
+ //
3
+ // Cursor movement:
4
+ // h, j, k, l
5
+ // e, E, w, W, b, B
6
+ // Ctrl-f, Ctrl-b
7
+ // Ctrl-n, Ctrl-p
8
+ // $, ^, 0
9
+ // G
10
+ // ge, gE
11
+ // gg
12
+ // f<char>, F<char>, t<char>, T<char>
13
+ // Ctrl-o, Ctrl-i TODO (FIXME - Ctrl-O wont work in Chrome)
14
+ // /, ?, n, N TODO (does not work)
15
+ // #, * TODO
16
+ //
17
+ // Entering insert mode:
18
+ // i, I, a, A, o, O
19
+ // s
20
+ // ce, cb (without support for number of actions like c3e - TODO)
21
+ // cc
22
+ // S, C TODO
23
+ // cf<char>, cF<char>, ct<char>, cT<char>
24
+ //
25
+ // Deleting text:
26
+ // x, X
27
+ // J
28
+ // dd, D
29
+ // de, db (without support for number of actions like d3e - TODO)
30
+ // df<char>, dF<char>, dt<char>, dT<char>
31
+ //
32
+ // Yanking and pasting:
33
+ // yy, Y
34
+ // p, P
35
+ // p'<char> TODO - test
36
+ // y'<char> TODO - test
37
+ // m<char> TODO - test
38
+ //
39
+ // Changing text in place:
40
+ // ~
41
+ // r<char>
42
+ //
43
+ // Visual mode:
44
+ // v, V TODO
45
+ //
46
+ // Misc:
47
+ // . TODO
48
+ //
49
+
50
+
51
+ (function() {
52
+ var count = "";
53
+ var sdir = "f";
54
+ var buf = "";
55
+ var yank = 0;
56
+ var mark = [];
57
+ function emptyBuffer() { buf = ""; }
58
+ function pushInBuffer(str) { buf += str; };
59
+ function pushCountDigit(digit) { return function(cm) {count += digit;} }
60
+ function popCount() { var i = parseInt(count); count = ""; return i || 1; }
61
+ function iterTimes(func) {
62
+ for (var i = 0, c = popCount(); i < c; ++i) func(i, i == c - 1);
63
+ }
64
+ function countTimes(func) {
65
+ if (typeof func == "string") func = CodeMirror.commands[func];
66
+ return function(cm) { iterTimes(function () { func(cm); }) };
67
+ }
68
+
69
+ function iterObj(o, f) {
70
+ for (var prop in o) if (o.hasOwnProperty(prop)) f(prop, o[prop]);
71
+ }
72
+ function iterList(l, f) {
73
+ for (var i in l) f(l[i]);
74
+ }
75
+ function toLetter(ch) {
76
+ // T -> t, Shift-T -> T, '*' -> *, "Space" -> " "
77
+ if (ch.slice(0, 6) == "Shift-") {
78
+ return ch.slice(0, 1);
79
+ } else {
80
+ if (ch == "Space") return " ";
81
+ if (ch.length == 3 && ch[0] == "'" && ch[2] == "'") return ch[1];
82
+ return ch.toLowerCase();
83
+ }
84
+ }
85
+ var SPECIAL_SYMBOLS = "~`!@#$%^&*()_-+=[{}]\\|/?.,<>:;\"\'1234567890";
86
+ function toCombo(ch) {
87
+ // t -> T, T -> Shift-T, * -> '*', " " -> "Space"
88
+ if (ch == " ") return "Space";
89
+ var specialIdx = SPECIAL_SYMBOLS.indexOf(ch);
90
+ if (specialIdx != -1) return "'" + ch + "'";
91
+ if (ch.toLowerCase() == ch) return ch.toUpperCase();
92
+ return "Shift-" + ch.toUpperCase();
93
+ }
94
+
95
+ var word = [/\w/, /[^\w\s]/], bigWord = [/\S/];
96
+ function findWord(line, pos, dir, regexps) {
97
+ var stop = 0, next = -1;
98
+ if (dir > 0) { stop = line.length; next = 0; }
99
+ var start = stop, end = stop;
100
+ // Find bounds of next one.
101
+ outer: for (; pos != stop; pos += dir) {
102
+ for (var i = 0; i < regexps.length; ++i) {
103
+ if (regexps[i].test(line.charAt(pos + next))) {
104
+ start = pos;
105
+ for (; pos != stop; pos += dir) {
106
+ if (!regexps[i].test(line.charAt(pos + next))) break;
107
+ }
108
+ end = pos;
109
+ break outer;
110
+ }
111
+ }
112
+ }
113
+ return {from: Math.min(start, end), to: Math.max(start, end)};
114
+ }
115
+ function moveToWord(cm, regexps, dir, where) {
116
+ var cur = cm.getCursor(), ch = cur.ch, line = cm.getLine(cur.line), word;
117
+ while (true) {
118
+ word = findWord(line, ch, dir, regexps);
119
+ ch = word[where == "end" ? "to" : "from"];
120
+ if (ch == cur.ch && word.from != word.to) ch = word[dir < 0 ? "from" : "to"];
121
+ else break;
122
+ }
123
+ cm.setCursor(cur.line, word[where == "end" ? "to" : "from"], true);
124
+ }
125
+ function joinLineNext(cm) {
126
+ var cur = cm.getCursor(), ch = cur.ch, line = cm.getLine(cur.line);
127
+ CodeMirror.commands.goLineEnd(cm);
128
+ if (cur.line != cm.lineCount()) {
129
+ CodeMirror.commands.goLineEnd(cm);
130
+ cm.replaceSelection(" ", "end");
131
+ CodeMirror.commands.delCharRight(cm);
132
+ }
133
+ }
134
+ function delTillMark(cm, cHar) {
135
+ var i = mark[cHar];
136
+ if (i === undefined) {
137
+ // console.log("Mark not set"); // TODO - show in status bar
138
+ return;
139
+ }
140
+ var l = cm.getCursor().line, start = i > l ? l : i, end = i > l ? i : l;
141
+ cm.setCursor(start);
142
+ for (var c = start; c <= end; c++) {
143
+ pushInBuffer("\n"+cm.getLine(start));
144
+ cm.removeLine(start);
145
+ }
146
+ }
147
+ function yankTillMark(cm, cHar) {
148
+ var i = mark[cHar];
149
+ if (i === undefined) {
150
+ // console.log("Mark not set"); // TODO - show in status bar
151
+ return;
152
+ }
153
+ var l = cm.getCursor().line, start = i > l ? l : i, end = i > l ? i : l;
154
+ for (var c = start; c <= end; c++) {
155
+ pushInBuffer("\n"+cm.getLine(c));
156
+ }
157
+ cm.setCursor(start);
158
+ }
159
+ function goLineStartText(cm) {
160
+ // Go to the start of the line where the text begins, or the end for whitespace-only lines
161
+ var cur = cm.getCursor(), firstNonWS = cm.getLine(cur.line).search(/\S/);
162
+ cm.setCursor(cur.line, firstNonWS == -1 ? line.length : firstNonWS, true);
163
+ }
164
+
165
+ function charIdxInLine(cm, cHar, motion_options) {
166
+ // Search for cHar in line.
167
+ // motion_options: {forward, inclusive}
168
+ // If inclusive = true, include it too.
169
+ // If forward = true, search forward, else search backwards.
170
+ // If char is not found on this line, do nothing
171
+ var cur = cm.getCursor(), line = cm.getLine(cur.line), idx;
172
+ var ch = toLetter(cHar), mo = motion_options;
173
+ if (mo.forward) {
174
+ idx = line.indexOf(ch, cur.ch + 1);
175
+ if (idx != -1 && mo.inclusive) idx += 1;
176
+ } else {
177
+ idx = line.lastIndexOf(ch, cur.ch);
178
+ if (idx != -1 && !mo.inclusive) idx += 1;
179
+ }
180
+ return idx;
181
+ }
182
+
183
+ function moveTillChar(cm, cHar, motion_options) {
184
+ // Move to cHar in line, as found by charIdxInLine.
185
+ var idx = charIdxInLine(cm, cHar, motion_options), cur = cm.getCursor();
186
+ if (idx != -1) cm.setCursor({line: cur.line, ch: idx});
187
+ }
188
+
189
+ function delTillChar(cm, cHar, motion_options) {
190
+ // delete text in this line, untill cHar is met,
191
+ // as found by charIdxInLine.
192
+ // If char is not found on this line, do nothing
193
+ var idx = charIdxInLine(cm, cHar, motion_options);
194
+ var cur = cm.getCursor();
195
+ if (idx !== -1) {
196
+ if (motion_options.forward) {
197
+ cm.replaceRange("", {line: cur.line, ch: cur.ch}, {line: cur.line, ch: idx});
198
+ } else {
199
+ cm.replaceRange("", {line: cur.line, ch: idx}, {line: cur.line, ch: cur.ch});
200
+ }
201
+ }
202
+ }
203
+
204
+ function enterInsertMode(cm) {
205
+ // enter insert mode: switch mode and cursor
206
+ if (!cm) console.log("call enterInsertMode with 'cm' as an argument");
207
+ popCount();
208
+ cm.setOption("keyMap", "vim-insert");
209
+ }
210
+
211
+ // main keymap
212
+ var map = CodeMirror.keyMap.vim = {
213
+ // Pipe (|); TODO: should be *screen* chars, so need a util function to turn tabs into spaces?
214
+ "'|'": function(cm) {
215
+ cm.setCursor(cm.getCursor().line, popCount() - 1, true);
216
+ },
217
+ "'^'": function(cm) { popCount(); goLineStartText(cm);},
218
+ "A": function(cm) {
219
+ cm.setCursor(cm.getCursor().line, cm.getCursor().ch+1, true);
220
+ enterInsertMode(cm);
221
+ },
222
+ "Shift-A": function(cm) { CodeMirror.commands.goLineEnd(cm); enterInsertMode(cm);},
223
+ "I": function(cm) { enterInsertMode(cm);},
224
+ "Shift-I": function(cm) { goLineStartText(cm); enterInsertMode(cm);},
225
+ "O": function(cm) {
226
+ CodeMirror.commands.goLineEnd(cm);
227
+ CodeMirror.commands.newlineAndIndent(cm);
228
+ enterInsertMode(cm);
229
+ },
230
+ "Shift-O": function(cm) {
231
+ CodeMirror.commands.goLineStart(cm);
232
+ cm.replaceSelection("\n", "start");
233
+ cm.indentLine(cm.getCursor().line);
234
+ enterInsertMode(cm);
235
+ },
236
+ "G": function(cm) { cm.setOption("keyMap", "vim-prefix-g");},
237
+ "Shift-D": function(cm) {
238
+ // commented out verions works, but I left original, cause maybe
239
+ // I don't know vim enouth to see what it does
240
+ /* var cur = cm.getCursor();
241
+ var f = {line: cur.line, ch: cur.ch}, t = {line: cur.line};
242
+ pushInBuffer(cm.getRange(f, t));
243
+ cm.replaceRange("", f, t);
244
+ */
245
+ emptyBuffer();
246
+ mark["Shift-D"] = cm.getCursor(false).line;
247
+ cm.setCursor(cm.getCursor(true).line);
248
+ delTillMark(cm,"Shift-D"); mark = [];
249
+ },
250
+
251
+ "S": function (cm) {
252
+ countTimes(function (_cm) {
253
+ CodeMirror.commands.delCharRight(_cm);
254
+ })(cm);
255
+ enterInsertMode(cm);
256
+ },
257
+ "M": function(cm) {cm.setOption("keyMap", "vim-prefix-m"); mark = [];},
258
+ "Y": function(cm) {cm.setOption("keyMap", "vim-prefix-y"); emptyBuffer(); yank = 0;},
259
+ "Shift-Y": function(cm) {
260
+ emptyBuffer();
261
+ mark["Shift-D"] = cm.getCursor(false).line;
262
+ cm.setCursor(cm.getCursor(true).line);
263
+ yankTillMark(cm,"Shift-D"); mark = [];
264
+ },
265
+ "/": function(cm) {var f = CodeMirror.commands.find; f && f(cm); sdir = "f";},
266
+ "'?'": function(cm) {
267
+ var f = CodeMirror.commands.find;
268
+ if (f) { f(cm); CodeMirror.commands.findPrev(cm); sdir = "r"; }
269
+ },
270
+ "N": function(cm) {
271
+ var fn = CodeMirror.commands.findNext;
272
+ if (fn) sdir != "r" ? fn(cm) : CodeMirror.commands.findPrev(cm);
273
+ },
274
+ "Shift-N": function(cm) {
275
+ var fn = CodeMirror.commands.findNext;
276
+ if (fn) sdir != "r" ? CodeMirror.commands.findPrev(cm) : fn.findNext(cm);
277
+ },
278
+ "Shift-G": function(cm) {
279
+ count == "" ? cm.setCursor(cm.lineCount()) : cm.setCursor(parseInt(count)-1);
280
+ popCount();
281
+ CodeMirror.commands.goLineStart(cm);
282
+ },
283
+ "'$'": function (cm) {
284
+ countTimes("goLineEnd")(cm);
285
+ if (cm.getCursor().ch) CodeMirror.commands.goColumnLeft(cm);
286
+ },
287
+ nofallthrough: true, style: "fat-cursor"
288
+ };
289
+
290
+ // standard mode switching
291
+ iterList(["d", "t", "T", "f", "F", "c", "r"],
292
+ function (ch) {
293
+ CodeMirror.keyMap.vim[toCombo(ch)] = function (cm) {
294
+ cm.setOption("keyMap", "vim-prefix-" + ch);
295
+ emptyBuffer();
296
+ };
297
+ });
298
+
299
+ function addCountBindings(keyMap) {
300
+ // Add bindings for number keys
301
+ keyMap["0"] = function(cm) {
302
+ count.length > 0 ? pushCountDigit("0")(cm) : CodeMirror.commands.goLineStart(cm);
303
+ };
304
+ for (var i = 1; i < 10; ++i) keyMap[i] = pushCountDigit(i);
305
+ }
306
+ addCountBindings(CodeMirror.keyMap.vim);
307
+
308
+ // main num keymap
309
+ // Add bindings that are influenced by number keys
310
+ iterObj({
311
+ "H": "goColumnLeft", "L": "goColumnRight", "J": "goLineDown",
312
+ "K": "goLineUp", "Left": "goColumnLeft", "Right": "goColumnRight",
313
+ "Down": "goLineDown", "Up": "goLineUp", "Backspace": "goCharLeft",
314
+ "Space": "goCharRight",
315
+ "B": function(cm) {moveToWord(cm, word, -1, "end");},
316
+ "E": function(cm) {moveToWord(cm, word, 1, "end");},
317
+ "W": function(cm) {moveToWord(cm, word, 1, "start");},
318
+ "Shift-B": function(cm) {moveToWord(cm, bigWord, -1, "end");},
319
+ "Shift-E": function(cm) {moveToWord(cm, bigWord, 1, "end");},
320
+ "Shift-W": function(cm) {moveToWord(cm, bigWord, 1, "start");},
321
+ "X": function(cm) {CodeMirror.commands.delCharRight(cm);},
322
+ "P": function(cm) {
323
+ var cur = cm.getCursor().line;
324
+ if (buf!= "") {
325
+ CodeMirror.commands.goLineEnd(cm);
326
+ cm.replaceSelection(buf, "end");
327
+ }
328
+ cm.setCursor(cur+1);
329
+ },
330
+ "Shift-X": function(cm) {CodeMirror.commands.delCharLeft(cm);},
331
+ "Shift-J": function(cm) {joinLineNext(cm);},
332
+ "Shift-P": function(cm) {
333
+ var cur = cm.getCursor().line;
334
+ if (buf!= "") {
335
+ CodeMirror.commands.goLineUp(cm);
336
+ CodeMirror.commands.goLineEnd(cm);
337
+ cm.replaceSelection(buf, "end");
338
+ }
339
+ cm.setCursor(cur+1);
340
+ },
341
+ "'~'": function(cm) {
342
+ var cur = cm.getCursor(), cHar = cm.getRange({line: cur.line, ch: cur.ch}, {line: cur.line, ch: cur.ch+1});
343
+ cHar = cHar != cHar.toLowerCase() ? cHar.toLowerCase() : cHar.toUpperCase();
344
+ cm.replaceRange(cHar, {line: cur.line, ch: cur.ch}, {line: cur.line, ch: cur.ch+1});
345
+ cm.setCursor(cur.line, cur.ch+1);
346
+ },
347
+ "Ctrl-B": function(cm) {CodeMirror.commands.goPageUp(cm);},
348
+ "Ctrl-F": function(cm) {CodeMirror.commands.goPageDown(cm);},
349
+ "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
350
+ "U": "undo", "Ctrl-R": "redo"
351
+ }, function(key, cmd) { map[key] = countTimes(cmd); });
352
+
353
+ // empty key maps
354
+ iterList([
355
+ "vim-prefix-d'",
356
+ "vim-prefix-y'",
357
+ "vim-prefix-df",
358
+ "vim-prefix-dF",
359
+ "vim-prefix-dt",
360
+ "vim-prefix-dT",
361
+ "vim-prefix-c",
362
+ "vim-prefix-cf",
363
+ "vim-prefix-cF",
364
+ "vim-prefix-ct",
365
+ "vim-prefix-cT",
366
+ "vim-prefix-",
367
+ "vim-prefix-f",
368
+ "vim-prefix-F",
369
+ "vim-prefix-t",
370
+ "vim-prefix-T",
371
+ "vim-prefix-r",
372
+ "vim-prefix-m"
373
+ ],
374
+ function (prefix) {
375
+ CodeMirror.keyMap[prefix] = {
376
+ auto: "vim",
377
+ nofallthrough: true
378
+ };
379
+ });
380
+
381
+ CodeMirror.keyMap["vim-prefix-g"] = {
382
+ "E": countTimes(function(cm) { moveToWord(cm, word, -1, "start");}),
383
+ "Shift-E": countTimes(function(cm) { moveToWord(cm, bigWord, -1, "start");}),
384
+ "G": function (cm) { cm.setCursor({line: 0, ch: cm.getCursor().ch});},
385
+ auto: "vim", nofallthrough: true, style: "fat-cursor"
386
+ };
387
+
388
+ CodeMirror.keyMap["vim-prefix-d"] = {
389
+ "D": countTimes(function(cm) {
390
+ pushInBuffer("\n"+cm.getLine(cm.getCursor().line));
391
+ cm.removeLine(cm.getCursor().line);
392
+ }),
393
+ "'": function(cm) {
394
+ cm.setOption("keyMap", "vim-prefix-d'");
395
+ emptyBuffer();
396
+ },
397
+ "E": countTimes("delWordRight"),
398
+ "B": countTimes("delWordLeft"),
399
+ auto: "vim", nofallthrough: true, style: "fat-cursor"
400
+ };
401
+ // FIXME - does not work for bindings like "d3e"
402
+ addCountBindings(CodeMirror.keyMap["vim-prefix-d"]);
403
+
404
+ CodeMirror.keyMap["vim-prefix-c"] = {
405
+ "E": function (cm) {
406
+ countTimes("delWordRight")(cm);
407
+ enterInsertMode(cm);
408
+ },
409
+ "B": function (cm) {
410
+ countTimes("delWordLeft")(cm);
411
+ enterInsertMode(cm);
412
+ },
413
+ "C": function (cm) {
414
+ iterTimes(function (i, last) {
415
+ CodeMirror.commands.deleteLine(cm);
416
+ if (i) {
417
+ CodeMirror.commands.delCharRight(cm);
418
+ if (last) CodeMirror.commands.deleteLine(cm);
419
+ }
420
+ });
421
+ enterInsertMode(cm);
422
+ },
423
+ auto: "vim", nofallthrough: true, style: "fat-cursor"
424
+ };
425
+
426
+ iterList(["vim-prefix-d", "vim-prefix-c", "vim-prefix-"], function (prefix) {
427
+ iterList(["f", "F", "T", "t"],
428
+ function (ch) {
429
+ CodeMirror.keyMap[prefix][toCombo(ch)] = function (cm) {
430
+ cm.setOption("keyMap", prefix + ch);
431
+ emptyBuffer();
432
+ };
433
+ });
434
+ });
435
+
436
+ var MOTION_OPTIONS = {
437
+ "t": {inclusive: false, forward: true},
438
+ "f": {inclusive: true, forward: true},
439
+ "T": {inclusive: false, forward: false},
440
+ "F": {inclusive: true, forward: false}
441
+ };
442
+
443
+ function setupPrefixBindingForKey(m) {
444
+ CodeMirror.keyMap["vim-prefix-m"][m] = function(cm) {
445
+ mark[m] = cm.getCursor().line;
446
+ };
447
+ CodeMirror.keyMap["vim-prefix-d'"][m] = function(cm) {
448
+ delTillMark(cm,m);
449
+ };
450
+ CodeMirror.keyMap["vim-prefix-y'"][m] = function(cm) {
451
+ yankTillMark(cm,m);
452
+ };
453
+ CodeMirror.keyMap["vim-prefix-r"][m] = function (cm) {
454
+ var cur = cm.getCursor();
455
+ cm.replaceRange(toLetter(m),
456
+ {line: cur.line, ch: cur.ch},
457
+ {line: cur.line, ch: cur.ch + 1});
458
+ CodeMirror.commands.goColumnLeft(cm);
459
+ };
460
+ // all commands, related to motions till char in line
461
+ iterObj(MOTION_OPTIONS, function (ch, options) {
462
+ CodeMirror.keyMap["vim-prefix-" + ch][m] = function(cm) {
463
+ moveTillChar(cm, m, options);
464
+ };
465
+ CodeMirror.keyMap["vim-prefix-d" + ch][m] = function(cm) {
466
+ delTillChar(cm, m, options);
467
+ };
468
+ CodeMirror.keyMap["vim-prefix-c" + ch][m] = function(cm) {
469
+ delTillChar(cm, m, options);
470
+ enterInsertMode(cm);
471
+ };
472
+ });
473
+ };
474
+ for (var i = 65; i < 65 + 26; i++) { // uppercase alphabet char codes
475
+ var ch = String.fromCharCode(i);
476
+ setupPrefixBindingForKey(toCombo(ch));
477
+ setupPrefixBindingForKey(toCombo(ch.toLowerCase()));
478
+ }
479
+ iterList(SPECIAL_SYMBOLS, function (ch) {
480
+ setupPrefixBindingForKey(toCombo(ch));
481
+ });
482
+ setupPrefixBindingForKey("Space");
483
+
484
+ CodeMirror.keyMap["vim-prefix-y"] = {
485
+ "Y": countTimes(function(cm) { pushInBuffer("\n"+cm.getLine(cm.getCursor().line+yank)); yank++; }),
486
+ "'": function(cm) {cm.setOption("keyMap", "vim-prefix-y'"); emptyBuffer();},
487
+ auto: "vim", nofallthrough: true, style: "fat-cursor"
488
+ };
489
+
490
+ CodeMirror.keyMap["vim-insert"] = {
491
+ // TODO: override navigation keys so that Esc will cancel automatic indentation from o, O, i_<CR>
492
+ "Esc": function(cm) {
493
+ cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1, true);
494
+ cm.setOption("keyMap", "vim");
495
+ },
496
+ "Ctrl-N": "autocomplete",
497
+ "Ctrl-P": "autocomplete",
498
+ fallthrough: ["default"]
499
+ };
500
+ })();