Project

General

Profile

Revision 219

View differences:

trunk/scripts/codemirror/mode/ruby/ruby.js
13 13
    "require_relative", "extend", "autoload"
14 14
  ]);
15 15
  var indentWords = wordObj(["def", "class", "case", "for", "while", "do", "module", "then",
16
                             "unless", "catch", "loop", "proc"]);
16
                             "catch", "loop", "proc", "begin"]);
17 17
  var dedentWords = wordObj(["end", "until"]);
18 18
  var matching = {"[": "]", "{": "}", "(": ")"};
19 19
  var curPunc;
......
31 31
    }
32 32
    if (stream.eatSpace()) return null;
33 33
    var ch = stream.next();
34
    if (ch == "`" || ch == "'" || ch == '"' || ch == "/") {
34
    if (ch == "`" || ch == "'" || ch == '"' ||
35
        (ch == "/" && !stream.eol() && stream.peek() != " ")) {
35 36
      return chain(readQuoted(ch, "string", ch == '"'), stream, state);
36 37
    } else if (ch == "%") {
37 38
      var style, embed = false;
......
165 166
          : "variable";
166 167
        if (indentWords.propertyIsEnumerable(word)) kwtype = "indent";
167 168
        else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent";
168
        else if (word == "if" && stream.column() == stream.indentation()) kwtype = "indent";
169
        else if ((word == "if" || word == "unless") && stream.column() == stream.indentation())
170
          kwtype = "indent";
169 171
      }
170 172
      if (curPunc || (style && style != "comment")) state.lastTok = word || curPunc || style;
171 173
      if (curPunc == "|") state.varList = !state.varList;
......
185 187
      var firstChar = textAfter && textAfter.charAt(0);
186 188
      var ct = state.context;
187 189
      var closing = ct.type == matching[firstChar] ||
188
        ct.type == "keyword" && /^(?:end|until|else|elsif|when)\b/.test(textAfter);
190
        ct.type == "keyword" && /^(?:end|until|else|elsif|when|rescue)\b/.test(textAfter);
189 191
      return ct.indented + (closing ? 0 : config.indentUnit) +
190 192
        (state.continuedLine ? config.indentUnit : 0);
191
    }
193
    },
194
     electricChars: "}de" // enD and rescuE
195

  
192 196
  };
193 197
});
194 198

  
195 199
CodeMirror.defineMIME("text/x-ruby", "ruby");
200

  
trunk/scripts/codemirror/mode/python/python.js
18 18
                          'for', 'from', 'global', 'if', 'import',
19 19
                          'lambda', 'pass', 'raise', 'return',
20 20
                          'try', 'while', 'with', 'yield'];
21
    var commontypes = ['bool', 'classmethod', 'complex', 'dict', 'enumerate',
22
                       'float', 'frozenset', 'int', 'list', 'object',
23
                       'property', 'reversed', 'set', 'slice', 'staticmethod',
24
                       'str', 'super', 'tuple', 'type'];
25
    var py2 = {'types': ['basestring', 'buffer', 'file', 'long', 'unicode',
26
                         'xrange'],
21
    var commonBuiltins = ['abs', 'all', 'any', 'bin', 'bool', 'bytearray', 'callable', 'chr',
22
                          'classmethod', 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod',
23
                          'enumerate', 'eval', 'filter', 'float', 'format', 'frozenset',
24
                          'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id',
25
                          'input', 'int', 'isinstance', 'issubclass', 'iter', 'len',
26
                          'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next',
27
                          'object', 'oct', 'open', 'ord', 'pow', 'property', 'range',
28
                          'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
29
                          'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple',
30
                          'type', 'vars', 'zip', '__import__', 'NotImplemented',
31
                          'Ellipsis', '__debug__'];
32
    var py2 = {'builtins': ['apply', 'basestring', 'buffer', 'cmp', 'coerce', 'execfile',
33
                            'file', 'intern', 'long', 'raw_input', 'reduce', 'reload',
34
                            'unichr', 'unicode', 'xrange', 'False', 'True', 'None'],
27 35
               'keywords': ['exec', 'print']};
28
    var py3 = {'types': ['bytearray', 'bytes', 'filter', 'map', 'memoryview',
29
                         'open', 'range', 'zip'],
30
               'keywords': ['nonlocal']};
36
    var py3 = {'builtins': ['ascii', 'bytes', 'exec', 'print'],
37
               'keywords': ['nonlocal', 'False', 'True', 'None']};
31 38

  
32 39
    if (!!parserConf.version && parseInt(parserConf.version, 10) === 3) {
33 40
        commonkeywords = commonkeywords.concat(py3.keywords);
34
        commontypes = commontypes.concat(py3.types);
41
        commonBuiltins = commonBuiltins.concat(py3.builtins);
35 42
        var stringPrefixes = new RegExp("^(([rb]|(br))?('{3}|\"{3}|['\"]))", "i");
36 43
    } else {
37 44
        commonkeywords = commonkeywords.concat(py2.keywords);
38
        commontypes = commontypes.concat(py2.types);
45
        commonBuiltins = commonBuiltins.concat(py2.builtins);
39 46
        var stringPrefixes = new RegExp("^(([rub]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i");
40 47
    }
41 48
    var keywords = wordRegexp(commonkeywords);
42
    var types = wordRegexp(commontypes);
49
    var builtins = wordRegexp(commonBuiltins);
43 50

  
44 51
    var indentInfo = null;
45 52

  
......
129 136
            return null;
130 137
        }
131 138
        
132
        if (stream.match(types)) {
133
            return 'builtin';
134
        }
135
        
136 139
        if (stream.match(keywords)) {
137 140
            return 'keyword';
138 141
        }
139 142
        
143
        if (stream.match(builtins)) {
144
            return 'builtin';
145
        }
146
        
140 147
        if (stream.match(identifiers)) {
141 148
            return 'variable';
142 149
        }
