feather_cms 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +4 -0
  3. data/README.md +46 -0
  4. data/Rakefile +1 -0
  5. data/feather_cms.gemspec +24 -0
  6. data/lib/feather_cms/config.rb +54 -0
  7. data/lib/feather_cms/model.rb +59 -0
  8. data/lib/feather_cms/railtie.rb +13 -0
  9. data/lib/feather_cms/template_cache.rb +52 -0
  10. data/lib/feather_cms/version.rb +3 -0
  11. data/lib/feather_cms/view_helper.rb +20 -0
  12. data/lib/feather_cms.rb +19 -0
  13. data/lib/generators/feather_cms/USAGE +10 -0
  14. data/lib/generators/feather_cms/feather_cms_generator.rb +63 -0
  15. data/lib/generators/feather_cms/templates/bootstrap.css +3992 -0
  16. data/lib/generators/feather_cms/templates/codemirror/codemirror.css +117 -0
  17. data/lib/generators/feather_cms/templates/codemirror/codemirror.js +2909 -0
  18. data/lib/generators/feather_cms/templates/codemirror/feather_cms.js +13 -0
  19. data/lib/generators/feather_cms/templates/codemirror/modes/css.js +124 -0
  20. data/lib/generators/feather_cms/templates/codemirror/modes/htmlmixed.js +83 -0
  21. data/lib/generators/feather_cms/templates/codemirror/modes/javascript.js +360 -0
  22. data/lib/generators/feather_cms/templates/codemirror/modes/xml.js +267 -0
  23. data/lib/generators/feather_cms/templates/codemirror/util/dialog.css +23 -0
  24. data/lib/generators/feather_cms/templates/codemirror/util/dialog.js +63 -0
  25. data/lib/generators/feather_cms/templates/codemirror/util/foldcode.js +186 -0
  26. data/lib/generators/feather_cms/templates/codemirror/util/formatting.js +294 -0
  27. data/lib/generators/feather_cms/templates/codemirror/util/javascript-hint.js +134 -0
  28. data/lib/generators/feather_cms/templates/codemirror/util/match-highlighter.js +44 -0
  29. data/lib/generators/feather_cms/templates/codemirror/util/overlay.js +51 -0
  30. data/lib/generators/feather_cms/templates/codemirror/util/runmode.js +49 -0
  31. data/lib/generators/feather_cms/templates/codemirror/util/search.js +114 -0
  32. data/lib/generators/feather_cms/templates/codemirror/util/searchcursor.js +117 -0
  33. data/lib/generators/feather_cms/templates/codemirror/util/simple-hint.css +16 -0
  34. data/lib/generators/feather_cms/templates/codemirror/util/simple-hint.js +68 -0
  35. data/lib/generators/feather_cms/templates/controller.rb +34 -0
  36. data/lib/generators/feather_cms/templates/form.html.erb +40 -0
  37. data/lib/generators/feather_cms/templates/index.html.erb +11 -0
  38. data/lib/generators/feather_cms/templates/initializer.rb +5 -0
  39. data/lib/generators/feather_cms/templates/layout.html.erb +52 -0
  40. data/lib/generators/feather_cms/templates/migration.rb +13 -0
  41. data/lib/generators/feather_cms/templates/model.rb +3 -0
  42. metadata +98 -0
