1/* 2 * Copyright (C) 2012 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 * @extends {WebInspector.VBox} 34 * @implements {WebInspector.TextEditor} 35 * @param {?string} url 36 * @param {!WebInspector.TextEditorDelegate} delegate 37 */ 38WebInspector.CodeMirrorTextEditor = function(url, delegate) 39{ 40 WebInspector.VBox.call(this); 41 this._delegate = delegate; 42 this._url = url; 43 44 this.registerRequiredCSS("cm/codemirror.css"); 45 this.registerRequiredCSS("cm/cmdevtools.css"); 46 47 this._codeMirror = new window.CodeMirror(this.element, { 48 lineNumbers: true, 49 gutters: ["CodeMirror-linenumbers"], 50 matchBrackets: true, 51 smartIndent: false, 52 styleSelectedText: true, 53 electricChars: false, 54 }); 55 this._codeMirror._codeMirrorTextEditor = this; 56 57 CodeMirror.keyMap["devtools-common"] = { 58 "Left": "goCharLeft", 59 "Right": "goCharRight", 60 "Up": "goLineUp", 61 "Down": "goLineDown", 62 "End": "goLineEnd", 63 "Home": "goLineStartSmart", 64 "PageUp": "goPageUp", 65 "PageDown": "goPageDown", 66 "Delete": "delCharAfter", 67 "Backspace": "delCharBefore", 68 "Tab": "defaultTab", 69 "Shift-Tab": "indentLess", 70 "Enter": "smartNewlineAndIndent", 71 "Ctrl-Space": "autocomplete", 72 "Esc": "dismissMultipleSelections" 73 }; 74 75 CodeMirror.keyMap["devtools-pc"] = { 76 "Ctrl-A": "selectAll", 77 "Ctrl-Z": "undoAndReveal", 78 "Shift-Ctrl-Z": "redoAndReveal", 79 "Ctrl-Y": "redo", 80 "Ctrl-Home": "goDocStart", 81 "Ctrl-Up": "goDocStart", 82 "Ctrl-End": "goDocEnd", 83 "Ctrl-Down": "goDocEnd", 84 "Ctrl-Left": "goGroupLeft", 85 "Ctrl-Right": "goGroupRight", 86 "Alt-Left": "goLineStart", 87 "Alt-Right": "goLineEnd", 88 "Ctrl-Backspace": "delGroupBefore", 89 "Ctrl-Delete": "delGroupAfter", 90 "Ctrl-/": "toggleComment", 91 "Ctrl-D": "selectNextOccurrence", 92 "Ctrl-U": "undoLastSelection", 93 fallthrough: "devtools-common" 94 }; 95 96 CodeMirror.keyMap["devtools-mac"] = { 97 "Cmd-A" : "selectAll", 98 "Cmd-Z" : "undoAndReveal", 99 "Shift-Cmd-Z": "redoAndReveal", 100 "Cmd-Up": "goDocStart", 101 "Cmd-Down": "goDocEnd", 102 "Alt-Left": "goGroupLeft", 103 "Alt-Right": "goGroupRight", 104 "Cmd-Left": "goLineStartSmart", 105 "Cmd-Right": "goLineEnd", 106 "Alt-Backspace": "delGroupBefore", 107 "Alt-Delete": "delGroupAfter", 108 "Cmd-/": "toggleComment", 109 "Cmd-D": "selectNextOccurrence", 110 "Cmd-U": "undoLastSelection", 111 fallthrough: "devtools-common" 112 }; 113 114 WebInspector.settings.textEditorIndent.addChangeListener(this._updateEditorIndentation, this); 115 this._updateEditorIndentation(); 116 WebInspector.settings.showWhitespacesInEditor.addChangeListener(this._updateCodeMirrorMode, this); 117 WebInspector.settings.textEditorBracketMatching.addChangeListener(this._enableBracketMatchingIfNeeded, this); 118 this._enableBracketMatchingIfNeeded(); 119 120 this._codeMirror.setOption("keyMap", WebInspector.isMac() ? "devtools-mac" : "devtools-pc"); 121 this._codeMirror.setOption("flattenSpans", false); 122 123 this._codeMirror.setOption("maxHighlightLength", WebInspector.CodeMirrorTextEditor.maxHighlightLength); 124 this._codeMirror.setOption("mode", null); 125 this._codeMirror.setOption("crudeMeasuringFrom", 1000); 126 127 this._shouldClearHistory = true; 128 this._lineSeparator = "\n"; 129 130 this._autocompleteController = WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy; 131 this._tokenHighlighter = new WebInspector.CodeMirrorTextEditor.TokenHighlighter(this, this._codeMirror); 132 this._blockIndentController = new WebInspector.CodeMirrorTextEditor.BlockIndentController(this._codeMirror); 133 this._fixWordMovement = new WebInspector.CodeMirrorTextEditor.FixWordMovement(this._codeMirror); 134 this._selectNextOccurrenceController = new WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController(this, this._codeMirror); 135 136 this._codeMirror.on("changes", this._changes.bind(this)); 137 this._codeMirror.on("gutterClick", this._gutterClick.bind(this)); 138 this._codeMirror.on("cursorActivity", this._cursorActivity.bind(this)); 139 this._codeMirror.on("beforeSelectionChange", this._beforeSelectionChange.bind(this)); 140 this._codeMirror.on("scroll", this._scroll.bind(this)); 141 this._codeMirror.on("focus", this._focus.bind(this)); 142 this.element.addEventListener("contextmenu", this._contextMenu.bind(this), false); 143 /** 144 * @this {WebInspector.CodeMirrorTextEditor} 145 */ 146 function updateAnticipateJumpFlag(value) 147 { 148 this._isHandlingMouseDownEvent = value; 149 } 150 this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(this, true), true); 151 this.element.addEventListener("mousedown", updateAnticipateJumpFlag.bind(this, false), false); 152 153 this.element.style.overflow = "hidden"; 154 this.element.firstChild.classList.add("source-code"); 155 this.element.firstChild.classList.add("fill"); 156 this._elementToWidget = new Map(); 157 this._nestedUpdatesCounter = 0; 158 159 this.element.addEventListener("focus", this._handleElementFocus.bind(this), false); 160 this.element.addEventListener("keydown", this._handleKeyDown.bind(this), true); 161 this.element.addEventListener("keydown", this._handlePostKeyDown.bind(this), false); 162 this.element.tabIndex = 0; 163 164 this._setupWhitespaceHighlight(); 165} 166 167/** @typedef {{canceled: boolean, from: !CodeMirror.Pos, to: !CodeMirror.Pos, text: string, origin: string, cancel: function()}} */ 168WebInspector.CodeMirrorTextEditor.BeforeChangeObject; 169 170/** @typedef {{from: !CodeMirror.Pos, to: !CodeMirror.Pos, origin: string, text: !Array.<string>, removed: !Array.<string>}} */ 171WebInspector.CodeMirrorTextEditor.ChangeObject; 172 173WebInspector.CodeMirrorTextEditor.maxHighlightLength = 1000; 174 175/** 176 * @param {!CodeMirror} codeMirror 177 */ 178WebInspector.CodeMirrorTextEditor.autocompleteCommand = function(codeMirror) 179{ 180 codeMirror._codeMirrorTextEditor._autocompleteController.autocomplete(); 181} 182CodeMirror.commands.autocomplete = WebInspector.CodeMirrorTextEditor.autocompleteCommand; 183 184/** 185 * @param {!CodeMirror} codeMirror 186 */ 187WebInspector.CodeMirrorTextEditor.undoLastSelectionCommand = function(codeMirror) 188{ 189 codeMirror._codeMirrorTextEditor._selectNextOccurrenceController.undoLastSelection(); 190} 191CodeMirror.commands.undoLastSelection = WebInspector.CodeMirrorTextEditor.undoLastSelectionCommand; 192 193/** 194 * @param {!CodeMirror} codeMirror 195 */ 196WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand = function(codeMirror) 197{ 198 codeMirror._codeMirrorTextEditor._selectNextOccurrenceController.selectNextOccurrence(); 199} 200CodeMirror.commands.selectNextOccurrence = WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand; 201 202/** 203 * @param {!CodeMirror} codeMirror 204 */ 205CodeMirror.commands.smartNewlineAndIndent = function(codeMirror) 206{ 207 codeMirror.operation(innerSmartNewlineAndIndent.bind(null, codeMirror)); 208 209 function countIndent(line) 210 { 211 for (var i = 0; i < line.length; ++i) { 212 if (!WebInspector.TextUtils.isSpaceChar(line[i])) 213 return i; 214 } 215 return line.length; 216 } 217 218 function innerSmartNewlineAndIndent(codeMirror) 219 { 220 var cur = codeMirror.getCursor("start"); 221 var line = codeMirror.getLine(cur.line); 222 var indent = cur.line > 0 ? countIndent(line) : 0; 223 if (cur.ch <= indent) { 224 codeMirror.replaceSelection("\n" + line.substring(0, cur.ch), "end", "+input"); 225 codeMirror.setSelection(new CodeMirror.Pos(cur.line + 1, cur.ch)); 226 } else 227 codeMirror.execCommand("newlineAndIndent"); 228 } 229} 230 231CodeMirror.commands.undoAndReveal = function(codemirror) 232{ 233 var scrollInfo = codemirror.getScrollInfo(); 234 codemirror.execCommand("undo"); 235 var cursor = codemirror.getCursor("start"); 236 codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo); 237 codemirror._codeMirrorTextEditor._autocompleteController.finishAutocomplete(); 238} 239 240CodeMirror.commands.redoAndReveal = function(codemirror) 241{ 242 var scrollInfo = codemirror.getScrollInfo(); 243 codemirror.execCommand("redo"); 244 var cursor = codemirror.getCursor("start"); 245 codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo); 246 codemirror._codeMirrorTextEditor._autocompleteController.finishAutocomplete(); 247} 248 249/** 250 * @return {!Object|undefined} 251 */ 252CodeMirror.commands.dismissMultipleSelections = function(codemirror) 253{ 254 var selections = codemirror.listSelections(); 255 var selection = selections[0]; 256 if (selections.length === 1) { 257 if (codemirror._codeMirrorTextEditor._isSearchActive()) 258 return CodeMirror.Pass; 259 if (WebInspector.CodeMirrorUtils.toRange(selection.anchor, selection.head).isEmpty()) 260 return CodeMirror.Pass; 261 codemirror.setSelection(selection.anchor, selection.anchor, {scroll: false}); 262 codemirror._codeMirrorTextEditor._revealLine(selection.anchor.line); 263 return; 264 } 265 266 codemirror.setSelection(selection.anchor, selection.head, {scroll: false}); 267 codemirror._codeMirrorTextEditor._revealLine(selection.anchor.line); 268} 269 270WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold = 2000; 271WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan = 16; 272WebInspector.CodeMirrorTextEditor.MaxEditableTextSize = 1024 * 1024 * 10; 273 274WebInspector.CodeMirrorTextEditor.prototype = { 275 dispose: function() 276 { 277 WebInspector.settings.textEditorIndent.removeChangeListener(this._updateEditorIndentation, this); 278 WebInspector.settings.showWhitespacesInEditor.removeChangeListener(this._updateCodeMirrorMode, this); 279 WebInspector.settings.textEditorBracketMatching.removeChangeListener(this._enableBracketMatchingIfNeeded, this); 280 }, 281 282 _enableBracketMatchingIfNeeded: function() 283 { 284 this._codeMirror.setOption("autoCloseBrackets", WebInspector.settings.textEditorBracketMatching.get() ? { explode: false } : false); 285 }, 286 287 wasShown: function() 288 { 289 if (this._wasOnceShown) 290 return; 291 this._wasOnceShown = true; 292 this._codeMirror.refresh(); 293 }, 294 295 _guessIndentationLevel: function() 296 { 297 var tabRegex = /^\t+/; 298 var tabLines = 0; 299 var indents = {}; 300 function processLine(lineHandle) 301 { 302 var text = lineHandle.text; 303 if (text.length === 0 || !WebInspector.TextUtils.isSpaceChar(text[0])) 304 return; 305 if (tabRegex.test(text)) { 306 ++tabLines; 307 return; 308 } 309 var i = 0; 310 while (i < text.length && WebInspector.TextUtils.isSpaceChar(text[i])) 311 ++i; 312 if (i % 2 !== 0) 313 return; 314 indents[i] = 1 + (indents[i] || 0); 315 } 316 this._codeMirror.eachLine(0, 1000, processLine); 317 318 var onePercentFilterThreshold = this.linesCount / 100; 319 if (tabLines && tabLines > onePercentFilterThreshold) 320 return "\t"; 321 var minimumIndent = Infinity; 322 for (var i in indents) { 323 if (indents[i] < onePercentFilterThreshold) 324 continue; 325 var indent = parseInt(i, 10); 326 if (minimumIndent > indent) 327 minimumIndent = indent; 328 } 329 if (minimumIndent === Infinity) 330 return WebInspector.TextUtils.Indent.FourSpaces; 331 return new Array(minimumIndent + 1).join(" "); 332 }, 333 334 _updateEditorIndentation: function() 335 { 336 var extraKeys = {}; 337 var indent = WebInspector.settings.textEditorIndent.get(); 338 if (WebInspector.settings.textEditorAutoDetectIndent.get()) 339 indent = this._guessIndentationLevel(); 340 if (indent === WebInspector.TextUtils.Indent.TabCharacter) { 341 this._codeMirror.setOption("indentWithTabs", true); 342 this._codeMirror.setOption("indentUnit", 4); 343 } else { 344 this._codeMirror.setOption("indentWithTabs", false); 345 this._codeMirror.setOption("indentUnit", indent.length); 346 extraKeys.Tab = function(codeMirror) 347 { 348 if (codeMirror.somethingSelected()) 349 return CodeMirror.Pass; 350 var pos = codeMirror.getCursor("head"); 351 codeMirror.replaceRange(indent.substring(pos.ch % indent.length), codeMirror.getCursor()); 352 } 353 } 354 this._codeMirror.setOption("extraKeys", extraKeys); 355 this._indentationLevel = indent; 356 }, 357 358 /** 359 * @return {string} 360 */ 361 indent: function() 362 { 363 return this._indentationLevel; 364 }, 365 366 /** 367 * @return {boolean} 368 */ 369 _isSearchActive: function() 370 { 371 return !!this._tokenHighlighter.highlightedRegex(); 372 }, 373 374 /** 375 * @param {!RegExp} regex 376 * @param {?WebInspector.TextRange} range 377 */ 378 highlightSearchResults: function(regex, range) 379 { 380 /** 381 * @this {WebInspector.CodeMirrorTextEditor} 382 */ 383 function innerHighlightRegex() 384 { 385 if (range) { 386 this._revealLine(range.startLine); 387 if (range.endColumn > WebInspector.CodeMirrorTextEditor.maxHighlightLength) 388 this.setSelection(range); 389 else 390 this.setSelection(WebInspector.TextRange.createFromLocation(range.startLine, range.startColumn)); 391 } else { 392 // Collapse selection to end on search start so that we jump to next occurrence on the first enter press. 393 this.setSelection(this.selection().collapseToEnd()); 394 } 395 this._tokenHighlighter.highlightSearchResults(regex, range); 396 } 397 if (!this._selectionBeforeSearch) 398 this._selectionBeforeSearch = this.selection(); 399 this._codeMirror.operation(innerHighlightRegex.bind(this)); 400 }, 401 402 cancelSearchResultsHighlight: function() 403 { 404 this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter)); 405 if (this._selectionBeforeSearch) { 406 this._reportJump(this._selectionBeforeSearch, this.selection()); 407 delete this._selectionBeforeSearch; 408 } 409 }, 410 411 undo: function() 412 { 413 this._codeMirror.undo(); 414 }, 415 416 redo: function() 417 { 418 this._codeMirror.redo(); 419 }, 420 421 _setupWhitespaceHighlight: function() 422 { 423 if (WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected || !WebInspector.settings.showWhitespacesInEditor.get()) 424 return; 425 WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected = true; 426 const classBase = ".show-whitespaces .CodeMirror .cm-whitespace-"; 427 const spaceChar = "·"; 428 var spaceChars = ""; 429 var rules = ""; 430 for (var i = 1; i <= WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan; ++i) { 431 spaceChars += spaceChar; 432 var rule = classBase + i + "::before { content: '" + spaceChars + "';}\n"; 433 rules += rule; 434 } 435 var style = document.createElement("style"); 436 style.textContent = rules; 437 document.head.appendChild(style); 438 }, 439 440 _handleKeyDown: function(e) 441 { 442 if (this._autocompleteController.keyDown(e)) 443 e.consume(true); 444 }, 445 446 _handlePostKeyDown: function(e) 447 { 448 if (e.defaultPrevented) 449 e.consume(true); 450 }, 451 452 /** 453 * @param {?WebInspector.CompletionDictionary} dictionary 454 */ 455 setCompletionDictionary: function(dictionary) 456 { 457 this._autocompleteController.dispose(); 458 if (dictionary) 459 this._autocompleteController = new WebInspector.CodeMirrorTextEditor.AutocompleteController(this, this._codeMirror, dictionary); 460 else 461 this._autocompleteController = WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy; 462 }, 463 464 /** 465 * @param {number} lineNumber 466 * @param {number} column 467 * @return {?{x: number, y: number, height: number}} 468 */ 469 cursorPositionToCoordinates: function(lineNumber, column) 470 { 471 if (lineNumber >= this._codeMirror.lineCount() || lineNumber < 0 || column < 0 || column > this._codeMirror.getLine(lineNumber).length) 472 return null; 473 474 var metrics = this._codeMirror.cursorCoords(new CodeMirror.Pos(lineNumber, column)); 475 476 return { 477 x: metrics.left, 478 y: metrics.top, 479 height: metrics.bottom - metrics.top 480 }; 481 }, 482 483 /** 484 * @param {number} x 485 * @param {number} y 486 * @return {?WebInspector.TextRange} 487 */ 488 coordinatesToCursorPosition: function(x, y) 489 { 490 var element = document.elementFromPoint(x, y); 491 if (!element || !element.isSelfOrDescendant(this._codeMirror.getWrapperElement())) 492 return null; 493 var gutterBox = this._codeMirror.getGutterElement().boxInWindow(); 494 if (x >= gutterBox.x && x <= gutterBox.x + gutterBox.width && 495 y >= gutterBox.y && y <= gutterBox.y + gutterBox.height) 496 return null; 497 var coords = this._codeMirror.coordsChar({left: x, top: y}); 498 return WebInspector.CodeMirrorUtils.toRange(coords, coords); 499 }, 500 501 /** 502 * @param {number} lineNumber 503 * @param {number} column 504 * @return {?{startColumn: number, endColumn: number, type: string}} 505 */ 506 tokenAtTextPosition: function(lineNumber, column) 507 { 508 if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount()) 509 return null; 510 var token = this._codeMirror.getTokenAt(new CodeMirror.Pos(lineNumber, (column || 0) + 1)); 511 if (!token || !token.type) 512 return null; 513 return { 514 startColumn: token.start, 515 endColumn: token.end - 1, 516 type: token.type 517 }; 518 }, 519 520 /** 521 * @param {!WebInspector.TextRange} textRange 522 * @return {string} 523 */ 524 copyRange: function(textRange) 525 { 526 var pos = WebInspector.CodeMirrorUtils.toPos(textRange.normalize()); 527 return this._codeMirror.getRange(pos.start, pos.end); 528 }, 529 530 /** 531 * @return {boolean} 532 */ 533 isClean: function() 534 { 535 return this._codeMirror.isClean(); 536 }, 537 538 markClean: function() 539 { 540 this._codeMirror.markClean(); 541 }, 542 543 _hasLongLines: function() 544 { 545 function lineIterator(lineHandle) 546 { 547 if (lineHandle.text.length > WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold) 548 hasLongLines = true; 549 return hasLongLines; 550 } 551 var hasLongLines = false; 552 this._codeMirror.eachLine(lineIterator); 553 return hasLongLines; 554 }, 555 556 /** 557 * @param {string} mimeType 558 * @return {string} 559 */ 560 _whitespaceOverlayMode: function(mimeType) 561 { 562 var modeName = CodeMirror.mimeModes[mimeType] ? (CodeMirror.mimeModes[mimeType].name || CodeMirror.mimeModes[mimeType]) : CodeMirror.mimeModes["text/plain"]; 563 modeName += "+whitespaces"; 564 if (CodeMirror.modes[modeName]) 565 return modeName; 566 567 function modeConstructor(config, parserConfig) 568 { 569 function nextToken(stream) 570 { 571 if (stream.peek() === " ") { 572 var spaces = 0; 573 while (spaces < WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan && stream.peek() === " ") { 574 ++spaces; 575 stream.next(); 576 } 577 return "whitespace whitespace-" + spaces; 578 } 579 while (!stream.eol() && stream.peek() !== " ") 580 stream.next(); 581 return null; 582 } 583 var whitespaceMode = { 584 token: nextToken 585 }; 586 return CodeMirror.overlayMode(CodeMirror.getMode(config, mimeType), whitespaceMode, false); 587 } 588 CodeMirror.defineMode(modeName, modeConstructor); 589 return modeName; 590 }, 591 592 _enableLongLinesMode: function() 593 { 594 this._codeMirror.setOption("styleSelectedText", false); 595 this._longLinesMode = true; 596 }, 597 598 _disableLongLinesMode: function() 599 { 600 this._codeMirror.setOption("styleSelectedText", true); 601 this._longLinesMode = false; 602 }, 603 604 _updateCodeMirrorMode: function() 605 { 606 var showWhitespaces = WebInspector.settings.showWhitespacesInEditor.get(); 607 this.element.classList.toggle("show-whitespaces", showWhitespaces); 608 this._codeMirror.setOption("mode", showWhitespaces ? this._whitespaceOverlayMode(this._mimeType) : this._mimeType); 609 }, 610 611 /** 612 * @param {string} mimeType 613 */ 614 setMimeType: function(mimeType) 615 { 616 this._mimeType = mimeType; 617 if (this._hasLongLines()) 618 this._enableLongLinesMode(); 619 else 620 this._disableLongLinesMode(); 621 this._updateCodeMirrorMode(); 622 this._autocompleteController.setMimeType(mimeType); 623 }, 624 625 /** 626 * @param {boolean} readOnly 627 */ 628 setReadOnly: function(readOnly) 629 { 630 this.element.classList.toggle("CodeMirror-readonly", readOnly) 631 this._codeMirror.setOption("readOnly", readOnly); 632 }, 633 634 /** 635 * @return {boolean} 636 */ 637 readOnly: function() 638 { 639 return !!this._codeMirror.getOption("readOnly"); 640 }, 641 642 /** 643 * @param {!Object} highlightDescriptor 644 */ 645 removeHighlight: function(highlightDescriptor) 646 { 647 highlightDescriptor.clear(); 648 }, 649 650 /** 651 * @param {!WebInspector.TextRange} range 652 * @param {string} cssClass 653 * @return {!Object} 654 */ 655 highlightRange: function(range, cssClass) 656 { 657 cssClass = "CodeMirror-persist-highlight " + cssClass; 658 var pos = WebInspector.CodeMirrorUtils.toPos(range); 659 ++pos.end.ch; 660 return this._codeMirror.markText(pos.start, pos.end, { 661 className: cssClass, 662 startStyle: cssClass + "-start", 663 endStyle: cssClass + "-end" 664 }); 665 }, 666 667 /** 668 * @return {!Element} 669 */ 670 defaultFocusedElement: function() 671 { 672 return this.element; 673 }, 674 675 focus: function() 676 { 677 this._codeMirror.focus(); 678 }, 679 680 _handleElementFocus: function() 681 { 682 this._codeMirror.focus(); 683 }, 684 685 beginUpdates: function() 686 { 687 ++this._nestedUpdatesCounter; 688 }, 689 690 endUpdates: function() 691 { 692 if (!--this._nestedUpdatesCounter) 693 this._codeMirror.refresh(); 694 }, 695 696 /** 697 * @param {number} lineNumber 698 */ 699 _revealLine: function(lineNumber) 700 { 701 this._innerRevealLine(lineNumber, this._codeMirror.getScrollInfo()); 702 }, 703 704 /** 705 * @param {number} lineNumber 706 * @param {!{left: number, top: number, width: number, height: number, clientWidth: number, clientHeight: number}} scrollInfo 707 */ 708 _innerRevealLine: function(lineNumber, scrollInfo) 709 { 710 var topLine = this._codeMirror.lineAtHeight(scrollInfo.top, "local"); 711 var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local"); 712 var linesPerScreen = bottomLine - topLine + 1; 713 if (lineNumber < topLine) { 714 var topLineToReveal = Math.max(lineNumber - (linesPerScreen / 2) + 1, 0) | 0; 715 this._codeMirror.scrollIntoView(new CodeMirror.Pos(topLineToReveal, 0)); 716 } else if (lineNumber > bottomLine) { 717 var bottomLineToReveal = Math.min(lineNumber + (linesPerScreen / 2) - 1, this.linesCount - 1) | 0; 718 this._codeMirror.scrollIntoView(new CodeMirror.Pos(bottomLineToReveal, 0)); 719 } 720 }, 721 722 _gutterClick: function(instance, lineNumber, gutter, event) 723 { 724 this.dispatchEventToListeners(WebInspector.TextEditor.Events.GutterClick, { lineNumber: lineNumber, event: event }); 725 }, 726 727 _contextMenu: function(event) 728 { 729 var contextMenu = new WebInspector.ContextMenu(event); 730 var target = event.target.enclosingNodeOrSelfWithClass("CodeMirror-gutter-elt"); 731 if (target) 732 this._delegate.populateLineGutterContextMenu(contextMenu, parseInt(target.textContent, 10) - 1); 733 else 734 this._delegate.populateTextAreaContextMenu(contextMenu, 0); 735 contextMenu.show(); 736 }, 737 738 /** 739 * @param {number} lineNumber 740 * @param {boolean} disabled 741 * @param {boolean} conditional 742 */ 743 addBreakpoint: function(lineNumber, disabled, conditional) 744 { 745 if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount()) 746 return; 747 var className = "cm-breakpoint" + (conditional ? " cm-breakpoint-conditional" : "") + (disabled ? " cm-breakpoint-disabled" : ""); 748 this._codeMirror.addLineClass(lineNumber, "wrap", className); 749 }, 750 751 /** 752 * @param {number} lineNumber 753 */ 754 removeBreakpoint: function(lineNumber) 755 { 756 if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount()) 757 return; 758 var wrapClasses = this._codeMirror.getLineHandle(lineNumber).wrapClass; 759 if (!wrapClasses) 760 return; 761 var classes = wrapClasses.split(" "); 762 for (var i = 0; i < classes.length; ++i) { 763 if (classes[i].startsWith("cm-breakpoint")) 764 this._codeMirror.removeLineClass(lineNumber, "wrap", classes[i]); 765 } 766 }, 767 768 /** 769 * @param {number} lineNumber 770 */ 771 setExecutionLine: function(lineNumber) 772 { 773 this.clearPositionHighlight(); 774 this._executionLine = this._codeMirror.getLineHandle(lineNumber); 775 if (!this._executionLine) 776 return; 777 this._codeMirror.addLineClass(this._executionLine, "wrap", "cm-execution-line"); 778 }, 779 780 clearExecutionLine: function() 781 { 782 this.clearPositionHighlight(); 783 if (this._executionLine) 784 this._codeMirror.removeLineClass(this._executionLine, "wrap", "cm-execution-line"); 785 delete this._executionLine; 786 }, 787 788 /** 789 * @param {number} lineNumber 790 * @param {!Element} element 791 */ 792 addDecoration: function(lineNumber, element) 793 { 794 var widget = this._codeMirror.addLineWidget(lineNumber, element); 795 this._elementToWidget.put(element, widget); 796 }, 797 798 /** 799 * @param {number} lineNumber 800 * @param {!Element} element 801 */ 802 removeDecoration: function(lineNumber, element) 803 { 804 var widget = this._elementToWidget.remove(element); 805 if (widget) 806 this._codeMirror.removeLineWidget(widget); 807 }, 808 809 /** 810 * @param {number} lineNumber 811 * @param {number=} columnNumber 812 * @param {boolean=} shouldHighlight 813 */ 814 revealPosition: function(lineNumber, columnNumber, shouldHighlight) 815 { 816 lineNumber = Number.constrain(lineNumber, 0, this._codeMirror.lineCount() - 1); 817 if (typeof columnNumber !== "number") 818 columnNumber = 0; 819 columnNumber = Number.constrain(columnNumber, 0, this._codeMirror.getLine(lineNumber).length); 820 821 this.clearPositionHighlight(); 822 this._highlightedLine = this._codeMirror.getLineHandle(lineNumber); 823 if (!this._highlightedLine) 824 return; 825 this._revealLine(lineNumber); 826 if (shouldHighlight) { 827 this._codeMirror.addLineClass(this._highlightedLine, null, "cm-highlight"); 828 this._clearHighlightTimeout = setTimeout(this.clearPositionHighlight.bind(this), 2000); 829 } 830 this.setSelection(WebInspector.TextRange.createFromLocation(lineNumber, columnNumber)); 831 }, 832 833 clearPositionHighlight: function() 834 { 835 if (this._clearHighlightTimeout) 836 clearTimeout(this._clearHighlightTimeout); 837 delete this._clearHighlightTimeout; 838 839 if (this._highlightedLine) 840 this._codeMirror.removeLineClass(this._highlightedLine, null, "cm-highlight"); 841 delete this._highlightedLine; 842 }, 843 844 /** 845 * @return {!Array.<!Element>} 846 */ 847 elementsToRestoreScrollPositionsFor: function() 848 { 849 return []; 850 }, 851 852 /** 853 * @param {!WebInspector.TextEditor} textEditor 854 */ 855 inheritScrollPositions: function(textEditor) 856 { 857 }, 858 859 /** 860 * @param {number} width 861 * @param {number} height 862 */ 863 _updatePaddingBottom: function(width, height) 864 { 865 var scrollInfo = this._codeMirror.getScrollInfo(); 866 var newPaddingBottom; 867 var linesElement = this.element.firstElementChild.querySelector(".CodeMirror-lines"); 868 var lineCount = this._codeMirror.lineCount(); 869 if (lineCount <= 1) 870 newPaddingBottom = 0; 871 else 872 newPaddingBottom = Math.max(scrollInfo.clientHeight - this._codeMirror.getLineHandle(this._codeMirror.lastLine()).height, 0); 873 newPaddingBottom += "px"; 874 linesElement.style.paddingBottom = newPaddingBottom; 875 this._codeMirror.setSize(width, height); 876 }, 877 878 _resizeEditor: function() 879 { 880 var parentElement = this.element.parentElement; 881 if (!parentElement || !this.isShowing()) 882 return; 883 var scrollLeft = this._codeMirror.doc.scrollLeft; 884 var scrollTop = this._codeMirror.doc.scrollTop; 885 var width = parentElement.offsetWidth; 886 var height = parentElement.offsetHeight; 887 this._codeMirror.setSize(width, height); 888 this._updatePaddingBottom(width, height); 889 this._codeMirror.scrollTo(scrollLeft, scrollTop); 890 }, 891 892 onResize: function() 893 { 894 this._autocompleteController.finishAutocomplete(); 895 this._resizeEditor(); 896 }, 897 898 /** 899 * @param {!WebInspector.TextRange} range 900 * @param {string} text 901 * @return {!WebInspector.TextRange} 902 */ 903 editRange: function(range, text) 904 { 905 var pos = WebInspector.CodeMirrorUtils.toPos(range); 906 this._codeMirror.replaceRange(text, pos.start, pos.end); 907 var newRange = WebInspector.CodeMirrorUtils.toRange(pos.start, this._codeMirror.posFromIndex(this._codeMirror.indexFromPos(pos.start) + text.length)); 908 this._delegate.onTextChanged(range, newRange); 909 if (WebInspector.settings.textEditorAutoDetectIndent.get()) 910 this._updateEditorIndentation(); 911 return newRange; 912 }, 913 914 /** 915 * @param {number} lineNumber 916 * @param {number} column 917 * @param {function(string):boolean} isWordChar 918 * @return {!WebInspector.TextRange} 919 */ 920 _wordRangeForCursorPosition: function(lineNumber, column, isWordChar) 921 { 922 var line = this.line(lineNumber); 923 var wordStart = column; 924 if (column !== 0 && isWordChar(line.charAt(column - 1))) { 925 wordStart = column - 1; 926 while (wordStart > 0 && isWordChar(line.charAt(wordStart - 1))) 927 --wordStart; 928 } 929 var wordEnd = column; 930 while (wordEnd < line.length && isWordChar(line.charAt(wordEnd))) 931 ++wordEnd; 932 return new WebInspector.TextRange(lineNumber, wordStart, lineNumber, wordEnd); 933 }, 934 935 /** 936 * @param {!WebInspector.CodeMirrorTextEditor.ChangeObject} changeObject 937 * @return {{oldRange: !WebInspector.TextRange, newRange: !WebInspector.TextRange}} 938 */ 939 _changeObjectToEditOperation: function(changeObject) 940 { 941 var oldRange = WebInspector.CodeMirrorUtils.toRange(changeObject.from, changeObject.to); 942 var newRange = oldRange.clone(); 943 var linesAdded = changeObject.text.length; 944 if (linesAdded === 0) { 945 newRange.endLine = newRange.startLine; 946 newRange.endColumn = newRange.startColumn; 947 } else if (linesAdded === 1) { 948 newRange.endLine = newRange.startLine; 949 newRange.endColumn = newRange.startColumn + changeObject.text[0].length; 950 } else { 951 newRange.endLine = newRange.startLine + linesAdded - 1; 952 newRange.endColumn = changeObject.text[linesAdded - 1].length; 953 } 954 return { 955 oldRange: oldRange, 956 newRange: newRange 957 }; 958 }, 959 960 /** 961 * @param {!CodeMirror} codeMirror 962 * @param {!Array.<!WebInspector.CodeMirrorTextEditor.ChangeObject>} changes 963 */ 964 _changes: function(codeMirror, changes) 965 { 966 if (!changes.length) 967 return; 968 // We do not show "scroll beyond end of file" span for one line documents, so we need to check if "document has one line" changed. 969 var hasOneLine = this._codeMirror.lineCount() === 1; 970 if (hasOneLine !== this._hasOneLine) 971 this._resizeEditor(); 972 this._hasOneLine = hasOneLine; 973 var widgets = this._elementToWidget.values(); 974 for (var i = 0; i < widgets.length; ++i) 975 this._codeMirror.removeLineWidget(widgets[i]); 976 this._elementToWidget.clear(); 977 978 for (var changeIndex = 0; changeIndex < changes.length; ++changeIndex) { 979 var changeObject = changes[changeIndex]; 980 981 var editInfo = this._changeObjectToEditOperation(changeObject); 982 if (!this._muteTextChangedEvent) 983 this._delegate.onTextChanged(editInfo.oldRange, editInfo.newRange); 984 } 985 }, 986 987 _cursorActivity: function() 988 { 989 var start = this._codeMirror.getCursor("anchor"); 990 var end = this._codeMirror.getCursor("head"); 991 this._delegate.selectionChanged(WebInspector.CodeMirrorUtils.toRange(start, end)); 992 if (!this._isSearchActive()) 993 this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter)); 994 }, 995 996 /** 997 * @param {!CodeMirror} codeMirror 998 * @param {{ranges: !Array.<{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}>}} selection 999 */ 1000 _beforeSelectionChange: function(codeMirror, selection) 1001 { 1002 this._selectNextOccurrenceController.selectionWillChange(); 1003 if (!this._isHandlingMouseDownEvent) 1004 return; 1005 if (!selection.ranges.length) 1006 return; 1007 var primarySelection = selection.ranges[0]; 1008 this._reportJump(this.selection(), WebInspector.CodeMirrorUtils.toRange(primarySelection.anchor, primarySelection.head)); 1009 }, 1010 1011 /** 1012 * @param {?WebInspector.TextRange} from 1013 * @param {?WebInspector.TextRange} to 1014 */ 1015 _reportJump: function(from, to) 1016 { 1017 if (from && to && from.equal(to)) 1018 return; 1019 this._delegate.onJumpToPosition(from, to); 1020 }, 1021 1022 _scroll: function() 1023 { 1024 if (this._scrollTimer) 1025 clearTimeout(this._scrollTimer); 1026 var topmostLineNumber = this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local"); 1027 this._scrollTimer = setTimeout(this._delegate.scrollChanged.bind(this._delegate, topmostLineNumber), 100); 1028 }, 1029 1030 _focus: function() 1031 { 1032 this._delegate.editorFocused(); 1033 }, 1034 1035 /** 1036 * @param {number} lineNumber 1037 */ 1038 scrollToLine: function(lineNumber) 1039 { 1040 var pos = new CodeMirror.Pos(lineNumber, 0); 1041 var coords = this._codeMirror.charCoords(pos, "local"); 1042 this._codeMirror.scrollTo(0, coords.top); 1043 }, 1044 1045 /** 1046 * @return {number} 1047 */ 1048 firstVisibleLine: function() 1049 { 1050 return this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local"); 1051 }, 1052 1053 /** 1054 * @return {number} 1055 */ 1056 lastVisibleLine: function() 1057 { 1058 var scrollInfo = this._codeMirror.getScrollInfo(); 1059 return this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local"); 1060 }, 1061 1062 /** 1063 * @return {!WebInspector.TextRange} 1064 */ 1065 selection: function() 1066 { 1067 var start = this._codeMirror.getCursor("anchor"); 1068 var end = this._codeMirror.getCursor("head"); 1069 1070 return WebInspector.CodeMirrorUtils.toRange(start, end); 1071 }, 1072 1073 /** 1074 * @return {!Array.<!WebInspector.TextRange>} 1075 */ 1076 selections: function() 1077 { 1078 var selectionList = this._codeMirror.listSelections(); 1079 var result = []; 1080 for (var i = 0; i < selectionList.length; ++i) { 1081 var selection = selectionList[i]; 1082 result.push(WebInspector.CodeMirrorUtils.toRange(selection.anchor, selection.head)); 1083 } 1084 return result; 1085 }, 1086 1087 /** 1088 * @return {?WebInspector.TextRange} 1089 */ 1090 lastSelection: function() 1091 { 1092 return this._lastSelection; 1093 }, 1094 1095 /** 1096 * @param {!WebInspector.TextRange} textRange 1097 */ 1098 setSelection: function(textRange) 1099 { 1100 this._lastSelection = textRange; 1101 var pos = WebInspector.CodeMirrorUtils.toPos(textRange); 1102 this._codeMirror.setSelection(pos.start, pos.end); 1103 }, 1104 1105 /** 1106 * @param {!Array.<!WebInspector.TextRange>} ranges 1107 * @param {number=} primarySelectionIndex 1108 */ 1109 setSelections: function(ranges, primarySelectionIndex) 1110 { 1111 var selections = []; 1112 for (var i = 0; i < ranges.length; ++i) { 1113 var selection = WebInspector.CodeMirrorUtils.toPos(ranges[i]); 1114 selections.push({ 1115 anchor: selection.start, 1116 head: selection.end 1117 }); 1118 } 1119 primarySelectionIndex = primarySelectionIndex || 0; 1120 this._codeMirror.setSelections(selections, primarySelectionIndex, { scroll: false }); 1121 }, 1122 1123 /** 1124 * @param {string} text 1125 */ 1126 _detectLineSeparator: function(text) 1127 { 1128 this._lineSeparator = text.indexOf("\r\n") >= 0 ? "\r\n" : "\n"; 1129 }, 1130 1131 /** 1132 * @param {string} text 1133 */ 1134 setText: function(text) 1135 { 1136 this._muteTextChangedEvent = true; 1137 if (text.length > WebInspector.CodeMirrorTextEditor.MaxEditableTextSize) { 1138 this._autocompleteController.setEnabled(false); 1139 this.setReadOnly(true); 1140 } 1141 this._codeMirror.setValue(text); 1142 this._updateEditorIndentation(); 1143 if (this._shouldClearHistory) { 1144 this._codeMirror.clearHistory(); 1145 this._shouldClearHistory = false; 1146 } 1147 this._detectLineSeparator(text); 1148 delete this._muteTextChangedEvent; 1149 }, 1150 1151 /** 1152 * @return {string} 1153 */ 1154 text: function() 1155 { 1156 return this._codeMirror.getValue().replace(/\n/g, this._lineSeparator); 1157 }, 1158 1159 /** 1160 * @return {!WebInspector.TextRange} 1161 */ 1162 range: function() 1163 { 1164 var lineCount = this.linesCount; 1165 var lastLine = this._codeMirror.getLine(lineCount - 1); 1166 return WebInspector.CodeMirrorUtils.toRange(new CodeMirror.Pos(0, 0), new CodeMirror.Pos(lineCount - 1, lastLine.length)); 1167 }, 1168 1169 /** 1170 * @param {number} lineNumber 1171 * @return {string} 1172 */ 1173 line: function(lineNumber) 1174 { 1175 return this._codeMirror.getLine(lineNumber); 1176 }, 1177 1178 /** 1179 * @return {number} 1180 */ 1181 get linesCount() 1182 { 1183 return this._codeMirror.lineCount(); 1184 }, 1185 1186 /** 1187 * @param {number} line 1188 * @param {string} name 1189 * @param {?Object} value 1190 */ 1191 setAttribute: function(line, name, value) 1192 { 1193 if (line < 0 || line >= this._codeMirror.lineCount()) 1194 return; 1195 var handle = this._codeMirror.getLineHandle(line); 1196 if (handle.attributes === undefined) handle.attributes = {}; 1197 handle.attributes[name] = value; 1198 }, 1199 1200 /** 1201 * @param {number} line 1202 * @param {string} name 1203 * @return {?Object} value 1204 */ 1205 getAttribute: function(line, name) 1206 { 1207 if (line < 0 || line >= this._codeMirror.lineCount()) 1208 return null; 1209 var handle = this._codeMirror.getLineHandle(line); 1210 return handle.attributes && handle.attributes[name] !== undefined ? handle.attributes[name] : null; 1211 }, 1212 1213 /** 1214 * @param {number} line 1215 * @param {string} name 1216 */ 1217 removeAttribute: function(line, name) 1218 { 1219 if (line < 0 || line >= this._codeMirror.lineCount()) 1220 return; 1221 var handle = this._codeMirror.getLineHandle(line); 1222 if (handle && handle.attributes) 1223 delete handle.attributes[name]; 1224 }, 1225 1226 /** 1227 * @param {number} lineNumber 1228 * @param {number} columnNumber 1229 * @return {!WebInspector.TextEditorPositionHandle} 1230 */ 1231 textEditorPositionHandle: function(lineNumber, columnNumber) 1232 { 1233 return new WebInspector.CodeMirrorPositionHandle(this._codeMirror, new CodeMirror.Pos(lineNumber, columnNumber)); 1234 }, 1235 1236 __proto__: WebInspector.VBox.prototype 1237} 1238 1239/** 1240 * @constructor 1241 * @implements {WebInspector.TextEditorPositionHandle} 1242 * @param {!CodeMirror} codeMirror 1243 * @param {!CodeMirror.Pos} pos 1244 */ 1245WebInspector.CodeMirrorPositionHandle = function(codeMirror, pos) 1246{ 1247 this._codeMirror = codeMirror; 1248 this._lineHandle = codeMirror.getLineHandle(pos.line); 1249 this._columnNumber = pos.ch; 1250} 1251 1252WebInspector.CodeMirrorPositionHandle.prototype = { 1253 /** 1254 * @return {?{lineNumber: number, columnNumber: number}} 1255 */ 1256 resolve: function() 1257 { 1258 var lineNumber = this._codeMirror.getLineNumber(this._lineHandle); 1259 if (typeof lineNumber !== "number") 1260 return null; 1261 return { 1262 lineNumber: lineNumber, 1263 columnNumber: this._columnNumber 1264 }; 1265 }, 1266 1267 /** 1268 * @param {!WebInspector.TextEditorPositionHandle} positionHandle 1269 * @return {boolean} 1270 */ 1271 equal: function(positionHandle) 1272 { 1273 return positionHandle._lineHandle === this._lineHandle && positionHandle._columnNumber == this._columnNumber && positionHandle._codeMirror === this._codeMirror; 1274 } 1275} 1276 1277/** 1278 * @constructor 1279 * @param {!WebInspector.CodeMirrorTextEditor} textEditor 1280 * @param {!CodeMirror} codeMirror 1281 */ 1282WebInspector.CodeMirrorTextEditor.TokenHighlighter = function(textEditor, codeMirror) 1283{ 1284 this._textEditor = textEditor; 1285 this._codeMirror = codeMirror; 1286} 1287 1288WebInspector.CodeMirrorTextEditor.TokenHighlighter.prototype = { 1289 /** 1290 * @param {!RegExp} regex 1291 * @param {?WebInspector.TextRange} range 1292 */ 1293 highlightSearchResults: function(regex, range) 1294 { 1295 var oldRegex = this._highlightRegex; 1296 this._highlightRegex = regex; 1297 this._highlightRange = range; 1298 if (this._searchResultMarker) { 1299 this._searchResultMarker.clear(); 1300 delete this._searchResultMarker; 1301 } 1302 if (this._highlightDescriptor && this._highlightDescriptor.selectionStart) 1303 this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, "wrap", "cm-line-with-selection"); 1304 var selectionStart = this._highlightRange ? new CodeMirror.Pos(this._highlightRange.startLine, this._highlightRange.startColumn) : null; 1305 if (selectionStart) 1306 this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection"); 1307 if (this._highlightRegex === oldRegex) { 1308 // Do not re-add overlay mode if regex did not change for better performance. 1309 if (this._highlightDescriptor) 1310 this._highlightDescriptor.selectionStart = selectionStart; 1311 } else { 1312 this._removeHighlight(); 1313 this._setHighlighter(this._searchHighlighter.bind(this, this._highlightRegex), selectionStart); 1314 } 1315 if (this._highlightRange) { 1316 var pos = WebInspector.CodeMirrorUtils.toPos(this._highlightRange); 1317 this._searchResultMarker = this._codeMirror.markText(pos.start, pos.end, {className: "cm-column-with-selection"}); 1318 } 1319 }, 1320 1321 /** 1322 * @return {!RegExp|undefined} 1323 */ 1324 highlightedRegex: function() 1325 { 1326 return this._highlightRegex; 1327 }, 1328 1329 highlightSelectedTokens: function() 1330 { 1331 delete this._highlightRegex; 1332 delete this._highlightRange; 1333 1334 if (this._highlightDescriptor && this._highlightDescriptor.selectionStart) 1335 this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, "wrap", "cm-line-with-selection"); 1336 this._removeHighlight(); 1337 var selectionStart = this._codeMirror.getCursor("start"); 1338 var selectionEnd = this._codeMirror.getCursor("end"); 1339 if (selectionStart.line !== selectionEnd.line) 1340 return; 1341 if (selectionStart.ch === selectionEnd.ch) 1342 return; 1343 1344 var selections = this._codeMirror.getSelections(); 1345 if (selections.length > 1) 1346 return; 1347 var selectedText = selections[0]; 1348 if (this._isWord(selectedText, selectionStart.line, selectionStart.ch, selectionEnd.ch)) { 1349 if (selectionStart) 1350 this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection") 1351 this._setHighlighter(this._tokenHighlighter.bind(this, selectedText, selectionStart), selectionStart); 1352 } 1353 }, 1354 1355 /** 1356 * @param {string} selectedText 1357 * @param {number} lineNumber 1358 * @param {number} startColumn 1359 * @param {number} endColumn 1360 */ 1361 _isWord: function(selectedText, lineNumber, startColumn, endColumn) 1362 { 1363 var line = this._codeMirror.getLine(lineNumber); 1364 var leftBound = startColumn === 0 || !WebInspector.TextUtils.isWordChar(line.charAt(startColumn - 1)); 1365 var rightBound = endColumn === line.length || !WebInspector.TextUtils.isWordChar(line.charAt(endColumn)); 1366 return leftBound && rightBound && WebInspector.TextUtils.isWord(selectedText); 1367 }, 1368 1369 _removeHighlight: function() 1370 { 1371 if (this._highlightDescriptor) { 1372 this._codeMirror.removeOverlay(this._highlightDescriptor.overlay); 1373 delete this._highlightDescriptor; 1374 } 1375 }, 1376 1377 /** 1378 * @param {!RegExp} regex 1379 * @param {!CodeMirror.StringStream} stream 1380 */ 1381 _searchHighlighter: function(regex, stream) 1382 { 1383 if (stream.column() === 0) 1384 delete this._searchMatchLength; 1385 if (this._searchMatchLength) { 1386 if (this._searchMatchLength > 2) { 1387 for (var i = 0; i < this._searchMatchLength - 2; ++i) 1388 stream.next(); 1389 this._searchMatchLength = 1; 1390 return "search-highlight"; 1391 } else { 1392 stream.next(); 1393 delete this._searchMatchLength; 1394 return "search-highlight search-highlight-end"; 1395 } 1396 } 1397 var match = stream.match(regex, false); 1398 if (match) { 1399 stream.next(); 1400 var matchLength = match[0].length; 1401 if (matchLength === 1) 1402 return "search-highlight search-highlight-full"; 1403 this._searchMatchLength = matchLength; 1404 return "search-highlight search-highlight-start"; 1405 } 1406 1407 while (!stream.match(regex, false) && stream.next()) {}; 1408 }, 1409 1410 /** 1411 * @param {string} token 1412 * @param {!CodeMirror.Pos} selectionStart 1413 * @param {!CodeMirror.StringStream} stream 1414 */ 1415 _tokenHighlighter: function(token, selectionStart, stream) 1416 { 1417 var tokenFirstChar = token.charAt(0); 1418 if (stream.match(token) && (stream.eol() || !WebInspector.TextUtils.isWordChar(stream.peek()))) 1419 return stream.column() === selectionStart.ch ? "token-highlight column-with-selection" : "token-highlight"; 1420 1421 var eatenChar; 1422 do { 1423 eatenChar = stream.next(); 1424 } while (eatenChar && (WebInspector.TextUtils.isWordChar(eatenChar) || stream.peek() !== tokenFirstChar)); 1425 }, 1426 1427 /** 1428 * @param {function(!CodeMirror.StringStream)} highlighter 1429 * @param {?CodeMirror.Pos} selectionStart 1430 */ 1431 _setHighlighter: function(highlighter, selectionStart) 1432 { 1433 var overlayMode = { 1434 token: highlighter 1435 }; 1436 this._codeMirror.addOverlay(overlayMode); 1437 this._highlightDescriptor = { 1438 overlay: overlayMode, 1439 selectionStart: selectionStart 1440 }; 1441 } 1442} 1443 1444/** 1445 * @constructor 1446 * @param {!CodeMirror} codeMirror 1447 */ 1448WebInspector.CodeMirrorTextEditor.BlockIndentController = function(codeMirror) 1449{ 1450 codeMirror.addKeyMap(this); 1451} 1452 1453WebInspector.CodeMirrorTextEditor.BlockIndentController.prototype = { 1454 name: "blockIndentKeymap", 1455 1456 /** 1457 * @return {*} 1458 */ 1459 Enter: function(codeMirror) 1460 { 1461 if (codeMirror.somethingSelected()) 1462 return CodeMirror.Pass; 1463 var cursor = codeMirror.getCursor(); 1464 if (cursor.ch === 0) 1465 return CodeMirror.Pass; 1466 var line = codeMirror.getLine(cursor.line); 1467 if (line.substr(cursor.ch - 1, 2) === "{}") { 1468 codeMirror.execCommand("newlineAndIndent"); 1469 codeMirror.setCursor(cursor); 1470 codeMirror.execCommand("newlineAndIndent"); 1471 codeMirror.execCommand("indentMore"); 1472 } else if (line.substr(cursor.ch - 1, 1) === "{") { 1473 codeMirror.execCommand("newlineAndIndent"); 1474 codeMirror.execCommand("indentMore"); 1475 } else 1476 return CodeMirror.Pass; 1477 }, 1478 1479 /** 1480 * @return {*} 1481 */ 1482 "'}'": function(codeMirror) 1483 { 1484 var cursor = codeMirror.getCursor(); 1485 var line = codeMirror.getLine(cursor.line); 1486 for (var i = 0 ; i < line.length; ++i) { 1487 if (!WebInspector.TextUtils.isSpaceChar(line.charAt(i))) 1488 return CodeMirror.Pass; 1489 } 1490 1491 codeMirror.replaceRange("}", cursor); 1492 var matchingBracket = codeMirror.findMatchingBracket(cursor); 1493 if (!matchingBracket || !matchingBracket.match) 1494 return; 1495 1496 line = codeMirror.getLine(matchingBracket.to.line); 1497 var desiredIndentation = 0; 1498 while (desiredIndentation < line.length && WebInspector.TextUtils.isSpaceChar(line.charAt(desiredIndentation))) 1499 ++desiredIndentation; 1500 1501 codeMirror.replaceRange(line.substr(0, desiredIndentation) + "}", new CodeMirror.Pos(cursor.line, 0), new CodeMirror.Pos(cursor.line, cursor.ch + 1)); 1502 } 1503} 1504 1505/** 1506 * @constructor 1507 * @param {!CodeMirror} codeMirror 1508 */ 1509WebInspector.CodeMirrorTextEditor.FixWordMovement = function(codeMirror) 1510{ 1511 function moveLeft(shift, codeMirror) 1512 { 1513 codeMirror.setExtending(shift); 1514 var cursor = codeMirror.getCursor("head"); 1515 codeMirror.execCommand("goGroupLeft"); 1516 var newCursor = codeMirror.getCursor("head"); 1517 if (newCursor.ch === 0 && newCursor.line !== 0) { 1518 codeMirror.setExtending(false); 1519 return; 1520 } 1521 1522 var skippedText = codeMirror.getRange(newCursor, cursor, "#"); 1523 if (/^\s+$/.test(skippedText)) 1524 codeMirror.execCommand("goGroupLeft"); 1525 codeMirror.setExtending(false); 1526 } 1527 1528 function moveRight(shift, codeMirror) 1529 { 1530 codeMirror.setExtending(shift); 1531 var cursor = codeMirror.getCursor("head"); 1532 codeMirror.execCommand("goGroupRight"); 1533 var newCursor = codeMirror.getCursor("head"); 1534 if (newCursor.ch === 0 && newCursor.line !== 0) { 1535 codeMirror.setExtending(false); 1536 return; 1537 } 1538 1539 var skippedText = codeMirror.getRange(cursor, newCursor, "#"); 1540 if (/^\s+$/.test(skippedText)) 1541 codeMirror.execCommand("goGroupRight"); 1542 codeMirror.setExtending(false); 1543 } 1544 1545 var modifierKey = WebInspector.isMac() ? "Alt" : "Ctrl"; 1546 var leftKey = modifierKey + "-Left"; 1547 var rightKey = modifierKey + "-Right"; 1548 var keyMap = {}; 1549 keyMap[leftKey] = moveLeft.bind(null, false); 1550 keyMap[rightKey] = moveRight.bind(null, false); 1551 keyMap["Shift-" + leftKey] = moveLeft.bind(null, true); 1552 keyMap["Shift-" + rightKey] = moveRight.bind(null, true); 1553 codeMirror.addKeyMap(keyMap); 1554} 1555 1556/** 1557 * @interface 1558 */ 1559WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI = function() {} 1560 1561WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI.prototype = { 1562 dispose: function() { }, 1563 1564 /** 1565 * @param {boolean} enabled 1566 */ 1567 setEnabled: function(enabled) { }, 1568 1569 /** 1570 * @param {string} mimeType 1571 */ 1572 setMimeType: function(mimeType) { }, 1573 1574 autocomplete: function() { }, 1575 1576 finishAutocomplete: function() { }, 1577 1578 /** 1579 * @param {?Event} e 1580 * @return {boolean} 1581 */ 1582 keyDown: function(e) { } 1583} 1584 1585/** 1586 * @constructor 1587 * @implements {WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI} 1588 */ 1589WebInspector.CodeMirrorTextEditor.DummyAutocompleteController = function() { } 1590 1591WebInspector.CodeMirrorTextEditor.DummyAutocompleteController.prototype = { 1592 dispose: function() { }, 1593 1594 /** 1595 * @param {boolean} enabled 1596 */ 1597 setEnabled: function(enabled) { }, 1598 1599 /** 1600 * @param {string} mimeType 1601 */ 1602 setMimeType: function(mimeType) { }, 1603 1604 autocomplete: function() { }, 1605 1606 finishAutocomplete: function() { }, 1607 1608 /** 1609 * @param {?Event} e 1610 * @return {boolean} 1611 */ 1612 keyDown: function(e) 1613 { 1614 return false; 1615 } 1616} 1617 1618/** 1619 * @constructor 1620 * @implements {WebInspector.SuggestBoxDelegate} 1621 * @implements {WebInspector.CodeMirrorTextEditor.AutocompleteControllerAPI} 1622 * @param {!WebInspector.CodeMirrorTextEditor} textEditor 1623 * @param {!CodeMirror} codeMirror 1624 * @param {?WebInspector.CompletionDictionary} dictionary 1625 */ 1626WebInspector.CodeMirrorTextEditor.AutocompleteController = function(textEditor, codeMirror, dictionary) 1627{ 1628 this._textEditor = textEditor; 1629 this._codeMirror = codeMirror; 1630 1631 this._onScroll = this._onScroll.bind(this); 1632 this._onCursorActivity = this._onCursorActivity.bind(this); 1633 this._changes = this._changes.bind(this); 1634 this._beforeChange = this._beforeChange.bind(this); 1635 this._blur = this._blur.bind(this); 1636 this._codeMirror.on("scroll", this._onScroll); 1637 this._codeMirror.on("cursorActivity", this._onCursorActivity); 1638 this._codeMirror.on("changes", this._changes); 1639 this._codeMirror.on("beforeChange", this._beforeChange); 1640 this._codeMirror.on("blur", this._blur); 1641 1642 this._additionalWordChars = WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars; 1643 this._enabled = true; 1644 1645 this._dictionary = dictionary; 1646 this._addTextToCompletionDictionary(this._textEditor.text()); 1647} 1648 1649WebInspector.CodeMirrorTextEditor.AutocompleteController.Dummy = new WebInspector.CodeMirrorTextEditor.DummyAutocompleteController(); 1650WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars = {}; 1651WebInspector.CodeMirrorTextEditor._CSSAdditionalWordChars = { ".": true, "-": true }; 1652 1653WebInspector.CodeMirrorTextEditor.AutocompleteController.prototype = { 1654 dispose: function() 1655 { 1656 this._codeMirror.off("scroll", this._onScroll); 1657 this._codeMirror.off("cursorActivity", this._onCursorActivity); 1658 this._codeMirror.off("changes", this._changes); 1659 this._codeMirror.off("beforeChange", this._beforeChange); 1660 this._codeMirror.off("blur", this._blur); 1661 }, 1662 1663 /** 1664 * @param {boolean} enabled 1665 */ 1666 setEnabled: function(enabled) 1667 { 1668 if (enabled === this._enabled) 1669 return; 1670 this._enabled = enabled; 1671 if (!enabled) 1672 this._dictionary.reset(); 1673 else 1674 this._addTextToCompletionDictionary(this._textEditor.text()); 1675 }, 1676 1677 /** 1678 * @param {string} mimeType 1679 */ 1680 setMimeType: function(mimeType) 1681 { 1682 var additionalWordChars = mimeType.indexOf("css") !== -1 ? WebInspector.CodeMirrorTextEditor._CSSAdditionalWordChars : WebInspector.CodeMirrorTextEditor._NoAdditionalWordChars; 1683 if (additionalWordChars !== this._additionalWordChars) { 1684 this._additionalWordChars = additionalWordChars; 1685 this._dictionary.reset(); 1686 this._addTextToCompletionDictionary(this._textEditor.text()); 1687 } 1688 }, 1689 1690 /** 1691 * @param {string} char 1692 * @return {boolean} 1693 */ 1694 _isWordChar: function(char) 1695 { 1696 return WebInspector.TextUtils.isWordChar(char) || !!this._additionalWordChars[char]; 1697 }, 1698 1699 /** 1700 * @param {string} word 1701 * @return {boolean} 1702 */ 1703 _shouldProcessWordForAutocompletion: function(word) 1704 { 1705 return !!word.length && (word[0] < '0' || word[0] > '9'); 1706 }, 1707 1708 /** 1709 * @param {string} text 1710 */ 1711 _addTextToCompletionDictionary: function(text) 1712 { 1713 if (!this._enabled) 1714 return; 1715 var words = WebInspector.TextUtils.textToWords(text, this._isWordChar.bind(this)); 1716 for (var i = 0; i < words.length; ++i) { 1717 if (this._shouldProcessWordForAutocompletion(words[i])) 1718 this._dictionary.addWord(words[i]); 1719 } 1720 }, 1721 1722 /** 1723 * @param {string} text 1724 */ 1725 _removeTextFromCompletionDictionary: function(text) 1726 { 1727 if (!this._enabled) 1728 return; 1729 var words = WebInspector.TextUtils.textToWords(text, this._isWordChar.bind(this)); 1730 for (var i = 0; i < words.length; ++i) { 1731 if (this._shouldProcessWordForAutocompletion(words[i])) 1732 this._dictionary.removeWord(words[i]); 1733 } 1734 }, 1735 1736 /** 1737 * @param {!CodeMirror} codeMirror 1738 * @param {!WebInspector.CodeMirrorTextEditor.BeforeChangeObject} changeObject 1739 */ 1740 _beforeChange: function(codeMirror, changeObject) 1741 { 1742 if (!this._enabled) 1743 return; 1744 this._updatedLines = this._updatedLines || {}; 1745 for (var i = changeObject.from.line; i <= changeObject.to.line; ++i) 1746 this._updatedLines[i] = this._textEditor.line(i); 1747 }, 1748 1749 /** 1750 * @param {!CodeMirror} codeMirror 1751 * @param {!Array.<!WebInspector.CodeMirrorTextEditor.ChangeObject>} changes 1752 */ 1753 _changes: function(codeMirror, changes) 1754 { 1755 if (!changes.length || !this._enabled) 1756 return; 1757 1758 if (this._updatedLines) { 1759 for (var lineNumber in this._updatedLines) 1760 this._removeTextFromCompletionDictionary(this._updatedLines[lineNumber]); 1761 delete this._updatedLines; 1762 } 1763 1764 var linesToUpdate = {}; 1765 var singleCharInput = false; 1766 for (var changeIndex = 0; changeIndex < changes.length; ++changeIndex) { 1767 var changeObject = changes[changeIndex]; 1768 singleCharInput = (changeObject.origin === "+input" && changeObject.text.length === 1 && changeObject.text[0].length === 1) || 1769 (changeObject.origin === "+delete" && changeObject.removed.length === 1 && changeObject.removed[0].length === 1); 1770 1771 var editInfo = this._textEditor._changeObjectToEditOperation(changeObject); 1772 for (var i = editInfo.newRange.startLine; i <= editInfo.newRange.endLine; ++i) 1773 linesToUpdate[i] = this._textEditor.line(i); 1774 } 1775 for (var lineNumber in linesToUpdate) 1776 this._addTextToCompletionDictionary(linesToUpdate[lineNumber]); 1777 1778 if (singleCharInput) 1779 this.autocomplete(); 1780 }, 1781 1782 _blur: function() 1783 { 1784 this.finishAutocomplete(); 1785 }, 1786 1787 /** 1788 * @param {number} lineNumber 1789 * @param {number} columnNumber 1790 * @return {!WebInspector.TextRange} 1791 */ 1792 _autocompleteWordRange: function(lineNumber, columnNumber) 1793 { 1794 return this._textEditor._wordRangeForCursorPosition(lineNumber, columnNumber, this._isWordChar.bind(this)); 1795 }, 1796 1797 /** 1798 * @param {!WebInspector.TextRange} mainSelection 1799 * @param {!Array.<!{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}>} selections 1800 * @return {boolean} 1801 */ 1802 _validateSelectionsContexts: function(mainSelection, selections) 1803 { 1804 var mainSelectionContext = this._textEditor.copyRange(mainSelection); 1805 for (var i = 0; i < selections.length; ++i) { 1806 var wordRange = this._autocompleteWordRange(selections[i].head.line, selections[i].head.ch); 1807 if (!wordRange) 1808 return false; 1809 var context = this._textEditor.copyRange(wordRange); 1810 if (context !== mainSelectionContext) 1811 return false; 1812 } 1813 return true; 1814 }, 1815 1816 autocomplete: function() 1817 { 1818 var dictionary = this._dictionary; 1819 if (this._codeMirror.somethingSelected()) { 1820 this.finishAutocomplete(); 1821 return; 1822 } 1823 1824 var selections = this._codeMirror.listSelections().slice(); 1825 var topSelection = selections.shift(); 1826 var cursor = topSelection.head; 1827 var substituteRange = this._autocompleteWordRange(cursor.line, cursor.ch); 1828 if (!substituteRange || substituteRange.startColumn === cursor.ch || !this._validateSelectionsContexts(substituteRange, selections)) { 1829 this.finishAutocomplete(); 1830 return; 1831 } 1832 1833 var prefixRange = substituteRange.clone(); 1834 prefixRange.endColumn = cursor.ch; 1835 1836 var substituteWord = this._textEditor.copyRange(substituteRange); 1837 var hasPrefixInDictionary = dictionary.hasWord(substituteWord); 1838 if (hasPrefixInDictionary) 1839 dictionary.removeWord(substituteWord); 1840 var wordsWithPrefix = dictionary.wordsWithPrefix(this._textEditor.copyRange(prefixRange)); 1841 if (hasPrefixInDictionary) 1842 dictionary.addWord(substituteWord); 1843 1844 function sortSuggestions(a, b) 1845 { 1846 return dictionary.wordCount(b) - dictionary.wordCount(a) || a.length - b.length; 1847 } 1848 1849 wordsWithPrefix.sort(sortSuggestions); 1850 1851 if (!this._suggestBox) 1852 this._suggestBox = new WebInspector.SuggestBox(this, 6); 1853 var oldPrefixRange = this._prefixRange; 1854 this._prefixRange = prefixRange; 1855 if (!oldPrefixRange || prefixRange.startLine !== oldPrefixRange.startLine || prefixRange.startColumn !== oldPrefixRange.startColumn) 1856 this._updateAnchorBox(); 1857 this._suggestBox.updateSuggestions(this._anchorBox, wordsWithPrefix, 0, true, this._textEditor.copyRange(prefixRange)); 1858 if (!this._suggestBox.visible()) 1859 this.finishAutocomplete(); 1860 }, 1861 1862 finishAutocomplete: function() 1863 { 1864 if (!this._suggestBox) 1865 return; 1866 this._suggestBox.hide(); 1867 this._suggestBox = null; 1868 this._prefixRange = null; 1869 this._anchorBox = null; 1870 }, 1871 1872 /** 1873 * @param {?Event} e 1874 * @return {boolean} 1875 */ 1876 keyDown: function(e) 1877 { 1878 if (!this._suggestBox) 1879 return false; 1880 if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) { 1881 this.finishAutocomplete(); 1882 return true; 1883 } 1884 if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Tab.code) { 1885 this._suggestBox.acceptSuggestion(); 1886 this.finishAutocomplete(); 1887 return true; 1888 } 1889 return this._suggestBox.keyPressed(e); 1890 }, 1891 1892 /** 1893 * @param {string} suggestion 1894 * @param {boolean=} isIntermediateSuggestion 1895 */ 1896 applySuggestion: function(suggestion, isIntermediateSuggestion) 1897 { 1898 this._currentSuggestion = suggestion; 1899 }, 1900 1901 acceptSuggestion: function() 1902 { 1903 if (this._prefixRange.endColumn - this._prefixRange.startColumn === this._currentSuggestion.length) 1904 return; 1905 1906 var selections = this._codeMirror.listSelections().slice(); 1907 var prefixLength = this._prefixRange.endColumn - this._prefixRange.startColumn; 1908 for (var i = selections.length - 1; i >= 0; --i) { 1909 var start = selections[i].head; 1910 var end = new CodeMirror.Pos(start.line, start.ch - prefixLength); 1911 this._codeMirror.replaceRange(this._currentSuggestion, start, end, "+autocomplete"); 1912 } 1913 }, 1914 1915 _onScroll: function() 1916 { 1917 if (!this._suggestBox) 1918 return; 1919 var cursor = this._codeMirror.getCursor(); 1920 var scrollInfo = this._codeMirror.getScrollInfo(); 1921 var topmostLineNumber = this._codeMirror.lineAtHeight(scrollInfo.top, "local"); 1922 var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local"); 1923 if (cursor.line < topmostLineNumber || cursor.line > bottomLine) 1924 this.finishAutocomplete(); 1925 else { 1926 this._updateAnchorBox(); 1927 this._suggestBox.setPosition(this._anchorBox); 1928 } 1929 }, 1930 1931 _onCursorActivity: function() 1932 { 1933 if (!this._suggestBox) 1934 return; 1935 var cursor = this._codeMirror.getCursor(); 1936 if (cursor.line !== this._prefixRange.startLine || cursor.ch > this._prefixRange.endColumn || cursor.ch <= this._prefixRange.startColumn) 1937 this.finishAutocomplete(); 1938 }, 1939 1940 _updateAnchorBox: function() 1941 { 1942 var line = this._prefixRange.startLine; 1943 var column = this._prefixRange.startColumn; 1944 var metrics = this._textEditor.cursorPositionToCoordinates(line, column); 1945 this._anchorBox = metrics ? new AnchorBox(metrics.x, metrics.y, 0, metrics.height) : null; 1946 }, 1947} 1948 1949/** 1950 * @constructor 1951 * @param {!WebInspector.CodeMirrorTextEditor} textEditor 1952 * @param {!CodeMirror} codeMirror 1953 */ 1954WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController = function(textEditor, codeMirror) 1955{ 1956 this._textEditor = textEditor; 1957 this._codeMirror = codeMirror; 1958} 1959 1960WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController.prototype = { 1961 selectionWillChange: function() 1962 { 1963 if (!this._muteSelectionListener) 1964 delete this._fullWordSelection; 1965 }, 1966 1967 /** 1968 * @param {!Array.<!WebInspector.TextRange>} selections 1969 * @param {!WebInspector.TextRange} range 1970 * @return {boolean} 1971 */ 1972 _findRange: function(selections, range) 1973 { 1974 for (var i = 0; i < selections.length; ++i) { 1975 if (range.equal(selections[i])) 1976 return true; 1977 } 1978 return false; 1979 }, 1980 1981 undoLastSelection: function() 1982 { 1983 this._muteSelectionListener = true; 1984 this._codeMirror.execCommand("undoSelection"); 1985 this._muteSelectionListener = false; 1986 }, 1987 1988 selectNextOccurrence: function() 1989 { 1990 var selections = this._textEditor.selections(); 1991 var anyEmptySelection = false; 1992 for (var i = 0; i < selections.length; ++i) { 1993 var selection = selections[i]; 1994 anyEmptySelection = anyEmptySelection || selection.isEmpty(); 1995 if (selection.startLine !== selection.endLine) 1996 return; 1997 } 1998 if (anyEmptySelection) { 1999 this._expandSelectionsToWords(selections); 2000 return; 2001 } 2002 2003 var last = selections[selections.length - 1]; 2004 var next = last; 2005 do { 2006 next = this._findNextOccurrence(next, !!this._fullWordSelection); 2007 } while (next && this._findRange(selections, next) && !next.equal(last)); 2008 2009 if (!next) 2010 return; 2011 selections.push(next); 2012 2013 this._muteSelectionListener = true; 2014 this._textEditor.setSelections(selections, selections.length - 1); 2015 delete this._muteSelectionListener; 2016 2017 this._textEditor._revealLine(next.startLine); 2018 }, 2019 2020 /** 2021 * @param {!Array.<!WebInspector.TextRange>} selections 2022 */ 2023 _expandSelectionsToWords: function(selections) 2024 { 2025 var newSelections = []; 2026 for (var i = 0; i < selections.length; ++i) { 2027 var selection = selections[i]; 2028 var startRangeWord = this._textEditor._wordRangeForCursorPosition(selection.startLine, selection.startColumn, WebInspector.TextUtils.isWordChar) 2029 || WebInspector.TextRange.createFromLocation(selection.startLine, selection.startColumn); 2030 var endRangeWord = this._textEditor._wordRangeForCursorPosition(selection.endLine, selection.endColumn, WebInspector.TextUtils.isWordChar) 2031 || WebInspector.TextRange.createFromLocation(selection.endLine, selection.endColumn); 2032 var newSelection = new WebInspector.TextRange(startRangeWord.startLine, startRangeWord.startColumn, endRangeWord.endLine, endRangeWord.endColumn); 2033 newSelections.push(newSelection); 2034 } 2035 this._textEditor.setSelections(newSelections, newSelections.length - 1); 2036 this._fullWordSelection = true; 2037 }, 2038 2039 /** 2040 * @param {!WebInspector.TextRange} range 2041 * @param {boolean} fullWord 2042 * @return {?WebInspector.TextRange} 2043 */ 2044 _findNextOccurrence: function(range, fullWord) 2045 { 2046 range = range.normalize(); 2047 var matchedLineNumber; 2048 var matchedColumnNumber; 2049 var textToFind = this._textEditor.copyRange(range); 2050 function findWordInLine(wordRegex, lineNumber, lineText, from, to) 2051 { 2052 if (typeof matchedLineNumber === "number") 2053 return true; 2054 wordRegex.lastIndex = from; 2055 var result = wordRegex.exec(lineText); 2056 if (!result || result.index + textToFind.length > to) 2057 return false; 2058 matchedLineNumber = lineNumber; 2059 matchedColumnNumber = result.index; 2060 return true; 2061 } 2062 2063 var iteratedLineNumber; 2064 function lineIterator(regex, lineHandle) 2065 { 2066 if (findWordInLine(regex, iteratedLineNumber++, lineHandle.text, 0, lineHandle.text.length)) 2067 return true; 2068 } 2069 2070 var regexSource = textToFind.escapeForRegExp(); 2071 if (fullWord) 2072 regexSource = "\\b" + regexSource + "\\b"; 2073 var wordRegex = new RegExp(regexSource, "gi"); 2074 var currentLineText = this._codeMirror.getLine(range.startLine); 2075 2076 findWordInLine(wordRegex, range.startLine, currentLineText, range.endColumn, currentLineText.length); 2077 iteratedLineNumber = range.startLine + 1; 2078 this._codeMirror.eachLine(range.startLine + 1, this._codeMirror.lineCount(), lineIterator.bind(null, wordRegex)); 2079 iteratedLineNumber = 0; 2080 this._codeMirror.eachLine(0, range.startLine, lineIterator.bind(null, wordRegex)); 2081 findWordInLine(wordRegex, range.startLine, currentLineText, 0, range.startColumn); 2082 2083 if (typeof matchedLineNumber !== "number") 2084 return null; 2085 return new WebInspector.TextRange(matchedLineNumber, matchedColumnNumber, matchedLineNumber, matchedColumnNumber + textToFind.length); 2086 } 2087} 2088 2089/** 2090 * @param {string} modeName 2091 * @param {string} tokenPrefix 2092 */ 2093WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens = function(modeName, tokenPrefix) 2094{ 2095 var oldModeName = modeName + "-old"; 2096 if (CodeMirror.modes[oldModeName]) 2097 return; 2098 2099 CodeMirror.defineMode(oldModeName, CodeMirror.modes[modeName]); 2100 CodeMirror.defineMode(modeName, modeConstructor); 2101 2102 function modeConstructor(config, parserConfig) 2103 { 2104 var innerConfig = {}; 2105 for (var i in parserConfig) 2106 innerConfig[i] = parserConfig[i]; 2107 innerConfig.name = oldModeName; 2108 var codeMirrorMode = CodeMirror.getMode(config, innerConfig); 2109 codeMirrorMode.name = modeName; 2110 codeMirrorMode.token = tokenOverride.bind(null, codeMirrorMode.token); 2111 return codeMirrorMode; 2112 } 2113 2114 function tokenOverride(superToken, stream, state) 2115 { 2116 var token = superToken(stream, state); 2117 return token ? tokenPrefix + token : token; 2118 } 2119} 2120 2121WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("css", "css-"); 2122WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("javascript", "js-"); 2123WebInspector.CodeMirrorTextEditor._overrideModeWithPrefixedTokens("xml", "xml-"); 2124 2125(function() { 2126 var backgroundColor = InspectorFrontendHost.getSelectionBackgroundColor(); 2127 var backgroundColorRule = backgroundColor ? ".CodeMirror .CodeMirror-selected { background-color: " + backgroundColor + ";}" : ""; 2128 var foregroundColor = InspectorFrontendHost.getSelectionForegroundColor(); 2129 var foregroundColorRule = foregroundColor ? ".CodeMirror .CodeMirror-selectedtext:not(.CodeMirror-persist-highlight) { color: " + foregroundColor + "!important;}" : ""; 2130 if (!foregroundColorRule && !backgroundColorRule) 2131 return; 2132 2133 var style = document.createElement("style"); 2134 style.textContent = backgroundColorRule + foregroundColorRule; 2135 document.head.appendChild(style); 2136})(); 2137