......
244 251
        if (current === '.') {
245 252
            style = state.tokenize(stream, state);
246 253
            current = stream.current();
247
            if (style === 'variable') {
254
            if (style === 'variable' || style === 'builtin') {
248 255
                return 'variable';
249 256
            } else {
250 257
                return ERRORCLASS;
trunk/scripts/codemirror/mode/php/php.js
99 99
        if (state.curState == html) cur = htmlNew;
100 100
        else if (state.curState == php) cur = phpNew;
101 101
        else cur = CodeMirror.copyState(state.curMode, state.curState);
102
        return {html: htmlNew, php: phpNew, curMode: state.curMode, curState: cur, curClose: state.curClose};
102
        return {html: htmlNew, php: phpNew, curMode: state.curMode, curState: cur,
103
                curClose: state.curClose, mode: state.mode};
103 104
      },
104 105

  
105 106
      token: dispatch,
trunk/scripts/codemirror/mode/javascript/javascript.js
69 69
      else if (state.reAllowed) {
70 70
        nextUntilUnescaped(stream, "/");
71 71
        stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
72
        return ret("regexp", "string");
72
        return ret("regexp", "string-2");
73 73
      }
74 74
      else {
75 75
        stream.eatWhile(isOperatorChar);
......
230 230
    if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
231 231
    if (type == "function") return cont(functiondef);
232 232
    if (type == "keyword c") return cont(maybeexpression);
233
    if (type == "(") return cont(pushlex(")"), expression, expect(")"), poplex, maybeoperator);
233
    if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
234 234
    if (type == "operator") return cont(expression);
235 235
    if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
236 236
    if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
trunk/scripts/codemirror/mode/coffeescript/coffeescript.js
4 4
 */
5 5
CodeMirror.defineMode('coffeescript', function(conf) {
6 6
    var ERRORCLASS = 'error';
7
    
7

  
8 8
    function wordRegexp(words) {
9 9
        return new RegExp("^((" + words.join(")|(") + "))\\b");
10 10
    }
11
    
11

  
12 12
    var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\?]");
13 13
    var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]');
14 14
    var doubleOperators = new RegExp("^((\->)|(\=>)|(\\+\\+)|(\\+\\=)|(\\-\\-)|(\\-\\=)|(\\*\\*)|(\\*\\=)|(\\/\\/)|(\\/\\=)|(==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//))");
15 15
    var doubleDelimiters = new RegExp("^((\\.\\.)|(\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
16 16
    var tripleDelimiters = new RegExp("^((\\.\\.\\.)|(//=)|(>>=)|(<<=)|(\\*\\*=))");
17
    var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*");
17
    var identifiers = new RegExp("^[_A-Za-z$][_A-Za-z$0-9]*");
18 18

  
19 19
    var wordOperators = wordRegexp(['and', 'or', 'not',
20 20
                                    'is', 'isnt', 'in',
......
57 57
        if (stream.eatSpace()) {
58 58
            return null;
59 59
        }
60
        
60

  
61 61
        var ch = stream.peek();
62
        
63
        // Handle comments
62

  
63
        // Handle multi line comments
64
        if (stream.match("###")) {
65
            state.tokenize = longComment;
66
            return state.tokenize(stream, state);
67
        }
68

  
69
        // Single line comment
64 70
        if (ch === '#') {
65 71
            stream.skipToEnd();
66 72
            return 'comment';
67 73
        }
68
        
74

  
69 75
        // Handle number literals
70 76
        if (stream.match(/^-?[0-9\.]/, false)) {
71 77
            var floatLiteral = false;
......
79 85
            if (stream.match(/^-?\.\d+/)) {
80 86
              floatLiteral = true;
81 87
            }
88

  
82 89
            if (floatLiteral) {
90
                // prevent from getting extra . on 1..
91
                if (stream.peek() == "."){
92
                    stream.backUp(1);
93
                }
83 94
                return 'number';
84 95
            }
85 96
            // Integers
......
100 111
                return 'number';
101 112
            }
102 113
        }
103
        
114

  
104 115
        // Handle strings
105 116
        if (stream.match(stringPrefixes)) {
106 117
            state.tokenize = tokenFactory(stream.current(), 'string');
......
115 126
                stream.backUp(1);
116 127
            }
117 128
        }
118
        
129

  
119 130
        // Handle operators and delimiters
120 131
        if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) {
121 132
            return 'punctuation';
......
128 139
        if (stream.match(singleDelimiters)) {
129 140
            return 'punctuation';
130 141
        }
131
        
142

  
132 143
        if (stream.match(constants)) {
133 144
            return 'atom';
134 145
        }
135
        
146

  
136 147
        if (stream.match(keywords)) {
137 148
            return 'keyword';
138 149
        }
139
        
150

  
140 151
        if (stream.match(identifiers)) {
141 152
            return 'variable';
142 153
        }
143
        
154

  
144 155
        // Handle non-detected items
145 156
        stream.next();
146 157
        return ERRORCLASS;
147 158
    }
148
    
159

  
149 160
    function tokenFactory(delimiter, outclass) {
150
        var delim_re = new RegExp(delimiter);
151 161
        var singleline = delimiter.length == 1;
152
        
153 162
        return function tokenString(stream, state) {
154 163
            while (!stream.eol()) {
155 164
                stream.eatWhile(/[^'"\/\\]/);
......
158 167
                    if (singleline && stream.eol()) {
159 168
                        return outclass;
160 169
                    }
161
                } else if (stream.match(delim_re)) {
170
                } else if (stream.match(delimiter)) {
162 171
                    state.tokenize = tokenBase;
163 172
                    return outclass;
164 173
                } else {
......
175 184
            return outclass;
176 185
        };
177 186
    }
178
    
187

  
188
    function longComment(stream, state) {
189
        while (!stream.eol()) {
190
            stream.eatWhile(/[^#]/);
191
            if (stream.match("###")) {
192
                state.tokenize = tokenBase;
193
                break;
194
            }
195
            stream.eatWhile("#");
196
        }
197
        return "comment"
198
    }
199

  
179 200
    function indent(stream, state, type) {
180 201
        type = type || 'coffee';
181 202
        var indentUnit = 0;
......
194 215
            type: type
195 216
        });
196 217
    }
197
    
218

  
198 219
    function dedent(stream, state) {
199 220
        if (state.scopes.length == 1) return;
200 221
        if (state.scopes[0].type === 'coffee') {
......
233 254
                return ERRORCLASS;
234 255
            }
235 256
        }
236
        
257

  
237 258
        // Handle properties
238 259
        if (current === '@') {
239
            style = state.tokenize(stream, state);
240
            current = stream.current();
241
            if (style === 'variable') {
242
                return 'variable-2';
243
            } else {
244
                return ERRORCLASS;
245
            }
260
            stream.eat('@');
261
            return 'keyword';
246 262
        }
247
        
263

  
248 264
        // Handle scope changes.
249 265
        if (current === 'return') {
250 266
            state.dedent += 1;
......
266 282
        if (current == 'then'){
267 283
            dedent(stream, state);
268 284
        }
269
        
270 285

  
286

  
271 287
        if (style === 'dedent') {
272 288
            if (dedent(stream, state)) {
273 289
                return ERRORCLASS;
......
283 299
            if (state.scopes.length > 1) state.scopes.shift();
284 300
            state.dedent -= 1;
285 301
        }
286
        
302

  
287 303
        return style;
288 304
    }
289 305

  
......
297 313
              dedent: 0
298 314
          };
299 315
        },
300
        
316

  
301 317
        token: function(stream, state) {
302 318
            var style = tokenLexer(stream, state);
303
            
319

  
304 320
            state.lastToken = {style:style, content: stream.current()};
305
            
321

  
306 322
            if (stream.eol() && stream.lambda) {
307 323
                state.lambda = false;
308 324
            }
309
            
325

  
310 326
            return style;
311 327
        },
312
        
328

  
313 329
        indent: function(state, textAfter) {
314 330
            if (state.tokenize != tokenBase) {
315 331
                return 0;
316 332
            }
317
            
333

  
318 334
            return state.scopes[0].offset;
319 335
        }
320
        
336

  
321 337
    };
322 338
    return external;
323 339
});
trunk/scripts/codemirror/mode/clike/clike.js
231 231
      }
232 232
    }
233 233
  });
234
  CodeMirror.defineMIME("text/x-groovy", {
235
    name: "clike",
236
    keywords: words("abstract as assert boolean break byte case catch char class const continue def default " +
237
                    "do double else enum extends final finally float for goto if implements import " +
238
                    "in instanceof int interface long native new package property private protected public " +
239
                    "return short static strictfp super switch synchronized this throw throws transient " +
240
                    "try void volatile while"),
241
    atoms: words("true false null"),
242
    hooks: {
243
      "@": function(stream, state) {
244
        stream.eatWhile(/[\w\$_]/);
245
        return "meta";
246
      }
247
    }
248
  });
249 234
}());
trunk/scripts/codemirror/mode/clike/index.html
96 96
    <p><strong>MIME types defined:</strong> <code>text/x-csrc</code>
97 97
    (C code), <code>text/x-c++src</code> (C++
98 98
    code), <code>text/x-java</code> (Java
99
    code), <code>text/x-groovy</code> (Groovy code).</p>
99
    code).</p>
100 100
  </body>
101 101
</html>
trunk/scripts/codemirror/mode/xmlpure/xmlpure.js
15 15
    var STYLE_ATTRIBUTE = "attribute";
