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

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

Powered by Google App Engine
This is Rietveld 408576698