~ubuntu-branches/ubuntu/utopic/codemirror-js/utopic

« back to all changes in this revision

Viewing changes to lib/util/formatting.js

  • Committer: Package Import Robot
  • Author(s): David Paleino
  • Date: 2012-04-12 12:25:28 UTC
  • Revision ID: package-import@ubuntu.com-20120412122528-8xp5a8frj4h1d3ee
Tags: upstream-2.23
ImportĀ upstreamĀ versionĀ 2.23

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
};