16 16
    var STYLE_WORD = "string";
17 17
    var STYLE_TEXT = "atom";
18
    var STYLE_ENTITIES = "string";
18 19

  
19 20
    var TAG_INSTRUCTION = "!instruction";
20 21
    var TAG_CDATA = "!cdata";
......
290 291
                state.tokenize = parseElementTagName;
291 292
                return STYLE_ELEMENT_NAME;
292 293
            }
294
        } else if(stream.eat("&")) {
295
            stream.eatWhile(/[^;]/);
296
            stream.eat(";");
297
            return STYLE_ENTITIES;
293 298
        } else {
294 299
            // new context: text
295 300
            pushContext(state, TAG_TEXT);
trunk/scripts/codemirror/mode/rust/rust.js
4 4
    "if": "if-style", "while": "if-style", "else": "else-style",
5 5
    "do": "else-style", "ret": "else-style", "fail": "else-style",
6 6
    "break": "atom", "cont": "atom", "const": "let", "resource": "fn",
7
    "let": "let", "fn": "fn", "for": "for", "alt": "alt", "obj": "fn",
8
    "lambda": "fn", "type": "type", "tag": "tag", "mod": "mod",
7
    "let": "let", "fn": "fn", "for": "for", "alt": "alt", "iface": "iface",
8
    "impl": "impl", "type": "type", "enum": "enum", "mod": "mod",
9 9
    "as": "op", "true": "atom", "false": "atom", "assert": "op", "check": "op",
10 10
    "claim": "op", "native": "ignore", "unsafe": "ignore", "import": "else-style",
11 11
    "export": "else-style", "copy": "op", "log": "op", "log_err": "op",
12
    "use": "op", "bind": "op"
12
    "use": "op", "bind": "op", "self": "atom"
13 13
  };
14 14
  var typeKeywords = function() {
15 15
    var keywords = {"fn": "fn", "block": "fn", "obj": "obj"};
......
169 169
    };
170 170
  }
171 171

  
172
  function stat_of(comb, tag) {
173
    return cont(pushlex("stat", tag), comb, poplex, block);
174
  }
172 175
  function block(type) {
173 176
    if (type == "}") return cont();
174
    if (type == "let") return cont(pushlex("stat", "let"), letdef1, poplex, block);
175
    if (type == "fn") return cont(pushlex("stat"), fndef, poplex, block);
177
    if (type == "let") return stat_of(letdef1, "let");
178
    if (type == "fn") return stat_of(fndef);
176 179
    if (type == "type") return cont(pushlex("stat"), tydef, endstatement, poplex, block);
177
    if (type == "tag") return cont(pushlex("stat"), tagdef, poplex, block);
178
    if (type == "mod") return cont(pushlex("stat"), mod, poplex, block);
180
    if (type == "enum") return stat_of(enumdef);
181
    if (type == "mod") return stat_of(mod);
182
    if (type == "iface") return stat_of(iface);
183
    if (type == "impl") return stat_of(impl);
179 184
    if (type == "open-attr") return cont(pushlex("]"), commasep(expression, "]"), poplex);
180 185
    if (type == "ignore" || type.match(/[\]\);,]/)) return cont(block);
181 186
    return pass(pushlex("stat"), expression, poplex, endstatement, block);
......
253 258
    return pass();
254 259
  }
255 260
  function fndef(type) {
261
    if (content == "@" || content == "~") {cx.marked = "keyword"; return cont(fndef);}
256 262
    if (type == "name") {cx.marked = "def"; return cont(fndef);}
257 263
    if (content == "<") return cont(typarams, fndef);
258 264
    if (type == "{") return pass(expression);
259 265
    if (type == "(") return cont(pushlex(")"), commasep(argdef, ")"), poplex, fndef);
260 266
    if (type == "->") return cont(typecx, rtype, valcx, fndef);
267
    if (type == ";") return cont();
261 268
    return cont(fndef);
262 269
  }
263 270
  function tydef(type) {
......
266 273
    if (content == "=") return cont(typecx, rtype, valcx);
267 274
    return cont(tydef);
268 275
  }
