OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (C) 2009 Google Inc. All rights reserved. | |
3 * | |
4 * Redistribution and use in source and binary forms, with or without | |
5 * modification, are permitted provided that the following conditions are | |
6 * met: | |
7 * | |
8 * * Redistributions of source code must retain the above copyright | |
9 * notice, this list of conditions and the following disclaimer. | |
10 * * Redistributions in binary form must reproduce the above | |
11 * copyright notice, this list of conditions and the following disclaimer | |
12 * in the documentation and/or other materials provided with the | |
13 * distribution. | |
14 * * Neither the name of Google Inc. nor the names of its | |
15 * contributors may be used to endorse or promote products derived from | |
16 * this software without specific prior written permission. | |
17 * | |
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
29 */ | |
30 | |
31 /** | |
32 * @constructor | |
33 * @param {WebInspector.TextRange} newRange | |
34 * @param {string} originalText | |
35 * @param {WebInspector.TextRange} originalSelection | |
36 */ | |
37 WebInspector.TextEditorCommand = function(newRange, originalText, originalSelect
ion) | |
38 { | |
39 this.newRange = newRange; | |
40 this.originalText = originalText; | |
41 this.originalSelection = originalSelection; | |
42 } | |
43 | |
44 /** | |
45 * @constructor | |
46 * @extends {WebInspector.Object} | |
47 */ | |
48 WebInspector.TextEditorModel = function() | |
49 { | |
50 this._lines = [""]; | |
51 this._attributes = []; | |
52 /** @type {Array.<WebInspector.TextEditorCommand>} */ | |
53 this._undoStack = []; | |
54 this._noPunctuationRegex = /[^ !%&()*+,-.:;<=>?\[\]\^{|}~]+/; | |
55 this._lineBreak = "\n"; | |
56 } | |
57 | |
58 WebInspector.TextEditorModel.Events = { | |
59 TextChanged: "TextChanged" | |
60 } | |
61 | |
62 WebInspector.TextEditorModel.endsWithBracketRegex = /[{(\[]\s*$/; | |
63 | |
64 WebInspector.TextEditorModel.prototype = { | |
65 /** | |
66 * @return {boolean} | |
67 */ | |
68 isClean: function() | |
69 { | |
70 return this._cleanState === this._undoStack.length; | |
71 }, | |
72 | |
73 markClean: function() | |
74 { | |
75 this._cleanState = this._undoStack.length; | |
76 }, | |
77 | |
78 /** | |
79 * @return {number} | |
80 */ | |
81 get linesCount() | |
82 { | |
83 return this._lines.length; | |
84 }, | |
85 | |
86 /** | |
87 * @return {string} | |
88 */ | |
89 text: function() | |
90 { | |
91 return this._lines.join(this._lineBreak); | |
92 }, | |
93 | |
94 /** | |
95 * @return {WebInspector.TextRange} | |
96 */ | |
97 range: function() | |
98 { | |
99 return new WebInspector.TextRange(0, 0, this._lines.length - 1, this._li
nes[this._lines.length - 1].length); | |
100 }, | |
101 | |
102 /** | |
103 * @return {string} | |
104 */ | |
105 get lineBreak() | |
106 { | |
107 return this._lineBreak; | |
108 }, | |
109 | |
110 /** | |
111 * @param {number} lineNumber | |
112 * @return {string} | |
113 */ | |
114 line: function(lineNumber) | |
115 { | |
116 if (lineNumber >= this._lines.length) | |
117 throw "Out of bounds:" + lineNumber; | |
118 return this._lines[lineNumber]; | |
119 }, | |
120 | |
121 /** | |
122 * @param {number} lineNumber | |
123 * @return {number} | |
124 */ | |
125 lineLength: function(lineNumber) | |
126 { | |
127 return this._lines[lineNumber].length; | |
128 }, | |
129 | |
130 /** | |
131 * @param {string} text | |
132 */ | |
133 setText: function(text) | |
134 { | |
135 this._resetUndoStack(); | |
136 text = text || ""; | |
137 this._attributes = []; | |
138 var range = this.range(); | |
139 this._lineBreak = /\r\n/.test(text) ? "\r\n" : "\n"; | |
140 var newRange = this._innerSetText(range, text); | |
141 this.dispatchEventToListeners(WebInspector.TextEditorModel.Events.TextCh
anged, { oldRange: range, newRange: newRange}); | |
142 }, | |
143 | |
144 /** | |
145 * @param {WebInspector.TextRange} range | |
146 * @return {boolean} | |
147 */ | |
148 _rangeHasOneCharacter: function(range) | |
149 { | |
150 if (range.startLine === range.endLine && range.endColumn - range.startCo
lumn === 1) | |
151 return true; | |
152 if (range.endLine - range.startLine === 1 && range.endColumn === 0 && ra
nge.startColumn === this.lineLength(range.startLine)) | |
153 return true; | |
154 return false; | |
155 }, | |
156 | |
157 /** | |
158 * @param {WebInspector.TextRange} range | |
159 * @param {string} text | |
160 * @param {WebInspector.TextRange=} originalSelection | |
161 * @return {boolean} | |
162 */ | |
163 _isEditRangeUndoBoundary: function(range, text, originalSelection) | |
164 { | |
165 if (originalSelection && !originalSelection.isEmpty()) | |
166 return true; | |
167 if (text) | |
168 return text.length > 1 || !range.isEmpty(); | |
169 return !this._rangeHasOneCharacter(range); | |
170 }, | |
171 | |
172 /** | |
173 * @param {WebInspector.TextRange} range | |
174 * @param {string} text | |
175 * @return {boolean} | |
176 */ | |
177 _isEditRangeAdjacentToLastCommand: function(range, text) | |
178 { | |
179 if (!this._lastCommand) | |
180 return true; | |
181 if (!text) { | |
182 // FIXME: Distinguish backspace and delete in lastCommand. | |
183 return this._lastCommand.newRange.immediatelyPrecedes(range) || this
._lastCommand.newRange.immediatelyFollows(range); | |
184 } | |
185 return text.indexOf("\n") === -1 && this._lastCommand.newRange.immediate
lyPrecedes(range); | |
186 }, | |
187 | |
188 /** | |
189 * @param {WebInspector.TextRange} range | |
190 * @param {string} text | |
191 * @param {WebInspector.TextRange=} originalSelection | |
192 * @return {WebInspector.TextRange} | |
193 */ | |
194 editRange: function(range, text, originalSelection) | |
195 { | |
196 var undoBoundary = this._isEditRangeUndoBoundary(range, text, originalSe
lection); | |
197 if (undoBoundary || !this._isEditRangeAdjacentToLastCommand(range, text)
) | |
198 this._markUndoableState(); | |
199 var newRange = this._innerEditRange(range, text, originalSelection); | |
200 if (undoBoundary) | |
201 this._markUndoableState(); | |
202 return newRange; | |
203 }, | |
204 | |
205 /** | |
206 * @param {WebInspector.TextRange} range | |
207 * @param {string} text | |
208 * @param {WebInspector.TextRange=} originalSelection | |
209 * @return {WebInspector.TextRange} | |
210 */ | |
211 _innerEditRange: function(range, text, originalSelection) | |
212 { | |
213 var originalText = this.copyRange(range); | |
214 var newRange = this._innerSetText(range, text); | |
215 this._lastCommand = this._pushUndoableCommand(newRange, originalText, or
iginalSelection || range); | |
216 this.dispatchEventToListeners(WebInspector.TextEditorModel.Events.TextCh
anged, { oldRange: range, newRange: newRange, editRange: true }); | |
217 return newRange; | |
218 }, | |
219 | |
220 /** | |
221 * @param {WebInspector.TextRange} range | |
222 * @param {string} text | |
223 * @return {WebInspector.TextRange} | |
224 */ | |
225 _innerSetText: function(range, text) | |
226 { | |
227 this._eraseRange(range); | |
228 if (text === "") | |
229 return new WebInspector.TextRange(range.startLine, range.startColumn
, range.startLine, range.startColumn); | |
230 | |
231 var newLines = text.split(/\r?\n/); | |
232 | |
233 var prefix = this._lines[range.startLine].substring(0, range.startColumn
); | |
234 var suffix = this._lines[range.startLine].substring(range.startColumn); | |
235 | |
236 var postCaret = prefix.length; | |
237 // Insert text. | |
238 if (newLines.length === 1) { | |
239 this._setLine(range.startLine, prefix + newLines[0] + suffix); | |
240 postCaret += newLines[0].length; | |
241 } else { | |
242 this._setLine(range.startLine, prefix + newLines[0]); | |
243 this._insertLines(range, newLines); | |
244 this._setLine(range.startLine + newLines.length - 1, newLines[newLin
es.length - 1] + suffix); | |
245 postCaret = newLines[newLines.length - 1].length; | |
246 } | |
247 | |
248 return new WebInspector.TextRange(range.startLine, range.startColumn, | |
249 range.startLine + newLines.length - 1,
postCaret); | |
250 }, | |
251 | |
252 /** | |
253 * @param {WebInspector.TextRange} range | |
254 * @param {Array.<string>} newLines | |
255 */ | |
256 _insertLines: function(range, newLines) | |
257 { | |
258 var lines = new Array(this._lines.length + newLines.length - 1); | |
259 for (var i = 0; i <= range.startLine; ++i) | |
260 lines[i] = this._lines[i]; | |
261 // Line at [0] is already set via setLine. | |
262 for (var i = 1; i < newLines.length; ++i) | |
263 lines[range.startLine + i] = newLines[i]; | |
264 for (var i = range.startLine + newLines.length; i < lines.length; ++i) | |
265 lines[i] = this._lines[i - newLines.length + 1]; | |
266 this._lines = lines; | |
267 | |
268 // Adjust attributes, attributes move with the first character of line. | |
269 var attributes = new Array(lines.length); | |
270 var insertionIndex = range.startColumn ? range.startLine + 1 : range.sta
rtLine; | |
271 for (var i = 0; i < insertionIndex; ++i) | |
272 attributes[i] = this._attributes[i]; | |
273 for (var i = insertionIndex + newLines.length - 1; i < attributes.length
; ++i) | |
274 attributes[i] = this._attributes[i - newLines.length + 1]; | |
275 this._attributes = attributes; | |
276 }, | |
277 | |
278 /** | |
279 * @param {WebInspector.TextRange} range | |
280 */ | |
281 _eraseRange: function(range) | |
282 { | |
283 if (range.isEmpty()) | |
284 return; | |
285 | |
286 var prefix = this._lines[range.startLine].substring(0, range.startColumn
); | |
287 var suffix = this._lines[range.endLine].substring(range.endColumn); | |
288 | |
289 if (range.endLine > range.startLine) { | |
290 this._lines.splice(range.startLine + 1, range.endLine - range.startL
ine); | |
291 // Adjust attributes, attributes move with the first character of li
ne. | |
292 this._attributes.splice(range.startColumn ? range.startLine + 1 : ra
nge.startLine, range.endLine - range.startLine); | |
293 } | |
294 this._setLine(range.startLine, prefix + suffix); | |
295 }, | |
296 | |
297 /** | |
298 * @param {number} lineNumber | |
299 * @param {string} text | |
300 */ | |
301 _setLine: function(lineNumber, text) | |
302 { | |
303 this._lines[lineNumber] = text; | |
304 }, | |
305 | |
306 /** | |
307 * @param {number} lineNumber | |
308 * @param {number} column | |
309 * @return {WebInspector.TextRange} | |
310 */ | |
311 wordRange: function(lineNumber, column) | |
312 { | |
313 return new WebInspector.TextRange(lineNumber, this.wordStart(lineNumber,
column, true), lineNumber, this.wordEnd(lineNumber, column, true)); | |
314 }, | |
315 | |
316 /** | |
317 * @param {number} lineNumber | |
318 * @param {number} column | |
319 * @param {boolean} gapless | |
320 * @return {number} | |
321 */ | |
322 wordStart: function(lineNumber, column, gapless) | |
323 { | |
324 var line = this._lines[lineNumber]; | |
325 var prefix = line.substring(0, column).split("").reverse().join(""); | |
326 var prefixMatch = this._noPunctuationRegex.exec(prefix); | |
327 return prefixMatch && (!gapless || prefixMatch.index === 0) ? column - p
refixMatch.index - prefixMatch[0].length : column; | |
328 }, | |
329 | |
330 /** | |
331 * @param {number} lineNumber | |
332 * @param {number} column | |
333 * @param {boolean} gapless | |
334 * @return {number} | |
335 */ | |
336 wordEnd: function(lineNumber, column, gapless) | |
337 { | |
338 var line = this._lines[lineNumber]; | |
339 var suffix = line.substring(column); | |
340 var suffixMatch = this._noPunctuationRegex.exec(suffix); | |
341 return suffixMatch && (!gapless || suffixMatch.index === 0) ? column + s
uffixMatch.index + suffixMatch[0].length : column; | |
342 }, | |
343 | |
344 /** | |
345 * @param {WebInspector.TextRange} range | |
346 * @return {string} | |
347 */ | |
348 copyRange: function(range) | |
349 { | |
350 if (!range) | |
351 range = this.range(); | |
352 | |
353 var clip = []; | |
354 if (range.startLine === range.endLine) { | |
355 clip.push(this._lines[range.startLine].substring(range.startColumn,
range.endColumn)); | |
356 return clip.join(this._lineBreak); | |
357 } | |
358 clip.push(this._lines[range.startLine].substring(range.startColumn)); | |
359 for (var i = range.startLine + 1; i < range.endLine; ++i) | |
360 clip.push(this._lines[i]); | |
361 clip.push(this._lines[range.endLine].substring(0, range.endColumn)); | |
362 return clip.join(this._lineBreak); | |
363 }, | |
364 | |
365 /** | |
366 * @param {number} line | |
367 * @param {string} name | |
368 * @param {Object?} value | |
369 */ | |
370 setAttribute: function(line, name, value) | |
371 { | |
372 var attrs = this._attributes[line]; | |
373 if (!attrs) { | |
374 attrs = {}; | |
375 this._attributes[line] = attrs; | |
376 } | |
377 attrs[name] = value; | |
378 }, | |
379 | |
380 /** | |
381 * @param {number} line | |
382 * @param {string} name | |
383 * @return {Object|null} value | |
384 */ | |
385 getAttribute: function(line, name) | |
386 { | |
387 var attrs = this._attributes[line]; | |
388 return attrs ? attrs[name] : null; | |
389 }, | |
390 | |
391 /** | |
392 * @param {number} line | |
393 * @param {string} name | |
394 */ | |
395 removeAttribute: function(line, name) | |
396 { | |
397 var attrs = this._attributes[line]; | |
398 if (attrs) | |
399 delete attrs[name]; | |
400 }, | |
401 | |
402 /** | |
403 * @param {WebInspector.TextRange} newRange | |
404 * @param {string} originalText | |
405 * @param {WebInspector.TextRange} originalSelection | |
406 * @return {WebInspector.TextEditorCommand} | |
407 */ | |
408 _pushUndoableCommand: function(newRange, originalText, originalSelection) | |
409 { | |
410 var command = new WebInspector.TextEditorCommand(newRange.clone(), origi
nalText, originalSelection); | |
411 if (this._inUndo) | |
412 this._redoStack.push(command); | |
413 else { | |
414 if (!this._inRedo) { | |
415 this._redoStack = []; | |
416 if (typeof this._cleanState === "number" && this._cleanState > t
his._undoStack.length) | |
417 delete this._cleanState; | |
418 } | |
419 this._undoStack.push(command); | |
420 } | |
421 return command; | |
422 }, | |
423 | |
424 /** | |
425 * @return {?WebInspector.TextRange} | |
426 */ | |
427 undo: function() | |
428 { | |
429 if (!this._undoStack.length) | |
430 return null; | |
431 | |
432 this._markRedoableState(); | |
433 | |
434 this._inUndo = true; | |
435 var range = this._doUndo(this._undoStack); | |
436 delete this._inUndo; | |
437 | |
438 return range; | |
439 }, | |
440 | |
441 /** | |
442 * @return {WebInspector.TextRange} | |
443 */ | |
444 redo: function() | |
445 { | |
446 if (!this._redoStack || !this._redoStack.length) | |
447 return null; | |
448 this._markUndoableState(); | |
449 | |
450 this._inRedo = true; | |
451 var range = this._doUndo(this._redoStack); | |
452 delete this._inRedo; | |
453 | |
454 return range ? range.collapseToEnd() : null; | |
455 }, | |
456 | |
457 /** | |
458 * @param {Array.<WebInspector.TextEditorCommand>} stack | |
459 * @return {WebInspector.TextRange} | |
460 */ | |
461 _doUndo: function(stack) | |
462 { | |
463 var range = null; | |
464 for (var i = stack.length - 1; i >= 0; --i) { | |
465 var command = stack[i]; | |
466 stack.length = i; | |
467 this._innerEditRange(command.newRange, command.originalText); | |
468 range = command.originalSelection; | |
469 if (i > 0 && stack[i - 1].explicit) | |
470 return range; | |
471 } | |
472 return range; | |
473 }, | |
474 | |
475 _markUndoableState: function() | |
476 { | |
477 if (this._undoStack.length) | |
478 this._undoStack[this._undoStack.length - 1].explicit = true; | |
479 }, | |
480 | |
481 _markRedoableState: function() | |
482 { | |
483 if (this._redoStack.length) | |
484 this._redoStack[this._redoStack.length - 1].explicit = true; | |
485 }, | |
486 | |
487 _resetUndoStack: function() | |
488 { | |
489 delete this._cleanState; | |
490 this._undoStack = []; | |
491 this._redoStack = []; | |
492 }, | |
493 | |
494 /** | |
495 * @param {WebInspector.TextRange} range | |
496 * @return {WebInspector.TextRange} | |
497 */ | |
498 indentLines: function(range) | |
499 { | |
500 this._markUndoableState(); | |
501 | |
502 var indent = WebInspector.settings.textEditorIndent.get(); | |
503 var newRange = range.clone(); | |
504 // Do not change a selection start position when it is at the beginning
of a line | |
505 if (range.startColumn) | |
506 newRange.startColumn += indent.length; | |
507 | |
508 var indentEndLine = range.endLine; | |
509 if (range.endColumn) | |
510 newRange.endColumn += indent.length; | |
511 else | |
512 indentEndLine--; | |
513 | |
514 for (var lineNumber = range.startLine; lineNumber <= indentEndLine; line
Number++) | |
515 this._innerEditRange(WebInspector.TextRange.createFromLocation(lineN
umber, 0), indent); | |
516 | |
517 return newRange; | |
518 }, | |
519 | |
520 /** | |
521 * @param {WebInspector.TextRange} range | |
522 * @return {WebInspector.TextRange} | |
523 */ | |
524 unindentLines: function(range) | |
525 { | |
526 this._markUndoableState(); | |
527 | |
528 var indent = WebInspector.settings.textEditorIndent.get(); | |
529 var indentLength = indent === WebInspector.TextUtils.Indent.TabCharacter
? 4 : indent.length; | |
530 var lineIndentRegex = new RegExp("^ {1," + indentLength + "}"); | |
531 var newRange = range.clone(); | |
532 | |
533 var indentEndLine = range.endLine; | |
534 if (!range.endColumn) | |
535 indentEndLine--; | |
536 | |
537 for (var lineNumber = range.startLine; lineNumber <= indentEndLine; line
Number++) { | |
538 var line = this.line(lineNumber); | |
539 var firstCharacter = line.charAt(0); | |
540 var lineIndentLength; | |
541 | |
542 if (firstCharacter === " ") | |
543 lineIndentLength = line.match(lineIndentRegex)[0].length; | |
544 else if (firstCharacter === "\t") | |
545 lineIndentLength = 1; | |
546 else | |
547 continue; | |
548 | |
549 this._innerEditRange(new WebInspector.TextRange(lineNumber, 0, lineN
umber, lineIndentLength), ""); | |
550 | |
551 if (lineNumber === range.startLine) | |
552 newRange.startColumn = Math.max(0, newRange.startColumn - lineIn
dentLength); | |
553 if (lineNumber === range.endLine) | |
554 newRange.endColumn = Math.max(0, newRange.endColumn - lineIndent
Length); | |
555 } | |
556 | |
557 return newRange; | |
558 }, | |
559 | |
560 /** | |
561 * @param {number=} from | |
562 * @param {number=} to | |
563 * @return {WebInspector.TextEditorModel} | |
564 */ | |
565 slice: function(from, to) | |
566 { | |
567 var textModel = new WebInspector.TextEditorModel(); | |
568 textModel._lines = this._lines.slice(from, to); | |
569 textModel._lineBreak = this._lineBreak; | |
570 return textModel; | |
571 }, | |
572 | |
573 /** | |
574 * @param {WebInspector.TextRange} range | |
575 * @return {WebInspector.TextRange} | |
576 */ | |
577 growRangeLeft: function(range) | |
578 { | |
579 var result = range.clone(); | |
580 if (result.startColumn) | |
581 --result.startColumn; | |
582 else if (result.startLine) | |
583 result.startColumn = this.lineLength(--result.startLine); | |
584 return result; | |
585 }, | |
586 | |
587 /** | |
588 * @param {WebInspector.TextRange} range | |
589 * @return {WebInspector.TextRange} | |
590 */ | |
591 growRangeRight: function(range) | |
592 { | |
593 var result = range.clone(); | |
594 if (result.endColumn < this.lineLength(result.endLine)) | |
595 ++result.endColumn; | |
596 else if (result.endLine < this.linesCount) { | |
597 result.endColumn = 0; | |
598 ++result.endLine; | |
599 } | |
600 return result; | |
601 }, | |
602 | |
603 __proto__: WebInspector.Object.prototype | |
604 } | |
605 | |
606 /** | |
607 * @constructor | |
608 * @param {WebInspector.TextEditorModel} textModel | |
609 */ | |
610 WebInspector.TextEditorModel.BraceMatcher = function(textModel) | |
611 { | |
612 this._textModel = textModel; | |
613 } | |
614 | |
615 WebInspector.TextEditorModel.BraceMatcher.prototype = { | |
616 /** | |
617 * @param {number} lineNumber | |
618 * @return {Array.<{startColumn: number, endColumn: number, token: string}>} | |
619 */ | |
620 _braceRanges: function(lineNumber) | |
621 { | |
622 if (lineNumber >= this._textModel.linesCount || lineNumber < 0) | |
623 return null; | |
624 | |
625 var attribute = this._textModel.getAttribute(lineNumber, "highlight"); | |
626 if (!attribute) | |
627 return null; | |
628 else | |
629 return attribute.braces; | |
630 }, | |
631 | |
632 /** | |
633 * @param {string} braceTokenLeft | |
634 * @param {string} braceTokenRight | |
635 * @return {boolean} | |
636 */ | |
637 _matches: function(braceTokenLeft, braceTokenRight) | |
638 { | |
639 return ((braceTokenLeft === "brace-start" && braceTokenRight === "brace-
end") || (braceTokenLeft === "block-start" && braceTokenRight === "block-end")); | |
640 }, | |
641 | |
642 /** | |
643 * @param {number} lineNumber | |
644 * @param {number} column | |
645 * @param {number=} maxBraceIteration | |
646 * @return {?{lineNumber: number, column: number, token: string}} | |
647 */ | |
648 findLeftCandidate: function(lineNumber, column, maxBraceIteration) | |
649 { | |
650 var braces = this._braceRanges(lineNumber); | |
651 if (!braces) | |
652 return null; | |
653 | |
654 var braceIndex = braces.length - 1; | |
655 while (braceIndex >= 0 && braces[braceIndex].startColumn > column) | |
656 --braceIndex; | |
657 | |
658 var brace = braceIndex >= 0 ? braces[braceIndex] : null; | |
659 if (brace && brace.startColumn === column && (brace.token === "block-end
" || brace.token === "brace-end")) | |
660 --braceIndex; | |
661 | |
662 var stack = []; | |
663 maxBraceIteration = maxBraceIteration || Number.MAX_VALUE; | |
664 while (--maxBraceIteration) { | |
665 if (braceIndex < 0) { | |
666 while ((braces = this._braceRanges(--lineNumber)) && !braces.len
gth) {}; | |
667 if (!braces) | |
668 return null; | |
669 braceIndex = braces.length - 1; | |
670 } | |
671 brace = braces[braceIndex]; | |
672 if (brace.token === "block-end" || brace.token === "brace-end") | |
673 stack.push(brace.token); | |
674 else if (stack.length === 0) | |
675 return { | |
676 lineNumber: lineNumber, | |
677 column: brace.startColumn, | |
678 token: brace.token | |
679 }; | |
680 else if (!this._matches(brace.token, stack.pop())) | |
681 return null; | |
682 | |
683 --braceIndex; | |
684 } | |
685 return null; | |
686 }, | |
687 | |
688 /** | |
689 * @param {number} lineNumber | |
690 * @param {number} column | |
691 * @param {number=} maxBraceIteration | |
692 * @return {?{lineNumber: number, column: number, token: string}} | |
693 */ | |
694 findRightCandidate: function(lineNumber, column, maxBraceIteration) | |
695 { | |
696 var braces = this._braceRanges(lineNumber); | |
697 if (!braces) | |
698 return null; | |
699 | |
700 var braceIndex = 0; | |
701 while (braceIndex < braces.length && braces[braceIndex].startColumn < co
lumn) | |
702 ++braceIndex; | |
703 | |
704 var brace = braceIndex < braces.length ? braces[braceIndex] : null; | |
705 if (brace && brace.startColumn === column && (brace.token === "block-sta
rt" || brace.token === "brace-start")) | |
706 ++braceIndex; | |
707 | |
708 var stack = []; | |
709 maxBraceIteration = maxBraceIteration || Number.MAX_VALUE; | |
710 while (--maxBraceIteration) { | |
711 if (braceIndex >= braces.length) { | |
712 while ((braces = this._braceRanges(++lineNumber)) && !braces.len
gth) {}; | |
713 if (!braces) | |
714 return null; | |
715 braceIndex = 0; | |
716 } | |
717 brace = braces[braceIndex]; | |
718 if (brace.token === "block-start" || brace.token === "brace-start") | |
719 stack.push(brace.token); | |
720 else if (stack.length === 0) | |
721 return { | |
722 lineNumber: lineNumber, | |
723 column: brace.startColumn, | |
724 token: brace.token | |
725 }; | |
726 else if (!this._matches(stack.pop(), brace.token)) | |
727 return null; | |
728 ++braceIndex; | |
729 } | |
730 return null; | |
731 }, | |
732 | |
733 /** | |
734 * @param {number} lineNumber | |
735 * @param {number} column | |
736 * @param {number=} maxBraceIteration | |
737 * @return {?{leftBrace: {lineNumber: number, column: number, token: string}
, rightBrace: {lineNumber: number, column: number, token: string}}} | |
738 */ | |
739 enclosingBraces: function(lineNumber, column, maxBraceIteration) | |
740 { | |
741 var leftBraceLocation = this.findLeftCandidate(lineNumber, column, maxBr
aceIteration); | |
742 if (!leftBraceLocation) | |
743 return null; | |
744 | |
745 var rightBraceLocation = this.findRightCandidate(lineNumber, column, max
BraceIteration); | |
746 if (!rightBraceLocation) | |
747 return null; | |
748 | |
749 if (!this._matches(leftBraceLocation.token, rightBraceLocation.token)) | |
750 return null; | |
751 | |
752 return { | |
753 leftBrace: leftBraceLocation, | |
754 rightBrace: rightBraceLocation | |
755 }; | |
756 }, | |
757 } | |
OLD | NEW |