@@ -0,0 +1,294 @@
1
+ // ============== Formatting extensions ============================
2
+ // A common storage for all mode-specific formatting features
3
+ if (!CodeMirror.modeExtensions) CodeMirror.modeExtensions = {};
4
+
5
+ // Returns the extension of the editor's current mode
6
+ CodeMirror.defineExtension("getModeExt", function () {
7
+ var mname = CodeMirror.resolveMode(this.getOption("mode")).name;
8
+ var ext = CodeMirror.modeExtensions[mname];
9
+ if (!ext) throw new Error("No extensions found for mode " + mname);
10
+ return ext;
11
+ });
12
+
13
+ // If the current mode is 'htmlmixed', returns the extension of a mode located at
14
+ // the specified position (can be htmlmixed, css or javascript). Otherwise, simply
15
+ // returns the extension of the editor's current mode.
16
+ CodeMirror.defineExtension("getModeExtAtPos", function (pos) {
17
+ var token = this.getTokenAt(pos);
18
+ if (token && token.state && token.state.mode)
19
+ return CodeMirror.modeExtensions[token.state.mode == "html" ? "htmlmixed" : token.state.mode];
20
+ else
21
+ return this.getModeExt();
22
+ });
23
+
24
+ // Comment/uncomment the specified range
25
+ CodeMirror.defineExtension("commentRange", function (isComment, from, to) {
26
+ var curMode = this.getModeExtAtPos(this.getCursor());
27
+ if (isComment) { // Comment range
28
+ var commentedText = this.getRange(from, to);
29
+ this.replaceRange(curMode.commentStart + this.getRange(from, to) + curMode.commentEnd
30
+ , from, to);
31
+ if (from.line == to.line && from.ch == to.ch) { // An empty comment inserted - put cursor inside
32
+ this.setCursor(from.line, from.ch + curMode.commentStart.length);
33
+ }
34
+ }
35
+ else { // Uncomment range
36
+ var selText = this.getRange(from, to);
37
+ var startIndex = selText.indexOf(curMode.commentStart);
38
+ var endIndex = selText.lastIndexOf(curMode.commentEnd);
39
+ if (startIndex > -1 && endIndex > -1 && endIndex > startIndex) {
40
+ // Take string till comment start
41
+ selText = selText.substr(0, startIndex)
42
+ // From comment start till comment end
43
+ + selText.substring(startIndex + curMode.commentStart.length, endIndex)
44
+ // From comment end till string end
45
+ + selText.substr(endIndex + curMode.commentEnd.length);
46
+ }
47
+ this.replaceRange(selText, from, to);
48
+ }
49
+ });
50
+
51
+ // Applies automatic mode-aware indentation to the specified range
52
+ CodeMirror.defineExtension("autoIndentRange", function (from, to) {
53
+ var cmInstance = this;
54
+ this.operation(function () {
55
+ for (var i = from.line; i <= to.line; i++) {
56
+ cmInstance.indentLine(i, "smart");
57
+ }
58
+ });
59
+ });
60
+
61
+ // Applies automatic formatting to the specified range
62
+ CodeMirror.defineExtension("autoFormatRange", function (from, to) {
63
+ var absStart = this.indexFromPos(from);
64
+ var absEnd = this.indexFromPos(to);
65
+ // Insert additional line breaks where necessary according to the
66
+ // mode's syntax
67
+ var res = this.getModeExt().autoFormatLineBreaks(this.getValue(), absStart, absEnd);
68
+ var cmInstance = this;
69
+
70
+ // Replace and auto-indent the range
71
+ this.operation(function () {
72
+ cmInstance.replaceRange(res, from, to);
73
+ var startLine = cmInstance.posFromIndex(absStart).line;
74
+ var endLine = cmInstance.posFromIndex(absStart + res.length).line;
75
+ for (var i = startLine; i <= endLine; i++) {
76
+ cmInstance.indentLine(i, "smart");
77
+ }
78
+ });
79
+ });
80
+
81
+ // Define extensions for a few modes
82
+
83
+ CodeMirror.modeExtensions["css"] = {
84
+ commentStart: "/*",
85
+ commentEnd: "*/",
86
+ wordWrapChars: [";", "\\{", "\\}"],
87
+ autoFormatLineBreaks: function (text) {
88
+ return text.replace(new RegExp("(;|\\{|\\})([^\r\n])", "g"), "$1\n$2");
89
+ }
90
+ };
91
+
92
+ CodeMirror.modeExtensions["javascript"] = {
93
+ commentStart: "/*",
94
+ commentEnd: "*/",
95
+ wordWrapChars: [";", "\\{", "\\}"],
96
+
97
+ getNonBreakableBlocks: function (text) {
98
+ var nonBreakableRegexes = [
99
+ new RegExp("for\\s*?\\(([\\s\\S]*?)\\)"),
100
+ new RegExp("'([\\s\\S]*?)('|$)"),
101
+ new RegExp("\"([\\s\\S]*?)(\"|$)"),
102
+ new RegExp("//.*([\r\n]|$)")
103
+ ];
104
+ var nonBreakableBlocks = new Array();
105
+ for (var i = 0; i < nonBreakableRegexes.length; i++) {
106
+ var curPos = 0;
107
+ while (curPos < text.length) {
108
+ var m = text.substr(curPos).match(nonBreakableRegexes[i]);
109
+ if (m != null) {
110
+ nonBreakableBlocks.push({
111
+ start: curPos + m.index,
112
+ end: curPos + m.index + m[0].length
113
+ });
114
+ curPos += m.index + Math.max(1, m[0].length);
115
+ }
116
+ else { // No more matches
117
+ break;
118
+ }
119
+ }
120
+ }
121
+ nonBreakableBlocks.sort(function (a, b) {
122
+ return a.start - b.start;
123
+ });
124
+
125
+ return nonBreakableBlocks;
126
+ },
127
+
128
+ autoFormatLineBreaks: function (text) {
129
+ var curPos = 0;
130
+ var reLinesSplitter = new RegExp("(;|\\{|\\})([^\r\n])", "g");
131
+ var nonBreakableBlocks = this.getNonBreakableBlocks(text);
132
+ if (nonBreakableBlocks != null) {
133
+ var res = "";
134
+ for (var i = 0; i < nonBreakableBlocks.length; i++) {
135
+ if (nonBreakableBlocks[i].start > curPos) { // Break lines till the block
136
+ res += text.substring(curPos, nonBreakableBlocks[i].start).replace(reLinesSplitter, "$1\n$2");
137
+ curPos = nonBreakableBlocks[i].start;
138
+ }
139
+ if (nonBreakableBlocks[i].start <= curPos
140
+ && nonBreakableBlocks[i].end >= curPos) { // Skip non-breakable block
141
+ res += text.substring(curPos, nonBreakableBlocks[i].end);
142
+ curPos = nonBreakableBlocks[i].end;
143
+ }
144
+ }
145
+ if (curPos < text.length - 1) {
146
+ res += text.substr(curPos).replace(reLinesSplitter, "$1\n$2");
147
+ }
148
+ return res;
149
+ }
150
+ else {
151
+ return text.replace(reLinesSplitter, "$1\n$2");
152
+ }
153
+ }
154
+ };
155
+
156
+ CodeMirror.modeExtensions["xml"] = {
157
+ commentStart: "<!--",
158
+ commentEnd: "-->",
159
+ wordWrapChars: [">"],
160
+
161
+ autoFormatLineBreaks: function (text) {
162
+ var lines = text.split("\n");
163
+ var reProcessedPortion = new RegExp("(^\\s*?<|^[^<]*?)(.+)(>\\s*?$|[^>]*?$)");
164
+ var reOpenBrackets = new RegExp("<", "g");
165
+ var reCloseBrackets = new RegExp("(>)([^\r\n])", "g");
166
+ for (var i = 0; i < lines.length; i++) {
167
+ var mToProcess = lines[i].match(reProcessedPortion);
168
+ if (mToProcess != null && mToProcess.length > 3) { // The line starts with whitespaces and ends with whitespaces
169
+ lines[i] = mToProcess[1]
170
+ + mToProcess[2].replace(reOpenBrackets, "\n$&").replace(reCloseBrackets, "$1\n$2")
171
+ + mToProcess[3];
172
+ continue;
173
+ }
174
+ }
175
+
176
+ return lines.join("\n");
177
+ }
178
+ };
179
+
180
+ CodeMirror.modeExtensions["htmlmixed"] = {
181
+ commentStart: "<!--",
182
+ commentEnd: "-->",
183
+ wordWrapChars: [">", ";", "\\{", "\\}"],
184
+
185
+ getModeInfos: function (text, absPos) {
186
+ var modeInfos = new Array();
187
+ modeInfos[0] =
188
+ {
189
+ pos: 0,
190
+ modeExt: CodeMirror.modeExtensions["xml"],
191
+ modeName: "xml"
192
+ };
193
+
194
+ var modeMatchers = new Array();
195
+ modeMatchers[0] =
196
+ {
197
+ regex: new RegExp("<style[^>]*>([\\s\\S]*?)(</style[^>]*>|$)", "i"),
198
+ modeExt: CodeMirror.modeExtensions["css"],
199
+ modeName: "css"
200
+ };
201
+ modeMatchers[1] =
202
+ {
203
+ regex: new RegExp("<script[^>]*>([\\s\\S]*?)(</script[^>]*>|$)", "i"),
204
+ modeExt: CodeMirror.modeExtensions["javascript"],
205
+ modeName: "javascript"
206
+ };
207
+
208
+ var lastCharPos = (typeof (absPos) !== "undefined" ? absPos : text.length - 1);
209
+ // Detect modes for the entire text
210
+ for (var i = 0; i < modeMatchers.length; i++) {
211
+ var curPos = 0;
212
+ while (curPos <= lastCharPos) {
213
+ var m = text.substr(curPos).match(modeMatchers[i].regex);
214
+ if (m != null) {
215
+ if (m.length > 1 && m[1].length > 0) {
216
+ // Push block begin pos
217
+ var blockBegin = curPos + m.index + m[0].indexOf(m[1]);
218
+ modeInfos.push(
219
+ {
220
+ pos: blockBegin,
221
+ modeExt: modeMatchers[i].modeExt,
222
+ modeName: modeMatchers[i].modeName
223
+ });
224
+ // Push block end pos
225
+ modeInfos.push(
226
+ {
227
+ pos: blockBegin + m[1].length,
228
+ modeExt: modeInfos[0].modeExt,
229
+ modeName: modeInfos[0].modeName
230
+ });
231
+ curPos += m.index + m[0].length;
232
+ continue;
233
+ }
234
+ else {
235
+ curPos += m.index + Math.max(m[0].length, 1);
236
+ }
237
+ }
238
+ else { // No more matches
239
+ break;
240
+ }
241
+ }
242
+ }
243
+ // Sort mode infos
244
+ modeInfos.sort(function sortModeInfo(a, b) {
245
+ return a.pos - b.pos;
246
+ });
247
+
248
+ return modeInfos;
249
+ },
250
+
251
+ autoFormatLineBreaks: function (text, startPos, endPos) {
252
+ var modeInfos = this.getModeInfos(text);
253
+ var reBlockStartsWithNewline = new RegExp("^\\s*?\n");
254
+ var reBlockEndsWithNewline = new RegExp("\n\\s*?$");
255
+ var res = "";
256
+ // Use modes info to break lines correspondingly
257
+ if (modeInfos.length > 1) { // Deal with multi-mode text
258
+ for (var i = 1; i <= modeInfos.length; i++) {
259
+ var selStart = modeInfos[i - 1].pos;
260
+ var selEnd = (i < modeInfos.length ? modeInfos[i].pos : endPos);
261
+
262
+ if (selStart >= endPos) { // The block starts later than the needed fragment
263
+ break;
264
+ }
265
+ if (selStart < startPos) {
266
+ if (selEnd <= startPos) { // The block starts earlier than the needed fragment
267
+ continue;
268
+ }
269
+ selStart = startPos;
270
+ }
271
+ if (selEnd > endPos) {
272
+ selEnd = endPos;
273
+ }
274
+ var textPortion = text.substring(selStart, selEnd);
275
+ if (modeInfos[i - 1].modeName != "xml") { // Starting a CSS or JavaScript block
276
+ if (!reBlockStartsWithNewline.test(textPortion)
277
+ && selStart > 0) { // The block does not start with a line break
278
+ textPortion = "\n" + textPortion;
279
+ }
280
+ if (!reBlockEndsWithNewline.test(textPortion)
281
+ && selEnd < text.length - 1) { // The block does not end with a line break
282
+ textPortion += "\n";
283
+ }
284
+ }
285
+ res += modeInfos[i - 1].modeExt.autoFormatLineBreaks(textPortion);
286
+ }
287
+ }
288
+ else { // Single-mode text
289
+ res = modeInfos[0].modeExt.autoFormatLineBreaks(text.substring(startPos, endPos));
290
+ }
291
+
292
+ return res;
293
+ }
294
+ };
@@ -0,0 +1,134 @@
1
+ (function () {
2
+ function forEach(arr, f) {
3
+ for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
4
+ }
5
+
6
+ function arrayContains(arr, item) {
7
+ if (!Array.prototype.indexOf) {
8
+ var i = arr.length;
9
+ while (i--) {
10
+ if (arr[i] === item) {
11
+ return true;
12
+ }
13
+ }
14
+ return false;
15
+ }
16
+ return arr.indexOf(item) != -1;
17
+ }
18
+
19
+ function scriptHint(editor, keywords, getToken) {
20
+ // Find the token at the cursor
21
+ var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token;
22
+ // If it's not a 'word-style' token, ignore the token.
23
+ if (!/^[\w$_]*$/.test(token.string)) {
24
+ token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state,
25
+ className: token.string == "." ? "property" : null};
26
+ }
27
+ // If it is a property, find out what it is a property of.
28
+ while (tprop.className == "property") {
29
+ tprop = getToken(editor, {line: cur.line, ch: tprop.start});
30
+ if (tprop.string != ".") return;
31
+ tprop = getToken(editor, {line: cur.line, ch: tprop.start});
32
+ if (tprop.string == ')') {
33
+ var level = 1;
34
+ do {
35
+ tprop = getToken(editor, {line: cur.line, ch: tprop.start});
36
+ switch (tprop.string) {
37
+ case ')': level++; break;
38
+ case '(': level--; break;
39
+ default: break;
40
+ }
41
+ } while (level > 0)
42
+ tprop = getToken(editor, {line: cur.line, ch: tprop.start});
43
+ if (tprop.className == 'variable')
44
+ tprop.className = 'function';
45
+ else return; // no clue
46
+ }
47
+ if (!context) var context = [];
48
+ context.push(tprop);
49
+ }
50
+ return {list: getCompletions(token, context, keywords),
51
+ from: {line: cur.line, ch: token.start},
52
+ to: {line: cur.line, ch: token.end}};
53
+ }
54
+
55
+ CodeMirror.javascriptHint = function(editor) {
56
+ return scriptHint(editor, javascriptKeywords,
57
+ function (e, cur) {return e.getTokenAt(cur);});
58
+ }
59
+
60
+ function getCoffeeScriptToken(editor, cur) {
61
+ // This getToken, it is for coffeescript, imitates the behavior of
62
+ // getTokenAt method in javascript.js, that is, returning "property"
63
+ // type and treat "." as indepenent token.
64
+ var token = editor.getTokenAt(cur);
65
+ if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') {
66
+ token.end = token.start;
67
+ token.string = '.';
68
+ token.className = "property";
69
+ }
70
+ else if (/^\.[\w$_]*$/.test(token.string)) {
71
+ token.className = "property";
72
+ token.start++;
73
+ token.string = token.string.replace(/\./, '');
74
+ }
75
+ return token;
76
+ }
77
+
78
+ CodeMirror.coffeescriptHint = function(editor) {
79
+ return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken);
80
+ }
81
+
82
+ var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
83
+ "toUpperCase toLowerCase split concat match replace search").split(" ");
84
+ var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
85
+ "lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
86
+ var funcProps = "prototype apply call bind".split(" ");
87
+ var javascriptKeywords = ("break case catch continue debugger default delete do else false finally for function " +
88
+ "if in instanceof new null return switch throw true try typeof var void while with").split(" ");
89
+ var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
90
+ "if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
91
+
92
+ function getCompletions(token, context, keywords) {
93
+ var found = [], start = token.string;
94
+ function maybeAdd(str) {
95
+ if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str);
96
+ }
97
+ function gatherCompletions(obj) {
98
+ if (typeof obj == "string") forEach(stringProps, maybeAdd);
99
+ else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
100
+ else if (obj instanceof Function) forEach(funcProps, maybeAdd);
101
+ for (var name in obj) maybeAdd(name);
102
+ }
103
+
104
+ if (context) {
105
+ // If this is a property, see if it belongs to some object we can
106
+ // find in the current environment.
107
+ var obj = context.pop(), base;
108
+ if (obj.className == "variable")
109
+ base = window[obj.string];
110
+ else if (obj.className == "string")
111
+ base = "";
112
+ else if (obj.className == "atom")
113
+ base = 1;
114
+ else if (obj.className == "function") {
115
+ if (window.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
116
+ (typeof window.jQuery == 'function'))
117
+ base = window.jQuery();
118
+ else if (window._ != null && (obj.string == '_') && (typeof window._ == 'function'))
119
+ base = window._();
120
+ }
121
+ while (base != null && context.length)
122
+ base = base[context.pop().string];
123
+ if (base != null) gatherCompletions(base);
124
+ }
125
+ else {
126
+ // If not, just look in the window object and any local scope
127
+ // (reading into JS mode internals to get at the local variables)
128
+ for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
129
+ gatherCompletions(window);
130
+ forEach(keywords, maybeAdd);
131
+ }
132
+ return found;
133
+ }
134
+ })();
@@ -0,0 +1,44 @@
1
+ // Define match-highlighter commands. Depends on searchcursor.js
2
+ // Use by attaching the following function call to the onCursorActivity event:
3
+ //myCodeMirror.matchHighlight(minChars);
4
+ // And including a special span.CodeMirror-matchhighlight css class (also optionally a separate one for .CodeMirror-focused -- see demo matchhighlighter.html)
5
+
6
+ (function() {
7
+ var DEFAULT_MIN_CHARS = 2;
8
+
9
+ function MatchHighlightState() {
10
+ this.marked = [];
11
+ }
12
+ function getMatchHighlightState(cm) {
13
+ return cm._matchHighlightState || (cm._matchHighlightState = new MatchHighlightState());
14
+ }
15
+
16
+ function clearMarks(cm) {
17
+ var state = getMatchHighlightState(cm);
18
+ for (var i = 0; i < state.marked.length; ++i)
19
+ state.marked[i].clear();
20
+ state.marked = [];
21
+ }
22
+
23
+ function markDocument(cm, className, minChars) {
24
+ clearMarks(cm);
25
+ minChars = (typeof minChars !== 'undefined' ? minChars : DEFAULT_MIN_CHARS);
26
+ if (cm.somethingSelected() && cm.getSelection().length >= minChars) {
27
+ var state = getMatchHighlightState(cm);
28
+ var query = cm.getSelection();
29
+ cm.operation(function() {
30
+ if (cm.lineCount() < 2000) { // This is too expensive on big documents.
31
+ for (var cursor = cm.getSearchCursor(query); cursor.findNext();) {
32
+ //Only apply matchhighlight to the matches other than the one actually selected
33
+ if (!(cursor.from().line === cm.getCursor(true).line && cursor.from().ch === cm.getCursor(true).ch))
34
+ state.marked.push(cm.markText(cursor.from(), cursor.to(), className));
35
+ }
36
+ }
37
+ });
38
+ }
39
+ }
40
+
41
+ CodeMirror.defineExtension("matchHighlight", function(className, minChars) {
42
+ markDocument(this, className, minChars);
43
+ });
44
+ })();
@@ -0,0 +1,51 @@
1
+ // Utility function that allows modes to be combined. The mode given
2
+ // as the base argument takes care of most of the normal mode
3
+ // functionality, but a second (typically simple) mode is used, which
4
+ // can override the style of text. Both modes get to parse all of the
5
+ // text, but when both assign a non-null style to a piece of code, the
6
+ // overlay wins, unless the combine argument was true, in which case
7
+ // the styles are combined.
8
+
9
+ CodeMirror.overlayParser = function(base, overlay, combine) {
10
+ return {
11
+ startState: function() {
12
+ return {
13
+ base: CodeMirror.startState(base),
14
+ overlay: CodeMirror.startState(overlay),
15
+ basePos: 0, baseCur: null,
16
+ overlayPos: 0, overlayCur: null
17
+ };
18
+ },
19
+ copyState: function(state) {
20
+ return {
21
+ base: CodeMirror.copyState(base, state.base),
22
+ overlay: CodeMirror.copyState(overlay, state.overlay),
23
+ basePos: state.basePos, baseCur: null,
24
+ overlayPos: state.overlayPos, overlayCur: null
25
+ };
26
+ },
27
+
28
+ token: function(stream, state) {
29
+ if (stream.start == state.basePos) {
30
+ state.baseCur = base.token(stream, state.base);
31
+ state.basePos = stream.pos;
32
+ }
33
+ if (stream.start == state.overlayPos) {
34
+ stream.pos = stream.start;
35
+ state.overlayCur = overlay.token(stream, state.overlay);
36
+ state.overlayPos = stream.pos;
37
+ }
38
+ stream.pos = Math.min(state.basePos, state.overlayPos);
39
+ if (stream.eol()) state.basePos = state.overlayPos = 0;
40
+
41
+ if (state.overlayCur == null) return state.baseCur;
42
+ if (state.baseCur != null && combine) return state.baseCur + " " + state.overlayCur;
43
+ else return state.overlayCur;
44
+ },
45
+
46
+ indent: function(state, textAfter) {
47
+ return base.indent(state.base, textAfter);
48
+ },
49
+ electricChars: base.electricChars
50
+ };
51
+ };
@@ -0,0 +1,49 @@
1
+ CodeMirror.runMode = function(string, modespec, callback, options) {
2
+ var mode = CodeMirror.getMode(CodeMirror.defaults, modespec);
3
+ var isNode = callback.nodeType == 1;
4
+ var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize;
5
+ if (isNode) {
6
+ var node = callback, accum = [], col = 0;
7
+ callback = function(text, style) {
8
+ if (text == "\n") {
9
+ accum.push("<br>");
10
+ col = 0;
11
+ return;
12
+ }
13
+ var escaped = "";
14
+ // HTML-escape and replace tabs
15
+ for (var pos = 0;;) {
16
+ var idx = text.indexOf("\t", pos);
17
+ if (idx == -1) {
18
+ escaped += CodeMirror.htmlEscape(text.slice(pos));
19
+ col += text.length - pos;
20
+ break;
21
+ } else {
22
+ col += idx - pos;
23
+ escaped += CodeMirror.htmlEscape(text.slice(pos, idx));
24
+ var size = tabSize - col % tabSize;
25
+ col += size;
26
+ for (var i = 0; i < size; ++i) escaped += " ";
27
+ pos = idx + 1;
28
+ }
29
+ }
30
+
31
+ if (style)
32
+ accum.push("<span class=\"cm-" + CodeMirror.htmlEscape(style) + "\">" + escaped + "</span>");
33
+ else
34
+ accum.push(escaped);
35
+ }
36
+ }
37
+ var lines = CodeMirror.splitLines(string), state = CodeMirror.startState(mode);
38
+ for (var i = 0, e = lines.length; i < e; ++i) {
39
+ if (i) callback("\n");
40
+ var stream = new CodeMirror.StringStream(lines[i]);
41
+ while (!stream.eol()) {
42
+ var style = mode.token(stream, state);
43
+ callback(stream.current(), style, i, stream.start);
44
+ stream.start = stream.pos;
45
+ }
46
+ }
47
+ if (isNode)
48
+ node.innerHTML = accum.join("");
49
+ };
@@ -0,0 +1,114 @@
1
+ // Define search commands. Depends on dialog.js or another
2
+ // implementation of the openDialog method.
3
+
4
+ // Replace works a little oddly -- it will do the replace on the next
5
+ // Ctrl-G (or whatever is bound to findNext) press. You prevent a
6
+ // replace by making sure the match is no longer selected when hitting
7
+ // Ctrl-G.
8
+
9
+ (function() {
10
+ function SearchState() {
11
+ this.posFrom = this.posTo = this.query = null;
12
+ this.marked = [];
13
+ }
14
+ function getSearchState(cm) {
15
+ return cm._searchState || (cm._searchState = new SearchState());
16
+ }
17
+ function dialog(cm, text, shortText, f) {
18
+ if (cm.openDialog) cm.openDialog(text, f);
19
+ else f(prompt(shortText, ""));
20
+ }
21
+ function confirmDialog(cm, text, shortText, fs) {
22
+ if (cm.openConfirm) cm.openConfirm(text, fs);
23
+ else if (confirm(shortText)) fs[0]();
24
+ }
25
+ function parseQuery(query) {
26
+ var isRE = query.match(/^\/(.*)\/$/);
27
+ return isRE ? new RegExp(isRE[1]) : query;
28
+ }
29
+ var queryDialog =
30
+ 'Search: <input type="text" style="width: 10em"> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
31
+ function doSearch(cm, rev) {
32
+ var state = getSearchState(cm);
33
+ if (state.query) return findNext(cm, rev);
34
+ dialog(cm, queryDialog, "Search for:", function(query) {
35
+ cm.operation(function() {
36
+ if (!query || state.query) return;
37
+ state.query = parseQuery(query);
38
+ if (cm.lineCount() < 2000) { // This is too expensive on big documents.
39
+ for (var cursor = cm.getSearchCursor(query); cursor.findNext();)
40
+ state.marked.push(cm.markText(cursor.from(), cursor.to(), "CodeMirror-searching"));
41
+ }
42
+ state.posFrom = state.posTo = cm.getCursor();
43
+ findNext(cm, rev);
44
+ });
45
+ });
46
+ }
47
+ function findNext(cm, rev) {cm.operation(function() {
48
+ var state = getSearchState(cm);
49
+ var cursor = cm.getSearchCursor(state.query, rev ? state.posFrom : state.posTo);
50
+ if (!cursor.find(rev)) {
51
+ cursor = cm.getSearchCursor(state.query, rev ? {line: cm.lineCount() - 1} : {line: 0, ch: 0});
52
+ if (!cursor.find(rev)) return;
53
+ }
54
+ cm.setSelection(cursor.from(), cursor.to());
55
+ state.posFrom = cursor.from(); state.posTo = cursor.to();
56
+ })}
57
+ function clearSearch(cm) {cm.operation(function() {
58
+ var state = getSearchState(cm);
59
+ if (!state.query) return;
60
+ state.query = null;
61
+ for (var i = 0; i < state.marked.length; ++i) state.marked[i].clear();
62
+ state.marked.length = 0;
63
+ })}
64
+
65
+ var replaceQueryDialog =
66
+ 'Replace: <input type="text" style="width: 10em"> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
67
+ var replacementQueryDialog = 'With: <input type="text" style="width: 10em">';
68
+ var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
69
+ function replace(cm, all) {
70
+ dialog(cm, replaceQueryDialog, "Replace:", function(query) {
71
+ if (!query) return;
72
+ query = parseQuery(query);
73
+ dialog(cm, replacementQueryDialog, "Replace with:", function(text) {
74
+ if (all) {
75
+ cm.operation(function() {
76
+ for (var cursor = cm.getSearchCursor(query); cursor.findNext();) {
77
+ if (typeof query != "string") {
78
+ var match = cm.getRange(cursor.from(), cursor.to()).match(query);
79
+ cursor.replace(text.replace(/\$(\d)/, function(w, i) {return match[i];}));
80
+ } else cursor.replace(text);
81
+ }
82
+ });
83
+ } else {
84
+ clearSearch(cm);
85
+ var cursor = cm.getSearchCursor(query, cm.getCursor());
86
+ function advance() {
87
+ var start = cursor.from(), match;
88
+ if (!(match = cursor.findNext())) {
89
+ cursor = cm.getSearchCursor(query);
90
+ if (!(match = cursor.findNext()) ||
91
+ (cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
92
+ }
93
+ cm.setSelection(cursor.from(), cursor.to());
94
+ confirmDialog(cm, doReplaceConfirm, "Replace?",
95
+ [function() {doReplace(match);}, advance]);
96
+ }
97
+ function doReplace(match) {
98
+ cursor.replace(typeof query == "string" ? text :
99
+ text.replace(/\$(\d)/, function(w, i) {return match[i];}));
100
+ advance();
101
+ }
102
+ advance();
103
+ }
104
+ });
105
+ });
106
+ }
107
+
108
+ CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
109
+ CodeMirror.commands.findNext = doSearch;
110
+ CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
111
+ CodeMirror.commands.clearSearch = clearSearch;
112
+ CodeMirror.commands.replace = replace;
113
+ CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
114
+ })();