269
  function tagdef(type) {
270
    if (type == "name") {cx.marked = "def"; return cont(tagdef);}
271
    if (content == "<") return cont(typarams, tagdef);
276
  function enumdef(type) {
277
    if (type == "name") {cx.marked = "def"; return cont(enumdef);}
278
    if (content == "<") return cont(typarams, enumdef);
272 279
    if (content == "=") return cont(typecx, rtype, valcx, endstatement);
273
    if (type == "{") return cont(pushlex("}"), typecx, tagblock, valcx, poplex);
274
    return cont(tagdef);
280
    if (type == "{") return cont(pushlex("}"), typecx, enumblock, valcx, poplex);
281
    return cont(enumdef);
275 282
  }
276
  function tagblock(type) {
283
  function enumblock(type) {
277 284
    if (type == "}") return cont();
278
    if (type == "(") return cont(pushlex(")"), commasep(rtype, ")"), poplex, tagblock);
285
    if (type == "(") return cont(pushlex(")"), commasep(rtype, ")"), poplex, enumblock);
279 286
    if (content.match(/^\w+$/)) cx.marked = "def";
280
    return cont(tagblock);
287
    return cont(enumblock);
281 288
  }
282 289
  function mod(type) {
283 290
    if (type == "name") {cx.marked = "def"; return cont(mod);}
284 291
    if (type == "{") return cont(pushlex("}"), block, poplex);
285 292
    return pass();
286 293
  }
294
  function iface(type) {
295
    if (type == "name") {cx.marked = "def"; return cont(iface);}
296
    if (content == "<") return cont(typarams, iface);
297
    if (type == "{") return cont(pushlex("}"), block, poplex);
298
    return pass();
299
  }
300
  function impl(type) {
301
    if (content == "<") return cont(typarams, impl);
302
    if (content == "of" || content == "for") {cx.marked = "keyword"; return cont(rtype, impl);}
303
    if (type == "name") {cx.marked = "def"; return cont(impl);}
304
    if (type == "{") return cont(pushlex("}"), block, poplex);
305
    return pass();
306
  }
287 307
  function typarams(type) {
288 308
    if (content == ">") return cont();
289 309
    if (content == ",") return cont(typarams);
310
    if (content == ":") return cont(rtype, typarams);
290 311
    return pass(rtype, typarams);
291 312
  }
292 313
  function argdef(type) {
trunk/scripts/codemirror/mode/rust/index.html
15 15
// Demo code.
16 16

  
17 17
type foo<T> = int;
18
tag bar {
19
    some(int, foo<float>);
20
    none;
18
enum bar {
19
    some(int, foo<float>),
20
    none
21 21
}
22 22

  
23 23
fn check_crate(x: int) {
trunk/scripts/codemirror/mode/xml/xml.js
47 47
      }
48 48
    }
49 49
    else if (ch == "&") {
50
      stream.eatWhile(/[^;]/);
51
      stream.eat(";");
52
      return "atom";
50
      var ok;
51
      if (stream.eat("#")) {
52
        if (stream.eat("x")) {
53
          ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");          
54
        } else {
55
          ok = stream.eatWhile(/[\d]/) && stream.eat(";");
56
        }
57
      } else {
58
        ok = stream.eatWhile(/[\w]/) && stream.eat(";");
59
      }
60
      return ok ? "atom" : "error";
53 61
    }
54 62
    else {
55 63
      stream.eatWhile(/[^&<]/);
trunk/scripts/codemirror/lib/codemirror.js
1
// CodeMirror version 2.2
1
// CodeMirror version 2.21
2 2
//
3 3
// All functions that need access to the editor's state live inside
4 4
// the CodeMirror function. Below that, at the bottom of the file,
......
22 22
    // This mess creates the base DOM structure for the editor.
23 23
    wrapper.innerHTML =
24 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" ' +
25
        '<textarea style="position: absolute; padding: 0; width: 1px; height: 1em" wrap="off" ' +
26 26
          'autocorrect="off" autocapitalize="off"></textarea></div>' +
27 27
      '<div class="CodeMirror-scroll" tabindex="-1">' +
28 28
        '<div style="position: relative">' + // Set to the height of the text, causes scrolling
29 29
          '<div style="position: relative">' + // Moved around its parent to cover visible view
30 30
            '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
31 31
            // Provides positioning relative to (visible) text origin
32
            '<div class="CodeMirror-lines"><div style="position: relative">' +
33
              '<div style="position: absolute; width: 100%; height: 0; overflow: hidden; visibility: hidden"></div>' +
32
            '<div class="CodeMirror-lines"><div style="position: relative; z-index: 0">' +
33
              '<div style="position: absolute; width: 100%; height: 0; overflow: hidden; visibility: hidden; outline: 5px auto none"></div>' +
34 34
              '<pre class="CodeMirror-cursor">&#160;</pre>' + // Absolutely positioned blinky cursor
35
              '<div></div>' + // This DIV contains the actual code
35
              '<div style="position: relative; z-index: -1"></div><div></div>' + // DIVs containing the selection and the actual code
36 36
            '</div></div></div></div></div>';
37 37
    if (place.appendChild) place.appendChild(wrapper); else place(wrapper);
38 38
    // I've never seen more elegant code in my life.
......
40 40
        scroller = wrapper.lastChild, code = scroller.firstChild,
41 41
        mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild,
42 42
        lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild,
43
        cursor = measure.nextSibling, lineDiv = cursor.nextSibling;
43
        cursor = measure.nextSibling, selectionDiv = cursor.nextSibling,
44
        lineDiv = selectionDiv.nextSibling;
44 45
    themeChanged();
45 46
    // 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 (ios) input.style.width = "0px";
47 48
    if (!webkit) lineSpace.draggable = true;
49
    lineSpace.style.outline = "none";
48 50
    if (options.tabindex != null) input.tabIndex = options.tabindex;
49 51
    if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
50 52

  
......
71 73
    var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false};
72 74
    // Selection-related flags. shiftSelecting obviously tracks
73 75
    // whether the user is holding shift.
74
    var shiftSelecting, lastClick, lastDoubleClick, draggingText, overwrite = false;
76
    var shiftSelecting, lastClick, lastDoubleClick, lastScrollPos = 0, draggingText,
77
        overwrite = false, suppressEdits = false;
75 78
    // Variables used by startOperation/endOperation to track what
76 79
    // happened during the operation.
77 80
    var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone,
......
99 102
    // handled in onMouseDown for Gecko.
100 103
    if (!gecko) connect(scroller, "contextmenu", onContextMenu);
101 104
    connect(scroller, "scroll", function() {
105
      lastScrollPos = scroller.scrollTop;
102 106
      updateDisplay([]);
103 107
      if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px";
104 108
      if (options.onScroll) options.onScroll(instance);
......
116 120
    connect(scroller, "drop", operation(onDrop));
117 121
    connect(scroller, "paste", function(){focusInput(); fastPoll();});
118 122
    connect(input, "paste", fastPoll);
119
    connect(input, "cut", operation(function(){replaceSelection("");}));
123
    connect(input, "cut", operation(function(){
124
      if (!options.readOnly) replaceSelection("");
125
    }));
120 126

  
121 127
    // IE throws unspecified error in certain cases, when
122 128
    // trying to access activeElement before onload
......
140 146
        var oldVal = options[option];
141 147
        options[option] = value;
142 148
        if (option == "mode" || option == "indentUnit") loadMode();
143
        else if (option == "readOnly" && value) {onBlur(); input.blur();}
149
        else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();}
150
        else if (option == "readOnly" && !value) {resetInput(true);}
144 151
        else if (option == "theme") themeChanged();
145 152
        else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)();
146 153
        else if (option == "tabSize") operation(tabsChanged)();
147 154
        if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme")
148
          operation(gutterChanged)();
155
          updateDisplay(true);
149 156
      },
150 157
      getOption: function(option) {return options[option];},
151 158
      undo: operation(undo),
152 159
      redo: operation(redo),
153 160
      indentLine: operation(function(n, dir) {
154
        if (isLine(n)) indentLine(n, dir == null ? "smart" : dir ? "add" : "subtract");
161
        if (typeof dir != "string") {
162
          if (dir == null) dir = options.smartIndent ? "smart" : "prev";
163
          else dir = dir ? "add" : "subtract";
164
        }
165
        if (isLine(n)) indentLine(n, dir);
155 166
      }),
156 167
      indentSelection: operation(indentSelected),
157 168
      historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
......
268 279
        });
269 280
        return index;
270 281
      },
282
      scrollTo: function(x, y) {
283
        if (x != null) scroller.scrollTop = x;
284
        if (y != null) scroller.scrollLeft = y;
285
        updateDisplay([]);
286
      },
271 287

  
272 288
      operation: function(f){return operation(f)();},
273
      refresh: function(){updateDisplay(true);},
289
      refresh: function(){
290
        updateDisplay(true);
291
        if (scroller.scrollHeight > lastScrollPos)
292
          scroller.scrollTop = lastScrollPos;
293
      },
274 294
      getInputField: function(){return input;},
275 295
      getWrapperElement: function(){return wrapper;},
276 296
      getScrollerElement: function(){return scroller;},
......
297 317
    }
298 318

  
299 319
    function onMouseDown(e) {
300
      setShift(e.shiftKey);
320
      setShift(e_prop(e, "shiftKey"));
301 321
      // Check whether this is a click in a widget
302 322
      for (var n = e_target(e); n != wrapper; n = n.parentNode)
303 323
        if (n.parentNode == code && n != mover) return;
......
339 359
      } else { lastClick = {time: now, pos: start}; }
340 360

  
341 361
      var last = start, going;
342
      if (dragAndDrop && !posEq(sel.from, sel.to) &&
362
      if (dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) &&
343 363
          !posLess(start, sel.from) && !posLess(sel.to, start)) {
344 364
        // Let the drag handler handle this.
345 365
        if (webkit) lineSpace.draggable = true;
......
422 442
        try {
423 443
          var text = e.dataTransfer.getData("Text");
424 444
          if (text) {
425
	    var end = replaceRange(text, pos, pos);
426
	    var curFrom = sel.from, curTo = sel.to;
427
	    setSelectionUser(pos, end);
445
            var curFrom = sel.from, curTo = sel.to;
446
            setSelectionUser(pos, pos);
428 447
            if (draggingText) replaceRange("", curFrom, curTo);
448
            replaceSelection(text);
429 449
	    focusInput();
430 450
	  }
431 451
        }
......
440 460
      e.dataTransfer.setData("Text", txt);
441 461
    }
