Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(75)

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/source_frame/SourcesTextEditor.js

Issue 2238883004: DevTools: Split off SourcesTextEditor from CodeMirrorTextEditor (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Stray line Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6 * @constructor
7 * @extends {WebInspector.CodeMirrorTextEditor}
8 * @param {!WebInspector.SourcesTextEditorDelegate} delegate
9 */
10 WebInspector.SourcesTextEditor = function(delegate)
11 {
12 WebInspector.CodeMirrorTextEditor.call(this);
13
14 this.codeMirror().addKeyMap({
15 "Enter": "smartNewlineAndIndent",
16 "Esc": "sourcesDismiss"
17 });
18
19 this._delegate = delegate;
20
21 this.codeMirror().on("changes", this._changesForDelegate.bind(this));
22 this.codeMirror().on("cursorActivity", this._cursorActivity.bind(this));
23 this.codeMirror().on("gutterClick", this._gutterClick.bind(this));
24 this.codeMirror().on("scroll", this._scroll.bind(this));
25 this.codeMirror().on("focus", this._focus.bind(this));
26 this.codeMirror().on("beforeSelectionChange", this._beforeSelectionChangeFor Delegate.bind(this));
27 this.element.addEventListener("contextmenu", this._contextMenu.bind(this), f alse);
28
29 this._blockIndentController = new WebInspector.SourcesTextEditor.BlockIndent Controller(this.codeMirror());
30 this._tokenHighlighter = new WebInspector.SourcesTextEditor.TokenHighlighter (this, this.codeMirror());
31
32 /** @type {!Array<string>} */
33 this._gutters = ["CodeMirror-linenumbers"];
34
35 /**
36 * @this {WebInspector.SourcesTextEditor}
37 */
38 function updateAnticipateJumpFlag(value)
39 {
40 this._isHandlingMouseDownEvent = value;
41 }
42
43 this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(thi s, true), true);
44 this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(thi s, false), false);
45 WebInspector.moduleSetting("textEditorIndent").addChangeListener(this._onUpd ateEditorIndentation, this);
46 WebInspector.moduleSetting("textEditorAutoDetectIndent").addChangeListener(t his._onUpdateEditorIndentation, this);
47 WebInspector.moduleSetting("showWhitespacesInEditor").addChangeListener(this ._updateWhitespace, this);
48
49 this._onUpdateEditorIndentation();
50 this._setupWhitespaceHighlight();
51 }
52 WebInspector.SourcesTextEditor.prototype = {
53 /**
54 * @return {boolean}
55 */
56 _isSearchActive: function()
57 {
58 return !!this._tokenHighlighter.highlightedRegex();
59 },
60
61 /**
62 * @param {!RegExp} regex
63 * @param {?WebInspector.TextRange} range
64 */
65 highlightSearchResults: function(regex, range)
66 {
67 /**
68 * @this {WebInspector.CodeMirrorTextEditor}
69 */
70 function innerHighlightRegex()
71 {
72 if (range) {
73 this.scrollLineIntoView(range.startLine);
74 if (range.endColumn > WebInspector.CodeMirrorTextEditor.maxHighl ightLength)
75 this.setSelection(range);
76 else
77 this.setSelection(WebInspector.TextRange.createFromLocation( range.startLine, range.startColumn));
78 }
79 this._tokenHighlighter.highlightSearchResults(regex, range);
80 }
81
82 if (!this._selectionBeforeSearch)
83 this._selectionBeforeSearch = this.selection();
84
85 this.codeMirror().operation(innerHighlightRegex.bind(this));
86 },
87
88 cancelSearchResultsHighlight: function()
89 {
90 this.codeMirror().operation(this._tokenHighlighter.highlightSelectedToke ns.bind(this._tokenHighlighter));
91
92 if (this._selectionBeforeSearch) {
93 this._reportJump(this._selectionBeforeSearch, this.selection());
94 delete this._selectionBeforeSearch;
95 }
96 },
97
98 /**
99 * @param {!Object} highlightDescriptor
100 */
101 removeHighlight: function(highlightDescriptor)
102 {
103 highlightDescriptor.clear();
104 },
105
106 /**
107 * @param {!WebInspector.TextRange} range
108 * @param {string} cssClass
109 * @return {!Object}
110 */
111 highlightRange: function(range, cssClass)
112 {
113 cssClass = "CodeMirror-persist-highlight " + cssClass;
114 var pos = WebInspector.CodeMirrorUtils.toPos(range);
115 ++pos.end.ch;
116 return this.codeMirror().markText(pos.start, pos.end, {
117 className: cssClass,
118 startStyle: cssClass + "-start",
119 endStyle: cssClass + "-end"
120 });
121 },
122
123 /**
124 * @param {number} lineNumber
125 * @param {boolean} disabled
126 * @param {boolean} conditional
127 */
128 addBreakpoint: function(lineNumber, disabled, conditional)
129 {
130 if (lineNumber < 0 || lineNumber >= this.codeMirror().lineCount())
131 return;
132
133 var className = "cm-breakpoint" + (conditional ? " cm-breakpoint-conditi onal" : "") + (disabled ? " cm-breakpoint-disabled" : "");
134 this.codeMirror().addLineClass(lineNumber, "wrap", className);
135 },
136
137 /**
138 * @param {number} lineNumber
139 */
140 removeBreakpoint: function(lineNumber)
141 {
142 if (lineNumber < 0 || lineNumber >= this.codeMirror().lineCount())
143 return;
144
145 var wrapClasses = this.codeMirror().getLineHandle(lineNumber).wrapClass;
146 if (!wrapClasses)
147 return;
148
149 var classes = wrapClasses.split(" ");
150 for (var i = 0; i < classes.length; ++i) {
151 if (classes[i].startsWith("cm-breakpoint"))
152 this.codeMirror().removeLineClass(lineNumber, "wrap", classes[i] );
153 }
154 },
155
156 /**
157 * @param {string} type
158 * @param {boolean} leftToNumbers
159 */
160 installGutter: function(type, leftToNumbers)
161 {
162 if (this._gutters.indexOf(type) !== -1)
163 return;
164
165 if (leftToNumbers)
166 this._gutters.unshift(type);
167 else
168 this._gutters.push(type);
169
170 this.codeMirror().setOption("gutters", this._gutters.slice());
171 this.codeMirror().refresh();
172 },
173
174 /**
175 * @param {string} type
176 */
177 uninstallGutter: function(type)
178 {
179 this._gutters = this._gutters.filter(gutter => gutter !== type);
180 this.codeMirror().setOption("gutters", this._gutters.slice());
181 this.codeMirror().refresh();
182 },
183
184 /**
185 * @param {number} lineNumber
186 * @param {string} type
187 * @param {?Element} element
188 */
189 setGutterDecoration: function(lineNumber, type, element)
190 {
191 console.assert(this._gutters.indexOf(type) !== -1, "Cannot decorate unex isting gutter.")
192 this.codeMirror().setGutterMarker(lineNumber, type, element);
193 },
194
195 /**
196 * @param {number} lineNumber
197 * @param {number} columnNumber
198 */
199 setExecutionLocation: function(lineNumber, columnNumber)
200 {
201 this.clearPositionHighlight();
202
203 this._executionLine = this.codeMirror().getLineHandle(lineNumber);
204 if (!this._executionLine)
205 return;
206
207 this.codeMirror().addLineClass(this._executionLine, "wrap", "cm-executio n-line");
208 this._executionLineTailMarker = this.codeMirror().markText({ line: lineN umber, ch: columnNumber }, { line: lineNumber, ch: this.codeMirror().getLine(lin eNumber).length }, { className: "cm-execution-line-tail" });
209 },
210
211 clearExecutionLine: function()
212 {
213 this.clearPositionHighlight();
214
215 if (this._executionLine)
216 this.codeMirror().removeLineClass(this._executionLine, "wrap", "cm-e xecution-line");
217 delete this._executionLine;
218
219 if (this._executionLineTailMarker)
220 this._executionLineTailMarker.clear();
221 delete this._executionLineTailMarker;
222 },
223
224 /**
225 * @param {number} lineNumber
226 * @param {string} className
227 * @param {boolean} toggled
228 */
229 toggleLineClass: function(lineNumber, className, toggled)
230 {
231 if (this.hasLineClass(lineNumber, className) === toggled)
232 return;
233
234 var lineHandle = this.codeMirror().getLineHandle(lineNumber);
235 if (!lineHandle)
236 return;
237
238 if (toggled) {
239 this.codeMirror().addLineClass(lineHandle, "gutter", className);
240 this.codeMirror().addLineClass(lineHandle, "wrap", className);
241 } else {
242 this.codeMirror().removeLineClass(lineHandle, "gutter", className);
243 this.codeMirror().removeLineClass(lineHandle, "wrap", className);
244 }
245 },
246
247 /**
248 * @param {number} lineNumber
249 * @param {string} className
250 * @return {boolean}
251 */
252 hasLineClass: function(lineNumber, className)
253 {
254 var lineInfo = this.codeMirror().lineInfo(lineNumber);
255 var wrapClass = lineInfo.wrapClass || "";
256 var classNames = wrapClass.split(" ");
257 return classNames.indexOf(className) !== -1;
258 },
259
260 _gutterClick: function(instance, lineNumber, gutter, event)
261 {
262 this.dispatchEventToListeners(WebInspector.SourcesTextEditor.Events.Gutt erClick, { lineNumber: lineNumber, event: event });
263 },
264
265 _contextMenu: function(event)
266 {
267 var contextMenu = new WebInspector.ContextMenu(event);
268 event.consume(true); // Consume event now to prevent document from handl ing the async menu
269 var target = event.target.enclosingNodeOrSelfWithClass("CodeMirror-gutte r-elt");
270 var promise;
271 if (target) {
272 promise = this._delegate.populateLineGutterContextMenu(contextMenu, parseInt(target.textContent, 10) - 1);
273 } else {
274 var textSelection = this.selection();
275 promise = this._delegate.populateTextAreaContextMenu(contextMenu, te xtSelection.startLine, textSelection.startColumn);
276 }
277 promise.then(showAsync.bind(this));
278
279 /**
280 * @this {WebInspector.SourcesTextEditor}
281 */
282 function showAsync()
283 {
284 contextMenu.appendApplicableItems(this);
285 contextMenu.show();
286 }
287 },
288
289 /**
290 * @override
291 * @param {!WebInspector.TextRange} range
292 * @param {string} text
293 * @param {string=} origin
294 * @return {!WebInspector.TextRange}
295 */
296 editRange: function(range, text, origin)
297 {
298 var newRange = WebInspector.CodeMirrorTextEditor.prototype.editRange.cal l(this, range, text, origin);
299 this._delegate.onTextChanged(range, newRange);
300
301 if (WebInspector.moduleSetting("textEditorAutoDetectIndent").get())
302 this._onUpdateEditorIndentation();
303
304 return newRange;
305 },
306
307 _onUpdateEditorIndentation: function()
308 {
309 this._setEditorIndentation(WebInspector.CodeMirrorUtils.pullLines(this.c odeMirror(), WebInspector.SourcesTextEditor.LinesToScanForIndentationGuessing));
310 },
311
312 /**
313 * @param {!Array.<string>} lines
314 */
315 _setEditorIndentation: function(lines)
316 {
317 var extraKeys = {};
318 var indent = WebInspector.moduleSetting("textEditorIndent").get();
319 if (WebInspector.moduleSetting("textEditorAutoDetectIndent").get())
320 indent = WebInspector.SourcesTextEditor._guessIndentationLevel(lines );
321
322 if (indent === WebInspector.TextUtils.Indent.TabCharacter) {
323 this.codeMirror().setOption("indentWithTabs", true);
324 this.codeMirror().setOption("indentUnit", 4);
325 } else {
326 this.codeMirror().setOption("indentWithTabs", false);
327 this.codeMirror().setOption("indentUnit", indent.length);
328 extraKeys.Tab = function(codeMirror)
329 {
330 if (codeMirror.somethingSelected())
331 return CodeMirror.Pass;
332 var pos = codeMirror.getCursor("head");
333 codeMirror.replaceRange(indent.substring(pos.ch % indent.length) , codeMirror.getCursor());
334 }
335 }
336
337 this.codeMirror().setOption("extraKeys", extraKeys);
338 this._indentationLevel = indent;
339 },
340
341 /**
342 * @return {string}
343 */
344 indent: function()
345 {
346 return this._indentationLevel;
347 },
348
349 _onAutoAppendedSpaces: function()
350 {
351 this._autoAppendedSpaces = this._autoAppendedSpaces || [];
352
353 for (var i = 0; i < this._autoAppendedSpaces.length; ++i) {
354 var position = this._autoAppendedSpaces[i].resolve();
355 if (!position)
356 continue;
357 var line = this.line(position.lineNumber);
358 if (line.length === position.columnNumber && WebInspector.TextUtils. lineIndent(line).length === line.length)
359 this.codeMirror().replaceRange("", new CodeMirror.Pos(position.l ineNumber, 0), new CodeMirror.Pos(position.lineNumber, position.columnNumber));
360 }
361
362 this._autoAppendedSpaces = [];
363 var selections = this.selections();
364 for (var i = 0; i < selections.length; ++i) {
365 var selection = selections[i];
366 this._autoAppendedSpaces.push(this.textEditorPositionHandle(selectio n.startLine, selection.startColumn));
367 }
368 },
369
370 /**
371 * @param {!CodeMirror} codeMirror
372 * @param {!Array.<!CodeMirror.ChangeObject>} changes
373 */
374 _changesForDelegate: function(codeMirror, changes)
375 {
376 if (!changes.length || this._muteTextChangedEvent)
377 return;
378 var edits = [];
379 var currentEdit;
380
381 for (var changeIndex = 0; changeIndex < changes.length; ++changeIndex) {
382 var changeObject = changes[changeIndex];
383 var edit = WebInspector.CodeMirrorUtils.changeObjectToEditOperation( changeObject);
384 if (currentEdit && edit.oldRange.equal(currentEdit.newRange)) {
385 currentEdit.newRange = edit.newRange;
386 } else {
387 currentEdit = edit;
388 edits.push(currentEdit);
389 }
390 }
391
392 for (var i = 0; i < edits.length; ++i) {
393 var edit = edits[i];
394 this._delegate.onTextChanged(edit.oldRange, edit.newRange);
395 }
396 },
397
398 _cursorActivity: function()
399 {
400 if (!this._isSearchActive())
401 this.codeMirror().operation(this._tokenHighlighter.highlightSelected Tokens.bind(this._tokenHighlighter));
402
403 var start = this.codeMirror().getCursor("anchor");
404 var end = this.codeMirror().getCursor("head");
405 this._delegate.selectionChanged(WebInspector.CodeMirrorUtils.toRange(sta rt, end));
406 },
407
408 /**
409 * @param {?WebInspector.TextRange} from
410 * @param {?WebInspector.TextRange} to
411 */
412 _reportJump: function(from, to)
413 {
414 if (from && to && from.equal(to))
415 return;
416 this._delegate.onJumpToPosition(from, to);
417 },
418
419 _scroll: function()
420 {
421 var topmostLineNumber = this.codeMirror().lineAtHeight(this.codeMirror() .getScrollInfo().top, "local");
422 this._delegate.scrollChanged(topmostLineNumber);
423 },
424
425 _focus: function()
426 {
427 this._delegate.editorFocused();
428 },
429
430 /**
431 * @param {!CodeMirror} codeMirror
432 * @param {{ranges: !Array.<{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos} >}} selection
433 */
434 _beforeSelectionChangeForDelegate: function(codeMirror, selection)
435 {
436 if (!this._isHandlingMouseDownEvent)
437 return;
438 if (!selection.ranges.length)
439 return;
440
441 var primarySelection = selection.ranges[0];
442 this._reportJump(this.selection(), WebInspector.CodeMirrorUtils.toRange( primarySelection.anchor, primarySelection.head));
443 },
444
445 /**
446 * @override
447 */
448 dispose: function()
449 {
450 WebInspector.CodeMirrorTextEditor.prototype.dispose.call(this);
451 WebInspector.moduleSetting("textEditorIndent").removeChangeListener(this ._onUpdateEditorIndentation, this);
452 WebInspector.moduleSetting("textEditorAutoDetectIndent").removeChangeLis tener(this._onUpdateEditorIndentation, this);
453 WebInspector.moduleSetting("showWhitespacesInEditor").removeChangeListen er(this._updateWhitespace, this);
454 },
455
456 /**
457 * @override
458 * @param {string} text
459 */
460 setText: function(text)
461 {
462 this._muteTextChangedEvent = true;
463 this._setEditorIndentation(text.split("\n").slice(0, WebInspector.Source sTextEditor.LinesToScanForIndentationGuessing));
464 WebInspector.CodeMirrorTextEditor.prototype.setText.call(this, text);
465 delete this._muteTextChangedEvent;
466 },
467
468 /**
469 * @override
470 * @param {string} mimeType
471 */
472 setMimeType: function(mimeType)
473 {
474 this._mimeType = mimeType;
475 WebInspector.CodeMirrorTextEditor.prototype.setMimeType.call(this, this. _applyWhitespaceMimetype(mimeType));
476 },
477
478 _updateWhitespace: function()
479 {
480 if (this._mimeType)
481 this.setMimeType(this._mimeType);
482 },
483
484 /**
485 * @param {string} mimeType
486 * @return {string}
487 */
488 _applyWhitespaceMimetype: function(mimeType)
489 {
490 this._setupWhitespaceHighlight();
491 var whitespaceMode = WebInspector.moduleSetting("showWhitespacesInEditor ").get();
492 this.element.classList.toggle("show-whitespaces", whitespaceMode === "al l");
493
494 if (whitespaceMode === "all")
495 return this._allWhitespaceOverlayMode(mimeType);
496 else if (whitespaceMode === "trailing")
497 return this._trailingWhitespaceOverlayMode(mimeType);
498
499 return mimeType;
500 },
501
502 /**
503 * @param {string} mimeType
504 * @return {string}
505 */
506 _allWhitespaceOverlayMode: function(mimeType)
507 {
508 var modeName = CodeMirror.mimeModes[mimeType] ? (CodeMirror.mimeModes[mi meType].name || CodeMirror.mimeModes[mimeType]) : CodeMirror.mimeModes["text/pla in"];
509 modeName += "+all-whitespaces";
510 if (CodeMirror.modes[modeName])
511 return modeName;
512
513 function modeConstructor(config, parserConfig)
514 {
515 function nextToken(stream)
516 {
517 if (stream.peek() === " ") {
518 var spaces = 0;
519 while (spaces < WebInspector.SourcesTextEditor.MaximumNumber OfWhitespacesPerSingleSpan && stream.peek() === " ") {
520 ++spaces;
521 stream.next();
522 }
523 return "whitespace whitespace-" + spaces;
524 }
525 while (!stream.eol() && stream.peek() !== " ")
526 stream.next();
527 return null;
528 }
529 var whitespaceMode = {
530 token: nextToken
531 };
532 return CodeMirror.overlayMode(CodeMirror.getMode(config, mimeType), whitespaceMode, false);
533 }
534 CodeMirror.defineMode(modeName, modeConstructor);
535 return modeName;
536 },
537
538 /**
539 * @param {string} mimeType
540 * @return {string}
541 */
542 _trailingWhitespaceOverlayMode: function(mimeType)
543 {
544 var modeName = CodeMirror.mimeModes[mimeType] ? (CodeMirror.mimeModes[mi meType].name || CodeMirror.mimeModes[mimeType]) : CodeMirror.mimeModes["text/pla in"];
545 modeName += "+trailing-whitespaces";
546 if (CodeMirror.modes[modeName])
547 return modeName;
548
549 function modeConstructor(config, parserConfig)
550 {
551 function nextToken(stream)
552 {
553 var pos = stream.pos;
554 if (stream.match(/^\s+$/, true))
555 return true ? "trailing-whitespace" : null;
556 do {
557 stream.next();
558 } while (!stream.eol() && stream.peek() !== " ");
559 return null;
560 }
561 var whitespaceMode = {
562 token: nextToken
563 };
564 return CodeMirror.overlayMode(CodeMirror.getMode(config, mimeType), whitespaceMode, false);
565 }
566 CodeMirror.defineMode(modeName, modeConstructor);
567 return modeName;
568 },
569
570 _setupWhitespaceHighlight: function()
571 {
572 var doc = this.element.ownerDocument;
573 if (doc._codeMirrorWhitespaceStyleInjected || !WebInspector.moduleSettin g("showWhitespacesInEditor").get())
574 return;
575 doc._codeMirrorWhitespaceStyleInjected = true;
576 const classBase = ".show-whitespaces .CodeMirror .cm-whitespace-";
577 const spaceChar = "·";
578 var spaceChars = "";
579 var rules = "";
580 for (var i = 1; i <= WebInspector.SourcesTextEditor.MaximumNumberOfWhite spacesPerSingleSpan; ++i) {
581 spaceChars += spaceChar;
582 var rule = classBase + i + "::before { content: '" + spaceChars + "' ;}\n";
583 rules += rule;
584 }
585 var style = doc.createElement("style");
586 style.textContent = rules;
587 doc.head.appendChild(style);
588 },
589
590 __proto__: WebInspector.CodeMirrorTextEditor.prototype
591 }
592
593 /** @typedef {{lineNumber: number, event: !Event}} */
594 WebInspector.SourcesTextEditor.GutterClickEventData;
595
596 /** @enum {string} */
597 WebInspector.SourcesTextEditor.Events = {
598 GutterClick: "GutterClick"
599 }
600 /**
601 * @interface
602 */
603 WebInspector.SourcesTextEditorDelegate = function() { }
604 WebInspector.SourcesTextEditorDelegate.prototype = {
605
606 /**
607 * @param {!WebInspector.TextRange} oldRange
608 * @param {!WebInspector.TextRange} newRange
609 */
610 onTextChanged: function(oldRange, newRange) { },
611
612 /**
613 * @param {!WebInspector.TextRange} textRange
614 */
615 selectionChanged: function(textRange) { },
616
617 /**
618 * @param {number} lineNumber
619 */
620 scrollChanged: function(lineNumber) { },
621
622 editorFocused: function() { },
623
624 /**
625 * @param {!WebInspector.ContextMenu} contextMenu
626 * @param {number} lineNumber
627 * @return {!Promise}
628 */
629 populateLineGutterContextMenu: function(contextMenu, lineNumber) { },
630
631 /**
632 * @param {!WebInspector.ContextMenu} contextMenu
633 * @param {number} lineNumber
634 * @param {number} columnNumber
635 * @return {!Promise}
636 */
637 populateTextAreaContextMenu: function(contextMenu, lineNumber, columnNumber) { },
638
639 /**
640 * @param {?WebInspector.TextRange} from
641 * @param {?WebInspector.TextRange} to
642 */
643 onJumpToPosition: function(from, to) { }
644 }
645
646 /**
647 * @param {!CodeMirror} codeMirror
648 */
649 CodeMirror.commands.smartNewlineAndIndent = function(codeMirror)
650 {
651 codeMirror.operation(innerSmartNewlineAndIndent.bind(null, codeMirror));
652 function innerSmartNewlineAndIndent(codeMirror)
653 {
654 var selections = codeMirror.listSelections();
655 var replacements = [];
656 for (var i = 0; i < selections.length; ++i) {
657 var selection = selections[i];
658 var cur = CodeMirror.cmpPos(selection.head, selection.anchor) < 0 ? selection.head : selection.anchor;
659 var line = codeMirror.getLine(cur.line);
660 var indent = WebInspector.TextUtils.lineIndent(line);
661 replacements.push("\n" + indent.substring(0, Math.min(cur.ch, indent .length)));
662 }
663 codeMirror.replaceSelections(replacements);
664 codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces();
665 }
666 }
667
668 /**
669 * @return {!Object|undefined}
670 */
671 CodeMirror.commands.sourcesDismiss = function(codemirror)
672 {
673 if (codemirror.listSelections().length === 1 && codemirror._codeMirrorTextEd itor._isSearchActive())
674 return CodeMirror.Pass;
675 return CodeMirror.commands.dismiss(codemirror);
676 }
677
678 /**
679 * @constructor
680 * @param {!CodeMirror} codeMirror
681 */
682 WebInspector.SourcesTextEditor.BlockIndentController = function(codeMirror)
683 {
684 codeMirror.addKeyMap(this);
685 }
686
687 WebInspector.SourcesTextEditor.BlockIndentController.prototype = {
688 name: "blockIndentKeymap",
689
690 /**
691 * @return {*}
692 */
693 Enter: function(codeMirror)
694 {
695 var selections = codeMirror.listSelections();
696 var replacements = [];
697 var allSelectionsAreCollapsedBlocks = false;
698 for (var i = 0; i < selections.length; ++i) {
699 var selection = selections[i];
700 var start = CodeMirror.cmpPos(selection.head, selection.anchor) < 0 ? selection.head : selection.anchor;
701 var line = codeMirror.getLine(start.line);
702 var indent = WebInspector.TextUtils.lineIndent(line);
703 var indentToInsert = "\n" + indent + codeMirror._codeMirrorTextEdito r.indent();
704 var isCollapsedBlock = false;
705 if (selection.head.ch === 0)
706 return CodeMirror.Pass;
707 if (line.substr(selection.head.ch - 1, 2) === "{}") {
708 indentToInsert += "\n" + indent;
709 isCollapsedBlock = true;
710 } else if (line.substr(selection.head.ch - 1, 1) !== "{") {
711 return CodeMirror.Pass;
712 }
713 if (i > 0 && allSelectionsAreCollapsedBlocks !== isCollapsedBlock)
714 return CodeMirror.Pass;
715 replacements.push(indentToInsert);
716 allSelectionsAreCollapsedBlocks = isCollapsedBlock;
717 }
718 codeMirror.replaceSelections(replacements);
719 if (!allSelectionsAreCollapsedBlocks) {
720 codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces();
721 return;
722 }
723 selections = codeMirror.listSelections();
724 var updatedSelections = [];
725 for (var i = 0; i < selections.length; ++i) {
726 var selection = selections[i];
727 var line = codeMirror.getLine(selection.head.line - 1);
728 var position = new CodeMirror.Pos(selection.head.line - 1, line.leng th);
729 updatedSelections.push({
730 head: position,
731 anchor: position
732 });
733 }
734 codeMirror.setSelections(updatedSelections);
735 codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces();
736 },
737
738 /**
739 * @return {*}
740 */
741 "'}'": function(codeMirror)
742 {
743 if (codeMirror.somethingSelected())
744 return CodeMirror.Pass;
745 var selections = codeMirror.listSelections();
746 var replacements = [];
747 for (var i = 0; i < selections.length; ++i) {
748 var selection = selections[i];
749 var line = codeMirror.getLine(selection.head.line);
750 if (line !== WebInspector.TextUtils.lineIndent(line))
751 return CodeMirror.Pass;
752 replacements.push("}");
753 }
754 codeMirror.replaceSelections(replacements);
755 selections = codeMirror.listSelections();
756 replacements = [];
757 var updatedSelections = [];
758 for (var i = 0; i < selections.length; ++i) {
759 var selection = selections[i];
760 var matchingBracket = codeMirror.findMatchingBracket(selection.head) ;
761 if (!matchingBracket || !matchingBracket.match)
762 return;
763 updatedSelections.push({
764 head: selection.head,
765 anchor: new CodeMirror.Pos(selection.head.line, 0)
766 });
767 var line = codeMirror.getLine(matchingBracket.to.line);
768 var indent = WebInspector.TextUtils.lineIndent(line);
769 replacements.push(indent + "}");
770 }
771 codeMirror.setSelections(updatedSelections);
772 codeMirror.replaceSelections(replacements);
773 }
774 }
775
776 /**
777 * @param {!Array.<string>} lines
778 * @return {string}
779 */
780 WebInspector.SourcesTextEditor._guessIndentationLevel = function(lines)
781 {
782 var tabRegex = /^\t+/;
783 var tabLines = 0;
784 var indents = {};
785 for (var lineNumber = 0; lineNumber < lines.length; ++lineNumber) {
786 var text = lines[lineNumber];
787 if (text.length === 0 || !WebInspector.TextUtils.isSpaceChar(text[0]))
788 continue;
789 if (tabRegex.test(text)) {
790 ++tabLines;
791 continue;
792 }
793 var i = 0;
794 while (i < text.length && WebInspector.TextUtils.isSpaceChar(text[i]))
795 ++i;
796 if (i % 2 !== 0)
797 continue;
798 indents[i] = 1 + (indents[i] || 0);
799 }
800 var linesCountPerIndentThreshold = 3 * lines.length / 100;
801 if (tabLines && tabLines > linesCountPerIndentThreshold)
802 return "\t";
803 var minimumIndent = Infinity;
804 for (var i in indents) {
805 if (indents[i] < linesCountPerIndentThreshold)
806 continue;
807 var indent = parseInt(i, 10);
808 if (minimumIndent > indent)
809 minimumIndent = indent;
810 }
811 if (minimumIndent === Infinity)
812 return WebInspector.moduleSetting("textEditorIndent").get();
813 return " ".repeat(minimumIndent);
814 }
815
816 /**
817 * @constructor
818 * @param {!WebInspector.SourcesTextEditor} textEditor
819 * @param {!CodeMirror} codeMirror
820 */
821 WebInspector.SourcesTextEditor.TokenHighlighter = function(textEditor, codeMirro r)
822 {
823 this._textEditor = textEditor;
824 this._codeMirror = codeMirror;
825 }
826
827 WebInspector.SourcesTextEditor.TokenHighlighter.prototype = {
828 /**
829 * @param {!RegExp} regex
830 * @param {?WebInspector.TextRange} range
831 */
832 highlightSearchResults: function(regex, range)
833 {
834 var oldRegex = this._highlightRegex;
835 this._highlightRegex = regex;
836 this._highlightRange = range;
837 if (this._searchResultMarker) {
838 this._searchResultMarker.clear();
839 delete this._searchResultMarker;
840 }
841 if (this._highlightDescriptor && this._highlightDescriptor.selectionStar t)
842 this._codeMirror.removeLineClass(this._highlightDescriptor.selection Start.line, "wrap", "cm-line-with-selection");
843 var selectionStart = this._highlightRange ? new CodeMirror.Pos(this._hig hlightRange.startLine, this._highlightRange.startColumn) : null;
844 if (selectionStart)
845 this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line- with-selection");
846 if (this._highlightRegex === oldRegex) {
847 // Do not re-add overlay mode if regex did not change for better per formance.
848 if (this._highlightDescriptor)
849 this._highlightDescriptor.selectionStart = selectionStart;
850 } else {
851 this._removeHighlight();
852 this._setHighlighter(this._searchHighlighter.bind(this, this._highli ghtRegex), selectionStart);
853 }
854 if (this._highlightRange) {
855 var pos = WebInspector.CodeMirrorUtils.toPos(this._highlightRange);
856 this._searchResultMarker = this._codeMirror.markText(pos.start, pos. end, {className: "cm-column-with-selection"});
857 }
858 },
859
860 /**
861 * @return {!RegExp|undefined}
862 */
863 highlightedRegex: function()
864 {
865 return this._highlightRegex;
866 },
867
868 highlightSelectedTokens: function()
869 {
870 delete this._highlightRegex;
871 delete this._highlightRange;
872 if (this._highlightDescriptor && this._highlightDescriptor.selectionStar t)
873 this._codeMirror.removeLineClass(this._highlightDescriptor.selection Start.line, "wrap", "cm-line-with-selection");
874 this._removeHighlight();
875 var selectionStart = this._codeMirror.getCursor("start");
876 var selectionEnd = this._codeMirror.getCursor("end");
877 if (selectionStart.line !== selectionEnd.line)
878 return;
879 if (selectionStart.ch === selectionEnd.ch)
880 return;
881 var selections = this._codeMirror.getSelections();
882 if (selections.length > 1)
883 return;
884 var selectedText = selections[0];
885 if (this._isWord(selectedText, selectionStart.line, selectionStart.ch, s electionEnd.ch)) {
886 if (selectionStart)
887 this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-l ine-with-selection");
888 this._setHighlighter(this._tokenHighlighter.bind(this, selectedText, selectionStart), selectionStart);
889 }
890 },
891
892 /**
893 * @param {string} selectedText
894 * @param {number} lineNumber
895 * @param {number} startColumn
896 * @param {number} endColumn
897 */
898 _isWord: function(selectedText, lineNumber, startColumn, endColumn)
899 {
900 var line = this._codeMirror.getLine(lineNumber);
901 var leftBound = startColumn === 0 || !WebInspector.TextUtils.isWordChar( line.charAt(startColumn - 1));
902 var rightBound = endColumn === line.length || !WebInspector.TextUtils.is WordChar(line.charAt(endColumn));
903 return leftBound && rightBound && WebInspector.TextUtils.isWord(selected Text);
904 },
905
906 _removeHighlight: function()
907 {
908 if (this._highlightDescriptor) {
909 this._codeMirror.removeOverlay(this._highlightDescriptor.overlay);
910 delete this._highlightDescriptor;
911 }
912 },
913
914 /**
915 * @param {!RegExp} regex
916 * @param {!CodeMirror.StringStream} stream
917 */
918 _searchHighlighter: function(regex, stream)
919 {
920 if (stream.column() === 0)
921 delete this._searchMatchLength;
922 if (this._searchMatchLength) {
923 if (this._searchMatchLength > 2) {
924 for (var i = 0; i < this._searchMatchLength - 2; ++i)
925 stream.next();
926 this._searchMatchLength = 1;
927 return "search-highlight";
928 } else {
929 stream.next();
930 delete this._searchMatchLength;
931 return "search-highlight search-highlight-end";
932 }
933 }
934 var match = stream.match(regex, false);
935 if (match) {
936 stream.next();
937 var matchLength = match[0].length;
938 if (matchLength === 1)
939 return "search-highlight search-highlight-full";
940 this._searchMatchLength = matchLength;
941 return "search-highlight search-highlight-start";
942 }
943 while (!stream.match(regex, false) && stream.next()) {}
944 },
945
946 /**
947 * @param {string} token
948 * @param {!CodeMirror.Pos} selectionStart
949 * @param {!CodeMirror.StringStream} stream
950 */
951 _tokenHighlighter: function(token, selectionStart, stream)
952 {
953 var tokenFirstChar = token.charAt(0);
954 if (stream.match(token) && (stream.eol() || !WebInspector.TextUtils.isWo rdChar(stream.peek())))
955 return stream.column() === selectionStart.ch ? "token-highlight colu mn-with-selection" : "token-highlight";
956 var eatenChar;
957 do {
958 eatenChar = stream.next();
959 } while (eatenChar && (WebInspector.TextUtils.isWordChar(eatenChar) || s tream.peek() !== tokenFirstChar));
960 },
961
962 /**
963 * @param {function(!CodeMirror.StringStream)} highlighter
964 * @param {?CodeMirror.Pos} selectionStart
965 */
966 _setHighlighter: function(highlighter, selectionStart)
967 {
968 var overlayMode = {
969 token: highlighter
970 };
971 this._codeMirror.addOverlay(overlayMode);
972 this._highlightDescriptor = {
973 overlay: overlayMode,
974 selectionStart: selectionStart
975 };
976 }
977 }
978
979 WebInspector.SourcesTextEditor.LinesToScanForIndentationGuessing = 1000;
980 WebInspector.SourcesTextEditor.MaximumNumberOfWhitespacesPerSingleSpan = 16;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698