442 462
    function handleKeyBinding(e) {
443
      var name = keyNames[e.keyCode], next = keyMap[options.keyMap].auto, bound, dropShift;
463
      var name = keyNames[e_prop(e, "keyCode")], next = keyMap[options.keyMap].auto, bound, dropShift;
464
      function handleNext() {
465
        return next.call ? next.call(null, instance) : next;
466
      }
444 467
      if (name == null || e.altGraphKey) {
445
        if (next) options.keyMap = next;
468
        if (next) options.keyMap = handleNext();
446 469
        return null;
447 470
      }
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))) {
471
      if (e_prop(e, "altKey")) name = "Alt-" + name;
472
      if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name;
473
      if (e_prop(e, "metaKey")) name = "Cmd-" + name;
474
      if (e_prop(e, "shiftKey") &&
475
          (bound = lookupKey("Shift-" + name, options.extraKeys, options.keyMap))) {
452 476
        dropShift = true;
453 477
      } else {
454 478
        bound = lookupKey(name, options.extraKeys, options.keyMap);
......
457 481
        if (commands.propertyIsEnumerable(bound)) bound = commands[bound];
458 482
        else bound = null;
459 483
      }
460
      if (next && (bound || !isModifierKey(e))) options.keyMap = next;
484
      if (next && (bound || !isModifierKey(e))) options.keyMap = handleNext();
461 485
      if (!bound) return false;
462
      if (dropShift) {
463
        var prevShift = shiftSelecting;
464
        shiftSelecting = null;
486
      var prevShift = shiftSelecting;
487
      try {
488
        if (options.readOnly) suppressEdits = true;
489
        if (dropShift) shiftSelecting = null;
465 490
        bound(instance);
491
      } finally {
466 492
        shiftSelecting = prevShift;
467
      } else bound(instance);
493
        suppressEdits = false;
494
      }
468 495
      e_preventDefault(e);
469 496
      return true;
470 497
    }
471 498
    var lastStoppedKey = null;
472 499
    function onKeyDown(e) {
473 500
      if (!focused) onFocus();
474
      var code = e.keyCode;
501
      if (ie && e.keyCode == 27) { e.returnValue = false; }
502
      if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
503
      var code = e_prop(e, "keyCode");
475 504
      // IE does strange things with escape.
476
      if (ie && code == 27) { e.returnValue = false; }
477
      setShift(code == 16 || e.shiftKey);
505
      setShift(code == 16 || e_prop(e, "shiftKey"));
478 506
      // First give onKeyEvent option a chance to handle this.
479
      if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
480 507
      var handled = handleKeyBinding(e);
481 508
      if (window.opera) {
482
        lastStoppedKey = handled ? e.keyCode : null;
509
        lastStoppedKey = handled ? code : null;
483 510
        // 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)
511
        if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey"))
485 512
          replaceSelection("");
486 513
      }
487 514
    }
488 515
    function onKeyPress(e) {
489
      if (window.opera && e.keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
516
      var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode");
517
      if (window.opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
490 518
      if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
491 519
      if (window.opera && !e.which && handleKeyBinding(e)) return;
492
      if (options.electricChars && mode.electricChars) {
493
        var ch = String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode);
520
      if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) {
521
        var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
494 522
        if (mode.electricChars.indexOf(ch) > -1)
495 523
          setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75);
496 524
      }
......
498 526
    }
499 527
    function onKeyUp(e) {
500 528
      if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
501
      if (e.keyCode == 16) shiftSelecting = null;
529
      if (e_prop(e, "keyCode") == 16) shiftSelecting = null;
502 530
    }
503 531

  
504 532
    function onFocus() {
505
      if (options.readOnly) return;
533
      if (options.readOnly == "nocursor") return;
506 534
      if (!focused) {
507 535
        if (options.onFocus) options.onFocus(instance);
508 536
        focused = true;
......
517 545
      if (focused) {
518 546
        if (options.onBlur) options.onBlur(instance);
519 547
        focused = false;
548
        if (bracketHighlighted)
549
          operation(function(){
550
            if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; }
551
          })();
520 552
        wrapper.className = wrapper.className.replace(" CodeMirror-focused", "");
521 553
      }
522 554
      clearInterval(blinker);
......
526 558
    // Replace the range from from to to by the strings in newText.
527 559
    // Afterwards, set the selection to selFrom, selTo.
528 560
    function updateLines(from, to, newText, selFrom, selTo) {
561
      if (suppressEdits) return;
529 562
      if (history) {
530 563
        var old = [];
531 564
        doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); });
......
534 567
      }
535 568
      updateLinesNoUndo(from, to, newText, selFrom, selTo);
536 569
    }
537
    function unredoHelper(from, to) {
538
      var change = from.pop();
539
      if (change) {
570
    function unredoHelper(from, to, dir) {
571
      var set = from.pop(), len = set ? set.length : 0, out = [];
572
      for (var i = dir > 0 ? 0 : len - 1, e = dir > 0 ? len : -1; i != e; i += dir) {
573
        var change = set[i];
540 574
        var replaced = [], end = change.start + change.added;
541 575
        doc.iter(change.start, end, function(line) { replaced.push(line.text); });
542
        to.push({start: change.start, added: change.old.length, old: replaced});
576
        out.push({start: change.start, added: change.old.length, old: replaced});
543 577
        var pos = clipPos({line: change.start + change.old.length - 1,
544 578
                           ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])});
545 579
        updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos);
546
        updateInput = true;
547 580
      }
581
      updateInput = true;
582
      to.push(out);
548 583
    }
549
    function undo() {unredoHelper(history.done, history.undone);}
550
    function redo() {unredoHelper(history.undone, history.done);}
584
    function undo() {unredoHelper(history.done, history.undone, -1);}
585
    function redo() {unredoHelper(history.undone, history.done, 1);}
551 586

  
552 587
    function updateLinesNoUndo(from, to, newText, selFrom, selTo) {
588
      if (suppressEdits) return;
553 589
      var recomputeMaxLength = false, maxLineLength = maxLine.length;
554 590
      if (!options.lineWrapping)
555 591
        doc.iter(from.line, to.line, function(line) {
......
651 687
      setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line));
652 688

  
653 689
      // Make sure the scroll-size div has the correct height.
654
      code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px";
690
      if (scroller.clientHeight)
691
        code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px";
655 692
    }
656 693

  
657 694
    function replaceRange(code, from, to) {
......
729 766
    // supported or compatible enough yet to rely on.)
730 767
    var prevInput = "";
731 768
    function readInput() {
732
      if (leaveInputAlone || !focused || hasSelection(input)) return false;
769
      if (leaveInputAlone || !focused || hasSelection(input) || options.readOnly) return false;
733 770
      var text = input.value;
734 771
      if (text == prevInput) return false;
735 772
      shiftSelecting = null;
......
752 789
    }
753 790

  
754 791
    function focusInput() {
755
      if (!options.readOnly) input.focus();
792
      if (options.readOnly != "nocursor") input.focus();
756 793
    }
757 794

  
758 795
    function scrollEditorIntoView() {
......
809 846
      // Compute the new visible window
810 847
      var visible = visibleLines();
811 848
      // 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;
849
      if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) return;
813 850
      var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100);
814 851
      if (showingFrom < from && from - showingFrom < 20) from = showingFrom;
815 852
      if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo);
......
831 868
      intact.sort(function(a, b) {return a.domStart - b.domStart;});
832 869

  
833 870
      var th = textHeight(), gutterDisplay = gutter.style.display;
834
      lineDiv.style.display = gutter.style.display = "none";
871
      lineDiv.style.display = "none";
835 872
      patchDisplay(from, to, intact);
836
      lineDiv.style.display = "";
873
      lineDiv.style.display = gutter.style.display = "";
837 874

  
838 875
      // Position the mover div to align with the lines it's supposed
839 876
      // to be showing (which will cover the visible display)
......
844 881
      showingFrom = from; showingTo = to;
845 882
      displayOffset = heightAtLine(doc, from);
846 883
      mover.style.top = (displayOffset * th) + "px";
847
      code.style.height = (doc.height * th + 2 * paddingTop()) + "px";
884
      if (scroller.clientHeight)
885
        code.style.height = (doc.height * th + 2 * paddingTop()) + "px";
848 886

  
849 887
      // Since this is all rather error prone, it is honoured with the
850 888
      // only assertion in the whole file.
......
854 892

  
855 893
      if (options.lineWrapping) {
856 894
        maxWidth = scroller.clientWidth;
857
        var curNode = lineDiv.firstChild;
895
        var curNode = lineDiv.firstChild, heightChanged = false;
858 896
        doc.iter(showingFrom, showingTo, function(line) {
859 897
          if (!line.hidden) {
860 898
            var height = Math.round(curNode.offsetHeight / th) || 1;
861
            if (line.height != height) {updateLineHeight(line, height); gutterDirty = true;}
899
            if (line.height != height) {
900
              updateLineHeight(line, height);
901
              gutterDirty = heightChanged = true;
902
            }
862 903
          }
863 904
          curNode = curNode.nextSibling;
864 905
        });
906
        if (heightChanged)
907
          code.style.height = (doc.height * th + 2 * paddingTop()) + "px";
865 908
      } else {
866 909
        if (maxWidth == null) maxWidth = stringWidth(maxLine);
867 910
        if (maxWidth > scroller.clientWidth) {
......
875 918
      }
876 919
      gutter.style.display = gutterDisplay;
877 920
      if (different || gutterDirty) updateGutter();
878
      updateCursor();
921
      updateSelection();
879 922
      if (!suppressCallback && options.onUpdate) options.onUpdate(instance);
880 923
      return true;
881 924
    }
......
922 965
      }
923 966
      // This pass fills in the lines that actually changed.
924 967
      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 968
      var scratch = targetDocument.createElement("div"), newElt;
927 969
      doc.iter(from, to, function(line) {
928
        var ch1 = null, ch2 = null;
929
        if (inSel) {
930
          ch1 = 0;
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;}
934
          else {inSel = true; ch1 = sel.from.ch;}
935
        }
936 970
        if (nextIntact && nextIntact.to == j) nextIntact = intact.shift();
937 971
        if (!nextIntact || nextIntact.from > j) {
938
          if (line.hidden) scratch.innerHTML = "<pre></pre>";
939
          else scratch.innerHTML = line.getHTML(ch1, ch2, true, tabText);
972
          if (line.hidden) var html = scratch.innerHTML = "<pre></pre>";
973
          else {
974
            var html = '<pre>' + line.getHTML(tabText) + '</pre>';
975
            // Kludge to make sure the styled element lies behind the selection (by z-index)
976
            if (line.className)
977
              html = '<div style="position: relative"><pre class="' + line.className +
978
              '" style="position: absolute; left: 0; right: 0; top: 0; bottom: 0; z-index: -2">&#160;</pre>' + html + "</div>";
979
          }
980
          scratch.innerHTML = html;
940 981
          lineDiv.insertBefore(scratch.firstChild, curNode);
941 982
        } else {
942 983
          curNode = curNode.nextSibling;
......
975 1016
      lineSpace.style.marginLeft = gutter.offsetWidth + "px";
976 1017
      gutterDirty = false;
977 1018
    }
978
    function updateCursor() {
979
      var head = sel.inverted ? sel.from : sel.to, lh = textHeight();
980
      var pos = localCoords(head, true);
1019
    function updateSelection() {
1020
      var collapsed = posEq(sel.from, sel.to);
1021
      var fromPos = localCoords(sel.from, true);
1022
      var toPos = collapsed ? fromPos : localCoords(sel.to, true);
1023
      var headPos = sel.inverted ? fromPos : toPos, th = textHeight();
981 1024
      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";
984
      if (posEq(sel.from, sel.to)) {
985
        cursor.style.top = pos.y + "px";
986
        cursor.style.left = (options.lineWrapping ? Math.min(pos.x, lineSpace.offsetWidth) : pos.x) + "px";
1025
      inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px";
1026
      inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px";
1027
      if (collapsed) {
1028
        cursor.style.top = headPos.y + "px";
1029
        cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px";
987 1030
        cursor.style.display = "";
1031
        selectionDiv.style.display = "none";
1032
      } else {
1033
        var sameLine = fromPos.y == toPos.y, html = "";
1034
        function add(left, top, right, height) {
1035
          html += '<div class="CodeMirror-selected" style="position: absolute; left: ' + left +
1036
            'px; top: ' + top + 'px; right: ' + right + 'px; height: ' + height + 'px"></div>';
1037
        }
1038
        if (sel.from.ch && fromPos.y >= 0) {
1039
          var right = sameLine ? lineSpace.clientWidth - toPos.x : 0;
1040
          add(fromPos.x, fromPos.y, right, th);
1041
        }
1042
        var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0));
1043
        var middleHeight = Math.min(toPos.y, lineSpace.clientHeight) - middleStart;
1044
        if (middleHeight > 0.2 * th)
1045
          add(0, middleStart, 0, middleHeight);
1046
        if ((!sameLine || !sel.from.ch) && toPos.y < lineSpace.clientHeight - .5 * th)
1047
          add(0, toPos.y, lineSpace.clientWidth - toPos.x, th);
1048
        selectionDiv.innerHTML = html;
1049
        cursor.style.display = "none";
1050
        selectionDiv.style.display = "";
988 1051
      }
989
      else cursor.style.display = "none";
990 1052
    }
991 1053

  
992 1054
    function setShift(val) {
......
1019 1081
      else if (posEq(from, sel.to)) sel.inverted = false;
1020 1082
      else if (posEq(to, sel.from)) sel.inverted = true;
1021 1083

  
1022
      // Some ugly logic used to only mark the lines that actually did
1023
      // see a change in selection as changed, rather than the whole
1024
      // selected range.
1025
      if (posEq(from, to)) {
1026
        if (!posEq(sel.from, sel.to))
1027
          changes.push({from: oldFrom, to: oldTo + 1});
1028
      }
1029
      else if (posEq(sel.from, sel.to)) {
1030
        changes.push({from: from.line, to: to.line + 1});
1031
      }
1032
      else {
1033
        if (!posEq(from, sel.from)) {
1034
          if (from.line < oldFrom)
1035
            changes.push({from: from.line, to: Math.min(to.line, oldFrom) + 1});
1036
          else
1037
            changes.push({from: oldFrom, to: Math.min(oldTo, from.line) + 1});
1038
        }
1039
        if (!posEq(to, sel.to)) {
1040
          if (to.line < oldTo)
1041
            changes.push({from: Math.max(oldFrom, from.line), to: oldTo + 1});
1042
          else
1043
            changes.push({from: Math.max(from.line, oldTo), to: to.line + 1});
1044
        }
1045
      }
1046 1084
      sel.from = from; sel.to = to;
1047 1085
      selectionChanged = true;
1048 1086
    }
......
1123 1161
    function moveV(dir, unit) {
1124 1162
      var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true);
1125 1163
      if (goalColumn != null) pos.x = goalColumn;
1126
      if (unit == "page") dist = scroller.clientHeight;
1164
      if (unit == "page") dist = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight);
1127 1165
      else if (unit == "line") dist = textHeight();
1128 1166
      var target = coordsChar(pos.x, pos.y + dist * dir + 2);
1129 1167
      setCursor(target.line, target.ch, true);
......
1316 1354
        if (line.hidden != hidden) {
1317 1355
          line.hidden = hidden;
1318 1356
          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));
1357
          var fline = sel.from.line, tline = sel.to.line;
1358
          if (hidden && (fline == no || tline == no)) {
1359
            var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from;
1360
            var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to;
1361
            setSelection(from, to);
1362
          }
1322 1363
          return (gutterDirty = true);
1323 1364
        }
1324 1365
      });
......
1351 1392
      if (x <= 0) return 0;
1352 1393
      var lineObj = getLine(line), text = lineObj.text;
1353 1394
      function getX(len) {
1354
        measure.innerHTML = "<pre><span>" + lineObj.getHTML(null, null, false, tabText, len) + "</span></pre>";
1395
        measure.innerHTML = "<pre><span>" + lineObj.getHTML(tabText, len) + "</span></pre>";
1355 1396
        return measure.firstChild.firstChild.offsetWidth;
1356 1397
      }
1357 1398
      var from = 0, fromX = 0, to = text.length, toX;
......
1377 1418

  
1378 1419
    var tempId = Math.floor(Math.random() * 0xffffff).toString(16);
1379 1420
    function measureLine(line, ch) {
1421
      if (ch == 0) return {top: 0, left: 0};
1380 1422
      var extra = "";
1381 1423
      // Include extra text at the end to make sure the measured line is wrapped in the right way.
1382 1424
      if (options.lineWrapping) {
1383 1425
        var end = line.text.indexOf(" ", ch + 2);
1384 1426
        extra = htmlEscape(line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0)));
1385 1427
      }
1386
      measure.innerHTML = "<pre>" + line.getHTML(null, null, false, tabText, ch) +
1428
      measure.innerHTML = "<pre>" + line.getHTML(tabText, ch) +
1387 1429
        '<span id="CodeMirror-temp-' + tempId + '">' + htmlEscape(line.text.charAt(ch) || " ") + "</span>" +
1388 1430
        extra + "</pre>";
1389 1431
      var elt = document.getElementById("CodeMirror-temp-" + tempId);
1390 1432
      var top = elt.offsetTop, left = elt.offsetLeft;
1391 1433
      // Older IEs report zero offsets for spans directly after a wrap
1392
      if (ie && ch && top == 0 && left == 0) {
1434
      if (ie && top == 0 && left == 0) {
1393 1435
        var backup = document.createElement("span");
1394 1436
        backup.innerHTML = "x";
1395 1437
        elt.parentNode.insertBefore(backup, elt.nextSibling);
......
1667 1709
      if (selectionChanged) reScroll = !scrollCursorIntoView();
1668 1710
      if (changes.length) updated = updateDisplay(changes, true);
1669 1711
      else {
1670
        if (selectionChanged) updateCursor();
1712
        if (selectionChanged) updateSelection();
1671 1713
        if (gutterDirty) updateGutter();
1672 1714
      }
1673 1715
      if (reScroll) scrollCursorIntoView();
......
1714 1756
    theme: "default",
1715 1757
    indentUnit: 2,
1716 1758
    indentWithTabs: false,
1759
    smartIndent: true,
1717 1760
    tabSize: 4,
1718 1761
    keyMap: "default",
1719 1762
    extraKeys: null,
......
1740 1783
    document: window.document
1741 1784
  };
1742 1785

  
1743
  var mac = /Mac/.test(navigator.platform);
1786
  var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
1787
  var mac = ios || /Mac/.test(navigator.platform);
1744 1788
  var win = /Win/.test(navigator.platform);
1745 1789

  
1746 1790
  // Known modes, by name and by MIME
......
1883 1927
    return extraMap ? lookup(name, extraMap, map) : lookup(name, keyMap[map]);
1884 1928
  }
1885 1929
  function isModifierKey(event) {
1886
    var name = keyNames[event.keyCode];
1930
    var name = keyNames[e_prop(event, "keyCode")];
1887 1931
    return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
1888 1932
  }
1889 1933

  
......
2100 2144
      this.stateAfter = null;
2101 2145
      if (mk) {
2102 2146
        var diff = text.length - (to - from);
2103
        for (var i = 0, mark = mk[i]; i < mk.length; ++i) {
2147
        for (var i = 0; i < mk.length; ++i) {
2148
          var mark = mk[i];
2104 2149
          mark.clipTo(from == null, from || 0, to_ == null, to, diff);
2105 2150
          if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);}
2106 2151
        }
......
2227 2272
    indentation: function(tabSize) {return countColumn(this.text, null, tabSize);},
2228 2273
    // Produces an HTML fragment for the line, taking selection,
2229 2274
    // marking, and highlighting into account.
2230
    getHTML: function(sfrom, sto, includePre, tabText, endAt) {
2275
    getHTML: function(tabText, endAt) {
2231 2276
      var html = [], first = true;
2232
      if (includePre)
2233
        html.push(this.className ? '<pre class="' + this.className + '">': "<pre>");
2234 2277
      function span(text, style) {
2235 2278
        if (!text) return;
2236 2279
        // Work around a bug where, in some compat modes, IE ignores leading spaces
......
2240 2283
        else html.push(htmlEscape(text).replace(/\t/g, tabText));
2241 2284
      }
2242 2285
      var st = this.styles, allText = this.text, marked = this.marked;
2243
      if (sfrom == sto) sfrom = null;
2244 2286
      var len = allText.length;
2245 2287
      if (endAt != null) len = Math.min(endAt, len);
2288
      function styleToClass(style) {
2289
        if (!style) return null;
2290
        return "cm-" + style.replace(/ +/g, " cm-");
2291
      }
2246 2292

  
2247 2293
      if (!allText && endAt == null)
2248
        span(" ", sfrom != null && sto == null ? "CodeMirror-selected" : null);
2249
      else if (!marked && sfrom == null)
2294
        span(" ");
2295
      else if (!marked || !marked.length)
2250 2296
        for (var i = 0, ch = 0; ch < len; i+=2) {
2251 2297
          var str = st[i], style = st[i+1], l = str.length;
2252 2298
          if (ch + l > len) str = str.slice(0, len - ch);
2253 2299
          ch += l;
2254
          span(str, style && "cm-" + style);
2300
          span(str, styleToClass(style));
2255 2301
        }
2256 2302
      else {
2257 2303
        var pos = 0, i = 0, text = "", style, sg = 0;
2258
        var markpos = -1, mark = null;
2259
        function nextMark() {
2260
          if (marked) {
2261
            markpos += 1;
2262
            mark = (markpos < marked.length) ? marked[markpos] : null;
2304
        var nextChange = marked[0].from || 0, marks = [], markpos = 0;
2305
        function advanceMarks() {
2306
          var m;
2307
          while (markpos < marked.length &&
2308
                 ((m = marked[markpos]).from == pos || m.from == null)) {
2309
            if (m.style != null) marks.push(m);
2310
            ++markpos;
2263 2311
          }
2312
          nextChange = markpos < marked.length ? marked[markpos].from : Infinity;
2313
          for (var i = 0; i < marks.length; ++i) {
2314
            var to = marks[i].to || Infinity;
2315
            if (to == pos) marks.splice(i--, 1);
2316
            else nextChange = Math.min(to, nextChange);
2317
          }
2264 2318
        }
2265
        nextMark();
2319
        var m = 0;
2266 2320
        while (pos < len) {
2267
          var upto = len;
2268
          var extraStyle = "";
2269
          if (sfrom != null) {
2270
            if (sfrom > pos) upto = sfrom;
2271
            else if (sto == null || sto > pos) {
2272
              extraStyle = " CodeMirror-selected";
2273
              if (sto != null) upto = Math.min(upto, sto);
2321
          if (nextChange == pos) advanceMarks();
2322
          var upto = Math.min(len, nextChange);
2323
          while (true) {
2324
            if (text) {
2325
              var end = pos + text.length;
2326
              var appliedStyle = style;
2327
              for (var j = 0; j < marks.length; ++j)
2328
                appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].style;
2329
              span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle);
2330
              if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
2331
              pos = end;
2274 2332
            }
2333
            text = st[i++]; style = styleToClass(st[i++]);
2275 2334
          }
2276
          while (mark && mark.to != null && mark.to <= pos) nextMark();
2277
          if (mark) {
2278
            if (mark.from > pos) upto = Math.min(upto, mark.from);
2279
            else {
2280
              extraStyle += " " + mark.style;
2281
              if (mark.to != null) upto = Math.min(upto, mark.to);
2282
            }
2283
          }
2284
          for (;;) {
2285
            var end = pos + text.length;
2286
            var appliedStyle = style;
2287
            if (extraStyle) appliedStyle = style ? style + extraStyle : extraStyle;
2288
            span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle);
2289
            if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
2290
            pos = end;
2291
            text = st[i++]; style = "cm-" + st[i++];
2292
          }
2293 2335
        }
2294
        if (sfrom != null && sto == null) span(" ", "CodeMirror-selected");
2295 2336
      }
2296
      if (includePre) html.push("</pre>");
2297 2337
      return html.join("");
2298 2338
    },
2299 2339
    cleanUp: function() {
......
2384 2424
        var lines = [];
2385 2425
        this.collapse(lines);
2386 2426
        this.children = [new LeafChunk(lines)];
2427
        this.children[0].parent = this;
2387 2428
      }
2388 2429
    },
2389 2430
    collapse: function(lines) {
......
2514 2555
  History.prototype = {
2515 2556
    addChange: function(start, added, old) {
2516 2557
      this.undone.length = 0;
2517
      var time = +new Date, last = this.done[this.done.length - 1];
2518
      if (time - this.time > 400 || !last ||
2519
          last.start > start + added || last.start + last.added < start - last.added + last.old.length)
2520
        this.done.push({start: start, added: added, old: old});
2521
      else {
2558
      var time = +new Date, cur = this.done[this.done.length - 1], last = cur && cur[cur.length - 1];
2559
      var dtime = time - this.time;
2560
      if (dtime > 400 || !last) {
2561
        this.done.push([{start: start, added: added, old: old}]);
2562
      } else if (last.start > start + added || last.start + last.added < start - last.added + last.old.length) {
2563
        cur.push({start: start, added: added, old: old});
2564
      } else {
2522 2565
        var oldoff = 0;
2523 2566
        if (start < last.start) {
2524 2567
          for (var i = last.start - start - 1; i >= 0; --i)
......
2566 2609
    else if (e.button & 4) return 2;
2567 2610
  }
2568 2611

  
2612
  // Allow 3rd-party code to override event properties by adding an override
2613
  // object to an event object.
2614
  function e_prop(e, prop) {
2615
    var overridden = e.override && e.override.hasOwnProperty(prop);
2616
    return overridden ? e.override[prop] : e[prop];
2617
  }
2618

  
2569 2619
  // Event handler registration. If disconnect is true, it'll return a
2570 2620
  // function that unregisters the handler.
2571 2621
  function connect(node, type, handler, disconnect) {
......
2665 2715
  function eltText(node) {
2666 2716
    return node.textContent || node.innerText || node.nodeValue || "";
2667 2717
  }
2718
  function selectInput(node) {
2719
    if (ios) { // Mobile Safari apparently has a bug where select() is broken.
2720
      node.selectionStart = 0;
2721
      node.selectionEnd = node.value.length;
2722
    } else node.select();
2723
  }
2668 2724

  
2669 2725
  // Operations on {line, ch} objects.
2670 2726
  function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
......
2695 2751
  // Used to position the cursor after an undo/redo by finding the
2696 2752
  // last edited character.
2697 2753
  function editEnd(from, to) {
2698
    if (!to) return from ? from.length : 0;
2754
    if (!to) return 0;
2699 2755
    if (!from) return to.length;
2700 2756
    for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j)
2701 2757
      if (from.charAt(i) != to.charAt(j)) break;
trunk/scripts/codemirror/lib/codemirror.css
64 64
  visibility: visible;
65 65
}
66 66

  
67
span.CodeMirror-selected { background: #d9d9d9; }
68
.CodeMirror-focused span.CodeMirror-selected { background: #d2dcf8; }
67
div.CodeMirror-selected { background: #d9d9d9; }
68
.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; }
69 69

  
70
.CodeMirror-searching {background: #ffa;}
70
.CodeMirror-searching {
71
  background: #ffa;
72
  background: rgba(255, 255, 0, .4);
73
}
71 74

  
72 75
/* Default theme */
73 76

  
trunk/scripts/codemirror/lib/util/formatting.js
50 50
  var cmInstance = this;
51 51
  this.operation(function () {
52 52
    for (var i = from.line; i <= to.line; i++) {
53
      cmInstance.indentLine(i);
53
      cmInstance.indentLine(i, "smart");
54 54
    }
55 55
  });
56 56
});
......
70 70
    var startLine = cmInstance.posFromIndex(absStart).line;
71 71
    var endLine = cmInstance.posFromIndex(absStart + res.length).line;
72 72
    for (var i = startLine; i <= endLine; i++) {
73
      cmInstance.indentLine(i);
73
      cmInstance.indentLine(i, "smart");
74 74
    }
75 75
  });
76 76
});
trunk/scripts/codemirror/lib/util/foldcode.js
1
// the tagRangeFinder function is
2
//   Copyright (C) 2011 by Daniel Glazman <daniel@glazman.org>
3
// released under the MIT license (../../LICENSE) like the rest of CodeMirror
4
CodeMirror.tagRangeFinder = function(cm, line) {
5
  var nameStartChar = "A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";
6
  var nameChar = nameStartChar + "\-\.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
7
  var xmlNAMERegExp = new RegExp("^[" + nameStartChar + "][" + nameChar + "]*");
8

  
9
  var lineText = cm.getLine(line);
10
  var found = false;
11
  var tag = null;
12
  var pos = 0;
13
  while (!found) {
14
    pos = lineText.indexOf("<", pos);
15
    if (-1 == pos) // no tag on line
16
      return;
17
    if (pos + 1 < lineText.length && lineText[pos + 1] == "/") { // closing tag
18
      pos++;
19
      continue;
20
    }
21
    // ok we weem to have a start tag
22
    if (!lineText.substr(pos + 1).match(xmlNAMERegExp)) { // not a tag name...
23
      pos++;
24
      continue;
25
    }
26
    var gtPos = lineText.indexOf(">", pos + 1);
27
    if (-1 == gtPos) { // end of start tag not in line
28
      var l = line + 1;
29
      var foundGt = false;
30
      var lastLine = cm.lineCount();
31
      while (l < lastLine && !foundGt) {
32
        var lt = cm.getLine(l);
33
        var gt = lt.indexOf(">");
34
        if (-1 != gt) { // found a >
35
          foundGt = true;
36
          var slash = lt.lastIndexOf("/", gt);
37
          if (-1 != slash && slash < gt) {
38
            var str = lineText.substr(slash, gt - slash + 1);
39
            if (!str.match( /\/\s*\>/ )) // yep, that's the end of empty tag
40
              return l+1;
41
          }
42
        }
43
        l++;
44
      }
45
      found = true;
46
    }
47
    else {
48
      var slashPos = lineText.lastIndexOf("/", gtPos);
49
      if (-1 == slashPos) { // cannot be empty tag
50
        found = true;
51
        // don't continue
52
      }
53
      else { // empty tag?
54
        // check if really empty tag
55
        var str = lineText.substr(slashPos, gtPos - slashPos + 1);
56
        if (!str.match( /\/\s*\>/ )) { // finally not empty
57
          found = true;
58
          // don't continue
59
        }
60
      }
61
    }
62
    if (found) {
63
      var subLine = lineText.substr(pos + 1);
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff