1/* 2 * Copyright (C) 2013 Apple 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 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26WebInspector.TextEditor = function(element, mimeType, delegate) 27{ 28 WebInspector.Object.call(this); 29 30 var text = (element ? element.textContent : ""); 31 this._element = element || document.createElement("div"); 32 this._element.classList.add(WebInspector.TextEditor.StyleClassName); 33 this._element.classList.add(WebInspector.SyntaxHighlightedStyleClassName); 34 35 this._codeMirror = CodeMirror(this.element, { 36 readOnly: true, 37 indentWithTabs: true, 38 indentUnit: 4, 39 lineNumbers: true, 40 lineWrapping: true, 41 matchBrackets: true, 42 autoCloseBrackets: true 43 }); 44 45 this._codeMirror.on("change", this._contentChanged.bind(this)); 46 this._codeMirror.on("gutterClick", this._gutterMouseDown.bind(this)); 47 this._codeMirror.on("gutterContextMenu", this._gutterContextMenu.bind(this)); 48 this._codeMirror.getScrollerElement().addEventListener("click", this._openClickedLinks.bind(this), true); 49 50 this._completionController = new WebInspector.CodeMirrorCompletionController(this._codeMirror, this); 51 this._tokenTrackingController = new WebInspector.CodeMirrorTokenTrackingController(this._codeMirror, this); 52 53 this._initialStringNotSet = true; 54 55 this.mimeType = mimeType; 56 57 this._breakpoints = {}; 58 this._executionLineNumber = NaN; 59 this._executionColumnNumber = NaN; 60 61 this._searchQuery = null; 62 this._searchResults = []; 63 this._currentSearchResultIndex = -1; 64 this._ignoreCodeMirrorContentDidChangeEvent = 0; 65 66 this._formatted = false; 67 this._formatterSourceMap = null; 68 69 this._delegate = delegate || null; 70}; 71 72WebInspector.Object.addConstructorFunctions(WebInspector.TextEditor); 73 74WebInspector.TextEditor.StyleClassName = "text-editor"; 75WebInspector.TextEditor.HighlightedStyleClassName = "highlighted"; 76WebInspector.TextEditor.SearchResultStyleClassName = "search-result"; 77WebInspector.TextEditor.HasBreakpointStyleClassName = "has-breakpoint"; 78WebInspector.TextEditor.BreakpointResolvedStyleClassName = "breakpoint-resolved"; 79WebInspector.TextEditor.BreakpointAutoContinueStyleClassName = "breakpoint-auto-continue"; 80WebInspector.TextEditor.BreakpointDisabledStyleClassName = "breakpoint-disabled"; 81WebInspector.TextEditor.MultipleBreakpointsStyleClassName = "multiple-breakpoints"; 82WebInspector.TextEditor.ExecutionLineStyleClassName = "execution-line"; 83WebInspector.TextEditor.BouncyHighlightStyleClassName = "bouncy-highlight"; 84WebInspector.TextEditor.NumberOfFindsPerSearchBatch = 10; 85WebInspector.TextEditor.HighlightAnimationDuration = 2000; 86 87WebInspector.TextEditor.Event = { 88 ExecutionLineNumberDidChange: "text-editor-execution-line-number-did-change", 89 NumberOfSearchResultsDidChange: "text-editor-number-of-search-results-did-change", 90 ContentDidChange: "text-editor-content-did-change", 91 FormattingDidChange: "text-editor-formatting-did-change" 92}; 93 94WebInspector.TextEditor.prototype = { 95 constructor: WebInspector.TextEditor, 96 97 // Public 98 99 get element() 100 { 101 return this._element; 102 }, 103 104 get string() 105 { 106 return this._codeMirror.getValue(); 107 }, 108 109 set string(newString) 110 { 111 function update() 112 { 113 this._codeMirror.setValue(newString); 114 115 if (this._initialStringNotSet) { 116 this._codeMirror.clearHistory(); 117 this._codeMirror.markClean(); 118 delete this._initialStringNotSet; 119 } 120 121 // Automatically format the content. 122 if (this._autoFormat) { 123 console.assert(!this.formatted); 124 this.formatted = true; 125 delete this._autoFormat; 126 } 127 128 // Update the execution line now that we might have content for that line. 129 this._updateExecutionLine(); 130 131 // Set the breakpoint styles now that we might have content for those lines. 132 for (var lineNumber in this._breakpoints) 133 this._setBreakpointStylesOnLine(lineNumber); 134 135 // Try revealing the pending line now that we might have content with enough lines. 136 this._revealPendingPositionIfPossible(); 137 } 138 139 this._ignoreCodeMirrorContentDidChangeEvent++; 140 this._codeMirror.operation(update.bind(this)); 141 this._ignoreCodeMirrorContentDidChangeEvent--; 142 console.assert(this._ignoreCodeMirrorContentDidChangeEvent >= 0); 143 }, 144 145 get readOnly() 146 { 147 return this._codeMirror.getOption("readOnly") || false; 148 }, 149 150 set readOnly(readOnly) 151 { 152 this._codeMirror.setOption("readOnly", readOnly); 153 }, 154 155 get formatted() 156 { 157 return this._formatted; 158 }, 159 160 set formatted(formatted) 161 { 162 if (this._formatted === formatted) 163 return; 164 165 console.assert(!formatted || this.canBeFormatted()); 166 if (formatted && !this.canBeFormatted()) 167 return; 168 169 this._ignoreCodeMirrorContentDidChangeEvent++; 170 this._prettyPrint(formatted); 171 this._ignoreCodeMirrorContentDidChangeEvent--; 172 console.assert(this._ignoreCodeMirrorContentDidChangeEvent >= 0); 173 174 this._formatted = formatted; 175 176 this.dispatchEventToListeners(WebInspector.TextEditor.Event.FormattingDidChange); 177 }, 178 179 set autoFormat(auto) 180 { 181 this._autoFormat = auto; 182 }, 183 184 hasFormatter: function() 185 { 186 const supportedModes = { 187 "javascript": true, 188 "css": true, 189 }; 190 191 var mode = this._codeMirror.getMode(); 192 return mode.name in supportedModes; 193 }, 194 195 canBeFormatted: function() 196 { 197 // Can be overriden by subclasses. 198 return this.hasFormatter(); 199 }, 200 201 get selectedTextRange() 202 { 203 var start = this._codeMirror.getCursor(true); 204 var end = this._codeMirror.getCursor(false); 205 return this._textRangeFromCodeMirrorPosition(start, end); 206 }, 207 208 set selectedTextRange(textRange) 209 { 210 var position = this._codeMirrorPositionFromTextRange(textRange); 211 this._codeMirror.setSelection(position.start, position.end); 212 }, 213 214 get mimeType() 215 { 216 return this._mimeType; 217 }, 218 219 set mimeType(newMIMEType) 220 { 221 newMIMEType = parseMIMEType(newMIMEType).type; 222 223 this._mimeType = newMIMEType; 224 this._codeMirror.setOption("mode", newMIMEType); 225 }, 226 227 get executionLineNumber() 228 { 229 return this._executionLineNumber; 230 }, 231 232 set executionLineNumber(lineNumber) 233 { 234 // Only return early if there isn't a line handle and that isn't changing. 235 if (!this._executionLineHandle && isNaN(lineNumber)) 236 return; 237 238 this._executionLineNumber = lineNumber; 239 this._updateExecutionLine(); 240 241 // Still dispatch the event even if the number didn't change. The execution state still 242 // could have changed (e.g. continuing in a loop with a breakpoint inside). 243 this.dispatchEventToListeners(WebInspector.TextEditor.Event.ExecutionLineNumberDidChange); 244 }, 245 246 get executionColumnNumber() 247 { 248 return this._executionColumnNumber; 249 }, 250 251 set executionColumnNumber(columnNumber) 252 { 253 this._executionColumnNumber = columnNumber; 254 }, 255 256 get formatterSourceMap() 257 { 258 return this._formatterSourceMap; 259 }, 260 261 get tokenTrackingController() 262 { 263 return this._tokenTrackingController; 264 }, 265 266 get delegate() 267 { 268 return this._delegate; 269 }, 270 271 set delegate(newDelegate) 272 { 273 this._delegate = newDelegate || null; 274 }, 275 276 get numberOfSearchResults() 277 { 278 return this._searchResults.length; 279 }, 280 281 get currentSearchQuery() 282 { 283 return this._searchQuery; 284 }, 285 286 set automaticallyRevealFirstSearchResult(reveal) 287 { 288 this._automaticallyRevealFirstSearchResult = reveal; 289 290 // If we haven't shown a search result yet, reveal one now. 291 if (this._automaticallyRevealFirstSearchResult && this._searchResults.length > 0) { 292 if (this._currentSearchResultIndex === -1) 293 this._revealFirstSearchResultAfterCursor(); 294 } 295 }, 296 297 performSearch: function(query) 298 { 299 if (this._searchQuery === query) 300 return; 301 302 this.searchCleared(); 303 304 this._searchQuery = query; 305 306 // Allow subclasses to handle the searching if they have a better way. 307 // If we are formatted, just use CodeMirror's search. 308 if (typeof this.customPerformSearch === "function" && !this.formatted) { 309 if (this.customPerformSearch(query)) 310 return; 311 } 312 313 // Go down the slow patch for all other text content. 314 var queryRegex = new RegExp(query.escapeForRegExp(), "gi"); 315 var searchCursor = this._codeMirror.getSearchCursor(queryRegex, {line: 0, ch: 0}, false); 316 var boundBatchSearch = batchSearch.bind(this); 317 var numberOfSearchResultsDidChangeTimeout = null; 318 319 function reportNumberOfSearchResultsDidChange() 320 { 321 if (numberOfSearchResultsDidChangeTimeout) { 322 clearTimeout(numberOfSearchResultsDidChangeTimeout); 323 numberOfSearchResultsDidChangeTimeout = null; 324 } 325 326 this.dispatchEventToListeners(WebInspector.TextEditor.Event.NumberOfSearchResultsDidChange); 327 } 328 329 function batchSearch() 330 { 331 // Bail if the query changed since we started. 332 if (this._searchQuery !== query) 333 return; 334 335 var newSearchResults = []; 336 var foundResult = false; 337 for (var i = 0; i < WebInspector.TextEditor.NumberOfFindsPerSearchBatch && (foundResult = searchCursor.findNext()); ++i) { 338 var textRange = this._textRangeFromCodeMirrorPosition(searchCursor.from(), searchCursor.to()); 339 newSearchResults.push(textRange); 340 } 341 342 this.addSearchResults(newSearchResults); 343 344 // Don't report immediately, coalesce updates so they come in no faster than half a second. 345 if (!numberOfSearchResultsDidChangeTimeout) 346 numberOfSearchResultsDidChangeTimeout = setTimeout(reportNumberOfSearchResultsDidChange.bind(this), 500); 347 348 if (foundResult) { 349 // More lines to search, set a timeout so we don't block the UI long. 350 setTimeout(boundBatchSearch, 50); 351 } else { 352 // Report immediately now that we are finished, canceling any pending update. 353 reportNumberOfSearchResultsDidChange.call(this); 354 } 355 } 356 357 // Start the search. 358 boundBatchSearch(); 359 }, 360 361 addSearchResults: function(textRanges) 362 { 363 console.assert(textRanges); 364 if (!textRanges || !textRanges.length) 365 return; 366 367 function markRanges() 368 { 369 for (var i = 0; i < textRanges.length; ++i) { 370 var position = this._codeMirrorPositionFromTextRange(textRanges[i]); 371 var mark = this._codeMirror.markText(position.start, position.end, {className: WebInspector.TextEditor.SearchResultStyleClassName}); 372 this._searchResults.push(mark); 373 } 374 375 // If we haven't shown a search result yet, reveal one now. 376 if (this._automaticallyRevealFirstSearchResult) { 377 if (this._currentSearchResultIndex === -1) 378 this._revealFirstSearchResultAfterCursor(); 379 } 380 } 381 382 this._codeMirror.operation(markRanges.bind(this)); 383 }, 384 385 searchCleared: function() 386 { 387 function clearResults() { 388 for (var i = 0; i < this._searchResults.length; ++i) 389 this._searchResults[i].clear(); 390 } 391 392 this._codeMirror.operation(clearResults.bind(this)); 393 394 this._searchQuery = null; 395 this._searchResults = []; 396 this._currentSearchResultIndex = -1; 397 }, 398 399 searchQueryWithSelection: function() 400 { 401 if (!this._codeMirror.somethingSelected()) 402 return null; 403 404 return this._codeMirror.getSelection(); 405 }, 406 407 revealPreviousSearchResult: function(changeFocus) 408 { 409 if (!this._searchResults.length) 410 return; 411 412 if (this._currentSearchResultIndex === -1 || this._cursorDoesNotMatchLastRevealedSearchResult()) { 413 this._revealFirstSearchResultBeforeCursor(changeFocus); 414 return; 415 } 416 417 if (this._currentSearchResultIndex > 0) 418 --this._currentSearchResultIndex; 419 else 420 this._currentSearchResultIndex = this._searchResults.length - 1; 421 422 this._revealSearchResult(this._searchResults[this._currentSearchResultIndex], changeFocus, -1); 423 }, 424 425 revealNextSearchResult: function(changeFocus) 426 { 427 if (!this._searchResults.length) 428 return; 429 430 if (this._currentSearchResultIndex === -1 || this._cursorDoesNotMatchLastRevealedSearchResult()) { 431 this._revealFirstSearchResultAfterCursor(changeFocus); 432 return; 433 } 434 435 if (this._currentSearchResultIndex + 1 < this._searchResults.length) 436 ++this._currentSearchResultIndex; 437 else 438 this._currentSearchResultIndex = 0; 439 440 this._revealSearchResult(this._searchResults[this._currentSearchResultIndex], changeFocus, 1); 441 }, 442 443 line: function(lineNumber) 444 { 445 return this._codeMirror.getLine(lineNumber); 446 }, 447 448 revealPosition: function(position, textRangeToSelect, forceUnformatted, noHighlight) 449 { 450 console.assert(position === undefined || position instanceof WebInspector.SourceCodePosition, "revealPosition called without a SourceCodePosition"); 451 if (!(position instanceof WebInspector.SourceCodePosition)) 452 return; 453 454 var lineHandle = this._codeMirror.getLineHandle(position.lineNumber); 455 if (!lineHandle || !this._visible || this._initialStringNotSet) { 456 // If we can't get a line handle or are not visible then we wait to do the reveal. 457 this._positionToReveal = position; 458 this._textRangeToSelect = textRangeToSelect; 459 this._forceUnformatted = forceUnformatted; 460 return; 461 } 462 463 // Delete now that the reveal is happening. 464 delete this._positionToReveal; 465 delete this._textRangeToSelect; 466 delete this._forceUnformatted; 467 468 // If we need to unformat, reveal the line after a wait. 469 // Otherwise the line highlight doesn't work properly. 470 if (this._formatted && forceUnformatted) { 471 this.formatted = false; 472 setTimeout(this.revealPosition.bind(this), 0, position, textRangeToSelect); 473 return; 474 } 475 476 if (!textRangeToSelect) 477 textRangeToSelect = new WebInspector.TextRange(position.lineNumber, position.columnNumber, position.lineNumber, position.columnNumber); 478 479 function removeStyleClass() 480 { 481 this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.HighlightedStyleClassName); 482 } 483 484 function revealAndHighlightLine() 485 { 486 // If the line is not visible, reveal it as the center line in the editor. 487 var position = this._codeMirrorPositionFromTextRange(textRangeToSelect); 488 if (!this._isPositionVisible(position.start)) 489 this._scrollIntoViewCentered(position.start); 490 491 this.selectedTextRange = textRangeToSelect; 492 493 if (noHighlight) 494 return; 495 496 this._codeMirror.addLineClass(lineHandle, "wrap", WebInspector.TextEditor.HighlightedStyleClassName); 497 498 // Use a timeout instead of a webkitAnimationEnd event listener because the line element might 499 // be removed if the user scrolls during the animation. In that case webkitAnimationEnd isn't 500 // fired, and the line would highlight again the next time it scrolls into view. 501 setTimeout(removeStyleClass.bind(this), WebInspector.TextEditor.HighlightAnimationDuration); 502 } 503 504 this._codeMirror.operation(revealAndHighlightLine.bind(this)); 505 }, 506 507 updateLayout: function(force) 508 { 509 this._codeMirror.refresh(); 510 }, 511 512 shown: function() 513 { 514 this._visible = true; 515 516 // Refresh since our size might have changed. 517 this._codeMirror.refresh(); 518 519 // Try revealing the pending line now that we are visible. 520 // This needs to be done as a separate operation from the refresh 521 // so that the scrollInfo coordinates are correct. 522 this._revealPendingPositionIfPossible(); 523 }, 524 525 hidden: function() 526 { 527 this._visible = false; 528 }, 529 530 setBreakpointInfoForLineAndColumn: function(lineNumber, columnNumber, breakpointInfo) 531 { 532 if (this._ignoreSetBreakpointInfoCalls) 533 return; 534 535 if (breakpointInfo) 536 this._addBreakpointToLineAndColumnWithInfo(lineNumber, columnNumber, breakpointInfo); 537 else 538 this._removeBreakpointFromLineAndColumn(lineNumber, columnNumber); 539 }, 540 541 updateBreakpointLineAndColumn: function(oldLineNumber, oldColumnNumber, newLineNumber, newColumnNumber) 542 { 543 console.assert(this._breakpoints[oldLineNumber]); 544 if (!this._breakpoints[oldLineNumber]) 545 return; 546 547 console.assert(this._breakpoints[oldLineNumber][oldColumnNumber]); 548 if (!this._breakpoints[oldLineNumber][oldColumnNumber]) 549 return; 550 551 var breakpointInfo = this._breakpoints[oldLineNumber][oldColumnNumber]; 552 this._removeBreakpointFromLineAndColumn(oldLineNumber, oldColumnNumber); 553 this._addBreakpointToLineAndColumnWithInfo(newLineNumber, newColumnNumber, breakpointInfo); 554 }, 555 556 addStyleClassToLine: function(lineNumber, styleClassName) 557 { 558 var lineHandle = this._codeMirror.getLineHandle(lineNumber); 559 console.assert(lineHandle); 560 if (!lineHandle) 561 return; 562 563 return this._codeMirror.addLineClass(lineHandle, "wrap", styleClassName); 564 }, 565 566 removeStyleClassFromLine: function(lineNumber, styleClassName) 567 { 568 var lineHandle = this._codeMirror.getLineHandle(lineNumber); 569 console.assert(lineHandle); 570 if (!lineHandle) 571 return; 572 573 return this._codeMirror.removeLineClass(lineHandle, "wrap", styleClassName); 574 }, 575 576 toggleStyleClassForLine: function(lineNumber, styleClassName) 577 { 578 var lineHandle = this._codeMirror.getLineHandle(lineNumber); 579 console.assert(lineHandle); 580 if (!lineHandle) 581 return; 582 583 return this._codeMirror.toggleLineClass(lineHandle, "wrap", styleClassName); 584 }, 585 586 get lineCount() 587 { 588 return this._codeMirror.lineCount(); 589 }, 590 591 focus: function() 592 { 593 this._codeMirror.focus(); 594 }, 595 596 contentDidChange: function(replacedRanges, newRanges) 597 { 598 // Implemented by subclasses. 599 }, 600 601 rectsForRange: function(range) 602 { 603 return this._codeMirror.rectsForRange(range); 604 }, 605 606 get markers() 607 { 608 return this._codeMirror.getAllMarks().map(function(codeMirrorTextMarker) { 609 return WebInspector.TextMarker.textMarkerForCodeMirrorTextMarker(codeMirrorTextMarker); 610 }); 611 }, 612 613 markersAtPosition: function(position) 614 { 615 return this._codeMirror.findMarksAt(position).map(function(codeMirrorTextMarker) { 616 return WebInspector.TextMarker.textMarkerForCodeMirrorTextMarker(codeMirrorTextMarker); 617 }); 618 }, 619 620 createColorMarkers: function(range) 621 { 622 return this._codeMirror.createColorMarkers(range); 623 }, 624 625 createGradientMarkers: function(range) 626 { 627 return this._codeMirror.createGradientMarkers(range); 628 }, 629 630 editingControllerForMarker: function(editableMarker) 631 { 632 switch (editableMarker.type) { 633 case WebInspector.TextMarker.Type.Color: 634 return new WebInspector.CodeMirrorColorEditingController(this._codeMirror, editableMarker); 635 case WebInspector.TextMarker.Type.Gradient: 636 return new WebInspector.CodeMirrorGradientEditingController(this._codeMirror, editableMarker); 637 default: 638 return new WebInspector.CodeMirrorEditingController(this._codeMirror, editableMarker); 639 } 640 }, 641 642 // Private 643 644 _contentChanged: function(codeMirror, change) 645 { 646 if (this._ignoreCodeMirrorContentDidChangeEvent > 0) 647 return; 648 649 var replacedRanges = []; 650 var newRanges = []; 651 while (change) { 652 replacedRanges.push(new WebInspector.TextRange( 653 change.from.line, 654 change.from.ch, 655 change.to.line, 656 change.to.ch 657 )); 658 newRanges.push(new WebInspector.TextRange( 659 change.from.line, 660 change.from.ch, 661 change.from.line + change.text.length - 1, 662 change.text.length === 1 ? change.from.ch + change.text[0].length : change.text.lastValue.length 663 )); 664 change = change.next; 665 } 666 this.contentDidChange(replacedRanges, newRanges); 667 668 if (this._formatted) { 669 this._formatterSourceMap = null; 670 this._formatted = false; 671 672 if (this._delegate && typeof this._delegate.textEditorUpdatedFormatting === "function") 673 this._delegate.textEditorUpdatedFormatting(this); 674 675 this.dispatchEventToListeners(WebInspector.TextEditor.Event.FormattingDidChange); 676 } 677 678 this.dispatchEventToListeners(WebInspector.TextEditor.Event.ContentDidChange); 679 }, 680 681 _textRangeFromCodeMirrorPosition: function(start, end) 682 { 683 console.assert(start); 684 console.assert(end); 685 686 return new WebInspector.TextRange(start.line, start.ch, end.line, end.ch); 687 }, 688 689 _codeMirrorPositionFromTextRange: function(textRange) 690 { 691 console.assert(textRange); 692 693 var start = {line: textRange.startLine, ch: textRange.startColumn}; 694 var end = {line: textRange.endLine, ch: textRange.endColumn}; 695 return {start: start, end: end}; 696 }, 697 698 _revealPendingPositionIfPossible: function() 699 { 700 // Nothing to do if we don't have a pending position. 701 if (!this._positionToReveal) 702 return; 703 704 // Don't try to reveal unless we are visible. 705 if (!this._visible) 706 return; 707 708 this.revealPosition(this._positionToReveal, this._textRangeToSelect, this._forceUnformatted); 709 }, 710 711 _revealSearchResult: function(result, changeFocus, directionInCaseOfRevalidation) 712 { 713 var position = result.find(); 714 715 // Check for a valid position, it might have been removed from editing by the user. 716 // If the position is invalide, revalidate all positions reveal as needed. 717 if (!position) { 718 this._revalidateSearchResults(directionInCaseOfRevalidation); 719 return; 720 } 721 722 // If the line is not visible, reveal it as the center line in the editor. 723 if (!this._isPositionVisible(position.from)) 724 this._scrollIntoViewCentered(position.from); 725 726 // Update the text selection to select the search result. 727 this.selectedTextRange = this._textRangeFromCodeMirrorPosition(position.from, position.to); 728 729 // Remove the automatically reveal state now that we have revealed a search result. 730 this._automaticallyRevealFirstSearchResult = false; 731 732 // Focus the editor if requested. 733 if (changeFocus) 734 this._codeMirror.focus(); 735 736 // Remove the bouncy highlight if it is still around. The animation will not 737 // start unless we remove it and add it back to the document. 738 if (this._bouncyHighlightElement) 739 this._bouncyHighlightElement.remove(); 740 741 // Create the bouncy highlight. 742 this._bouncyHighlightElement = document.createElement("div"); 743 this._bouncyHighlightElement.className = WebInspector.TextEditor.BouncyHighlightStyleClassName; 744 745 // Collect info for the bouncy highlight. 746 var textContent = this._codeMirror.getSelection(); 747 var coordinates = this._codeMirror.cursorCoords(true, "page"); 748 749 // Adjust the coordinates to be based in the text editor's space. 750 var textEditorRect = this._element.getBoundingClientRect(); 751 coordinates.top -= textEditorRect.top; 752 coordinates.left -= textEditorRect.left; 753 754 // Position and show the bouncy highlight. 755 this._bouncyHighlightElement.textContent = textContent; 756 this._bouncyHighlightElement.style.top = coordinates.top + "px"; 757 this._bouncyHighlightElement.style.left = coordinates.left + "px"; 758 this._element.appendChild(this._bouncyHighlightElement); 759 760 function animationEnded() 761 { 762 if (!this._bouncyHighlightElement) 763 return; 764 765 this._bouncyHighlightElement.remove(); 766 delete this._bouncyHighlightElement; 767 } 768 769 // Listen for the end of the animation so we can remove the element. 770 this._bouncyHighlightElement.addEventListener("webkitAnimationEnd", animationEnded.bind(this)); 771 }, 772 773 _binarySearchInsertionIndexInSearchResults: function(object, comparator) 774 { 775 // It is possible that markers in the search results array may have been deleted. 776 // In those cases the comparator will return "null" and we immediately stop 777 // the binary search and return null. The search results list needs to be updated. 778 var array = this._searchResults; 779 780 var first = 0; 781 var last = array.length - 1; 782 783 while (first <= last) { 784 var mid = (first + last) >> 1; 785 var c = comparator(object, array[mid]); 786 if (c === null) 787 return null; 788 if (c > 0) 789 first = mid + 1; 790 else if (c < 0) 791 last = mid - 1; 792 else 793 return mid; 794 } 795 796 return first - 1; 797 }, 798 799 _revealFirstSearchResultBeforeCursor: function(changeFocus) 800 { 801 console.assert(this._searchResults.length); 802 803 var currentCursorPosition = this._codeMirror.getCursor("start"); 804 if (currentCursorPosition.line === 0 && currentCursorPosition.ch === 0) { 805 this._currentSearchResultIndex = this._searchResults.length - 1; 806 this._revealSearchResult(this._searchResults[this._currentSearchResultIndex], changeFocus, -1); 807 return; 808 } 809 810 var index = this._binarySearchInsertionIndexInSearchResults(currentCursorPosition, function(current, searchResult) { 811 var searchResultMarker = searchResult.find(); 812 if (!searchResultMarker) 813 return null; 814 return WebInspector.compareCodeMirrorPositions(current, searchResultMarker.from); 815 }); 816 817 if (index === null) { 818 this._revalidateSearchResults(-1); 819 return; 820 } 821 822 this._currentSearchResultIndex = index; 823 this._revealSearchResult(this._searchResults[this._currentSearchResultIndex], changeFocus); 824 }, 825 826 _revealFirstSearchResultAfterCursor: function(changeFocus) 827 { 828 console.assert(this._searchResults.length); 829 830 var currentCursorPosition = this._codeMirror.getCursor("start"); 831 if (currentCursorPosition.line === 0 && currentCursorPosition.ch === 0) { 832 this._currentSearchResultIndex = 0; 833 this._revealSearchResult(this._searchResults[this._currentSearchResultIndex], changeFocus, 1); 834 return; 835 } 836 837 var index = this._binarySearchInsertionIndexInSearchResults(currentCursorPosition, function(current, searchResult) { 838 var searchResultMarker = searchResult.find(); 839 if (!searchResultMarker) 840 return null; 841 return WebInspector.compareCodeMirrorPositions(current, searchResultMarker.from); 842 }); 843 844 if (index === null) { 845 this._revalidateSearchResults(1); 846 return; 847 } 848 849 if (index + 1 < this._searchResults.length) 850 ++index; 851 else 852 index = 0; 853 854 this._currentSearchResultIndex = index; 855 this._revealSearchResult(this._searchResults[this._currentSearchResultIndex], changeFocus); 856 }, 857 858 _cursorDoesNotMatchLastRevealedSearchResult: function() 859 { 860 console.assert(this._currentSearchResultIndex !== -1); 861 console.assert(this._searchResults.length); 862 863 var lastRevealedSearchResultMarker = this._searchResults[this._currentSearchResultIndex].find(); 864 if (!lastRevealedSearchResultMarker) 865 return true; 866 867 var currentCursorPosition = this._codeMirror.getCursor("start"); 868 var lastRevealedSearchResultPosition = lastRevealedSearchResultMarker.from; 869 870 return WebInspector.compareCodeMirrorPositions(currentCursorPosition, lastRevealedSearchResultPosition) !== 0; 871 }, 872 873 _revalidateSearchResults: function(direction) 874 { 875 console.assert(direction !== undefined); 876 877 this._currentSearchResultIndex = -1; 878 879 var updatedSearchResults = []; 880 for (var i = 0; i < this._searchResults.length; ++i) { 881 if (this._searchResults[i].find()) 882 updatedSearchResults.push(this._searchResults[i]); 883 } 884 885 console.assert(updatedSearchResults.length !== this._searchResults.length); 886 887 this._searchResults = updatedSearchResults; 888 this.dispatchEventToListeners(WebInspector.TextEditor.Event.NumberOfSearchResultsDidChange); 889 890 if (this._searchResults.length) { 891 if (direction > 0) 892 this._revealFirstSearchResultAfterCursor(); 893 else 894 this._revealFirstSearchResultBeforeCursor(); 895 } 896 }, 897 898 _updateExecutionLine: function() 899 { 900 function update() 901 { 902 if (this._executionLineHandle) 903 this._codeMirror.removeLineClass(this._executionLineHandle, "wrap", WebInspector.TextEditor.ExecutionLineStyleClassName); 904 905 this._executionLineHandle = !isNaN(this._executionLineNumber) ? this._codeMirror.getLineHandle(this._executionLineNumber) : null; 906 907 if (this._executionLineHandle) 908 this._codeMirror.addLineClass(this._executionLineHandle, "wrap", WebInspector.TextEditor.ExecutionLineStyleClassName); 909 } 910 911 this._codeMirror.operation(update.bind(this)); 912 }, 913 914 _setBreakpointStylesOnLine: function(lineNumber) 915 { 916 var columnBreakpoints = this._breakpoints[lineNumber]; 917 console.assert(columnBreakpoints); 918 if (!columnBreakpoints) 919 return; 920 921 var allDisabled = true; 922 var allResolved = true; 923 var allAutoContinue = true; 924 var multiple = Object.keys(columnBreakpoints).length > 1; 925 for (var columnNumber in columnBreakpoints) { 926 var breakpointInfo = columnBreakpoints[columnNumber]; 927 if (!breakpointInfo.disabled) 928 allDisabled = false; 929 if (!breakpointInfo.resolved) 930 allResolved = false; 931 if (!breakpointInfo.autoContinue) 932 allAutoContinue = false; 933 } 934 935 function updateStyles() 936 { 937 // We might not have a line if the content isn't fully populated yet. 938 // This will be called again when the content is available. 939 var lineHandle = this._codeMirror.getLineHandle(lineNumber); 940 if (!lineHandle) 941 return; 942 943 this._codeMirror.addLineClass(lineHandle, "wrap", WebInspector.TextEditor.HasBreakpointStyleClassName); 944 945 if (allResolved) 946 this._codeMirror.addLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointResolvedStyleClassName); 947 else 948 this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointResolvedStyleClassName); 949 950 if (allDisabled) 951 this._codeMirror.addLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointDisabledStyleClassName); 952 else 953 this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointDisabledStyleClassName); 954 955 if (allAutoContinue) 956 this._codeMirror.addLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointAutoContinueStyleClassName); 957 else 958 this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointAutoContinueStyleClassName); 959 960 if (multiple) 961 this._codeMirror.addLineClass(lineHandle, "wrap", WebInspector.TextEditor.MultipleBreakpointsStyleClassName); 962 else 963 this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.MultipleBreakpointsStyleClassName); 964 } 965 966 this._codeMirror.operation(updateStyles.bind(this)); 967 }, 968 969 _addBreakpointToLineAndColumnWithInfo: function(lineNumber, columnNumber, breakpointInfo) 970 { 971 if (!this._breakpoints[lineNumber]) 972 this._breakpoints[lineNumber] = {}; 973 this._breakpoints[lineNumber][columnNumber] = breakpointInfo; 974 975 this._setBreakpointStylesOnLine(lineNumber); 976 }, 977 978 _removeBreakpointFromLineAndColumn: function(lineNumber, columnNumber) 979 { 980 console.assert(columnNumber in this._breakpoints[lineNumber]); 981 delete this._breakpoints[lineNumber][columnNumber]; 982 983 // There are still breakpoints on the line. Update the breakpoint style. 984 if (!isEmptyObject(this._breakpoints[lineNumber])) { 985 this._setBreakpointStylesOnLine(lineNumber); 986 return; 987 } 988 989 delete this._breakpoints[lineNumber]; 990 991 function updateStyles() 992 { 993 var lineHandle = this._codeMirror.getLineHandle(lineNumber); 994 if (!lineHandle) 995 return; 996 997 this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.HasBreakpointStyleClassName); 998 this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointResolvedStyleClassName); 999 this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointDisabledStyleClassName); 1000 this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointAutoContinueStyleClassName); 1001 this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.MultipleBreakpointsStyleClassName); 1002 } 1003 1004 this._codeMirror.operation(updateStyles.bind(this)); 1005 }, 1006 1007 _allColumnBreakpointInfoForLine: function(lineNumber) 1008 { 1009 return this._breakpoints[lineNumber]; 1010 }, 1011 1012 _setColumnBreakpointInfoForLine: function(lineNumber, columnBreakpointInfo) 1013 { 1014 console.assert(columnBreakpointInfo); 1015 this._breakpoints[lineNumber] = columnBreakpointInfo; 1016 this._setBreakpointStylesOnLine(lineNumber); 1017 }, 1018 1019 _gutterMouseDown: function(codeMirror, lineNumber, gutterElement, event) 1020 { 1021 if (event.button !== 0 || event.ctrlKey) 1022 return; 1023 1024 if (!this._codeMirror.hasLineClass(lineNumber, "wrap", WebInspector.TextEditor.HasBreakpointStyleClassName)) { 1025 console.assert(!(lineNumber in this._breakpoints)); 1026 1027 // No breakpoint, add a new one. 1028 if (this._delegate && typeof this._delegate.textEditorBreakpointAdded === "function") { 1029 var data = this._delegate.textEditorBreakpointAdded(this, lineNumber, 0); 1030 if (data) { 1031 var breakpointInfo = data.breakpointInfo; 1032 if (breakpointInfo) 1033 this._addBreakpointToLineAndColumnWithInfo(data.lineNumber, data.columnNumber, breakpointInfo); 1034 } 1035 } 1036 1037 return; 1038 } 1039 1040 console.assert(lineNumber in this._breakpoints); 1041 1042 if (this._codeMirror.hasLineClass(lineNumber, "wrap", WebInspector.TextEditor.MultipleBreakpointsStyleClassName)) { 1043 console.assert(!isEmptyObject(this._breakpoints[lineNumber])); 1044 return; 1045 } 1046 1047 // Single existing breakpoint, start tracking it for dragging. 1048 console.assert(Object.keys(this._breakpoints[lineNumber]).length === 1); 1049 var columnNumber = Object.keys(this._breakpoints[lineNumber])[0]; 1050 this._draggingBreakpointInfo = this._breakpoints[lineNumber][columnNumber]; 1051 this._lineNumberWithMousedDownBreakpoint = lineNumber; 1052 this._lineNumberWithDraggedBreakpoint = lineNumber; 1053 this._columnNumberWithMousedDownBreakpoint = columnNumber; 1054 this._columnNumberWithDraggedBreakpoint = columnNumber; 1055 1056 this._documentMouseMovedEventListener = this._documentMouseMoved.bind(this); 1057 this._documentMouseUpEventListener = this._documentMouseUp.bind(this); 1058 1059 // Register these listeners on the document so we can track the mouse if it leaves the gutter. 1060 document.addEventListener("mousemove", this._documentMouseMovedEventListener, true); 1061 document.addEventListener("mouseup", this._documentMouseUpEventListener, true); 1062 }, 1063 1064 _gutterContextMenu: function(codeMirror, lineNumber, gutterElement, event) 1065 { 1066 if (this._delegate && typeof this._delegate.textEditorGutterContextMenu === "function") { 1067 var breakpoints = []; 1068 for (var columnNumber in this._breakpoints[lineNumber]) 1069 breakpoints.push({lineNumber:lineNumber, columnNumber:columnNumber}); 1070 1071 this._delegate.textEditorGutterContextMenu(this, lineNumber, 0, breakpoints, event); 1072 } 1073 }, 1074 1075 _documentMouseMoved: function(event) 1076 { 1077 console.assert("_lineNumberWithMousedDownBreakpoint" in this); 1078 if (!("_lineNumberWithMousedDownBreakpoint" in this)) 1079 return; 1080 1081 event.preventDefault(); 1082 1083 var lineNumber; 1084 var position = this._codeMirror.coordsChar({left: event.pageX, top: event.pageY}); 1085 1086 // CodeMirror's coordsChar returns a position even if it is outside the bounds. Nullify the position 1087 // if the event is outside the bounds of the gutter so we will remove the breakpoint. 1088 var gutterBounds = this._codeMirror.getGutterElement().getBoundingClientRect(); 1089 if (event.pageX < gutterBounds.left || event.pageX > gutterBounds.right || event.pageY < gutterBounds.top || event.pageY > gutterBounds.bottom) 1090 position = null; 1091 1092 // If we have a position and it has a line then use it. 1093 if (position && "line" in position) 1094 lineNumber = position.line; 1095 1096 // The _lineNumberWithDraggedBreakpoint property can be undefined if the user drags 1097 // outside of the gutter. The lineNumber variable can be undefined for the same reason. 1098 1099 if (lineNumber === this._lineNumberWithDraggedBreakpoint) 1100 return; 1101 1102 // Record that the mouse dragged some so when mouse up fires we know to do the 1103 // work of removing and moving the breakpoint. 1104 this._mouseDragged = true; 1105 1106 if ("_lineNumberWithDraggedBreakpoint" in this) { 1107 // We have a line that is currently showing the dragged breakpoint. Remove that breakpoint 1108 // and restore the previous one (if any.) 1109 if (this._previousColumnBreakpointInfo) 1110 this._setColumnBreakpointInfoForLine(this._lineNumberWithDraggedBreakpoint, this._previousColumnBreakpointInfo); 1111 else 1112 this._removeBreakpointFromLineAndColumn(this._lineNumberWithDraggedBreakpoint, this._columnNumberWithDraggedBreakpoint); 1113 1114 delete this._previousColumnBreakpointInfo; 1115 delete this._lineNumberWithDraggedBreakpoint; 1116 delete this._columnNumberWithDraggedBreakpoint; 1117 } 1118 1119 if (lineNumber !== undefined) { 1120 // We have a new line that will now show the dragged breakpoint. 1121 var newColumnBreakpoints = {}; 1122 var columnNumber = (lineNumber === this._lineNumberWithMousedDownBreakpoint ? this._columnNumberWithDraggedBreakpoint : 0); 1123 newColumnBreakpoints[columnNumber] = this._draggingBreakpointInfo; 1124 this._previousColumnBreakpointInfo = this._allColumnBreakpointInfoForLine(lineNumber); 1125 this._setColumnBreakpointInfoForLine(lineNumber, newColumnBreakpoints); 1126 this._lineNumberWithDraggedBreakpoint = lineNumber; 1127 this._columnNumberWithDraggedBreakpoint = columnNumber; 1128 } 1129 }, 1130 1131 _documentMouseUp: function(event) 1132 { 1133 console.assert("_lineNumberWithMousedDownBreakpoint" in this); 1134 if (!("_lineNumberWithMousedDownBreakpoint" in this)) 1135 return; 1136 1137 event.preventDefault(); 1138 1139 document.removeEventListener("mousemove", this._documentMouseMovedEventListener, true); 1140 document.removeEventListener("mouseup", this._documentMouseUpEventListener, true); 1141 1142 const delegateImplementsBreakpointClicked = this._delegate && typeof this._delegate.textEditorBreakpointClicked === "function"; 1143 const delegateImplementsBreakpointRemoved = this._delegate && typeof this._delegate.textEditorBreakpointRemoved === "function"; 1144 const delegateImplementsBreakpointMoved = this._delegate && typeof this._delegate.textEditorBreakpointMoved === "function"; 1145 1146 if (this._mouseDragged) { 1147 if (!("_lineNumberWithDraggedBreakpoint" in this)) { 1148 // The breakpoint was dragged off the gutter, remove it. 1149 if (delegateImplementsBreakpointRemoved) { 1150 this._ignoreSetBreakpointInfoCalls = true; 1151 this._delegate.textEditorBreakpointRemoved(this, this._lineNumberWithMousedDownBreakpoint, this._columnNumberWithMousedDownBreakpoint); 1152 delete this._ignoreSetBreakpointInfoCalls; 1153 } 1154 } else if (this._lineNumberWithMousedDownBreakpoint !== this._lineNumberWithDraggedBreakpoint) { 1155 // The dragged breakpoint was moved to a new line. 1156 1157 // If there is are breakpoints already at the drop line, tell the delegate to remove them. 1158 // We have already updated the breakpoint info internally, so when the delegate removes the breakpoints 1159 // and tells us to clear the breakpoint info, we can ignore those calls. 1160 if (this._previousColumnBreakpointInfo && delegateImplementsBreakpointRemoved) { 1161 this._ignoreSetBreakpointInfoCalls = true; 1162 for (var columnNumber in this._previousColumnBreakpointInfo) 1163 this._delegate.textEditorBreakpointRemoved(this, this._lineNumberWithDraggedBreakpoint, columnNumber); 1164 delete this._ignoreSetBreakpointInfoCalls; 1165 } 1166 1167 // Tell the delegate to move the breakpoint from one line to another. 1168 if (delegateImplementsBreakpointMoved) { 1169 this._ignoreSetBreakpointInfoCalls = true; 1170 this._delegate.textEditorBreakpointMoved(this, this._lineNumberWithMousedDownBreakpoint, this._columnNumberWithMousedDownBreakpoint, this._lineNumberWithDraggedBreakpoint, this._columnNumberWithDraggedBreakpoint); 1171 delete this._ignoreSetBreakpointInfoCalls; 1172 } 1173 } 1174 } else { 1175 // Toggle the disabled state of the breakpoint. 1176 console.assert(this._lineNumberWithMousedDownBreakpoint in this._breakpoints); 1177 console.assert(this._columnNumberWithMousedDownBreakpoint in this._breakpoints[this._lineNumberWithMousedDownBreakpoint]); 1178 if (this._lineNumberWithMousedDownBreakpoint in this._breakpoints && this._columnNumberWithMousedDownBreakpoint in this._breakpoints[this._lineNumberWithMousedDownBreakpoint] && delegateImplementsBreakpointClicked) 1179 this._delegate.textEditorBreakpointClicked(this, this._lineNumberWithMousedDownBreakpoint, this._columnNumberWithMousedDownBreakpoint); 1180 } 1181 1182 delete this._documentMouseMovedEventListener; 1183 delete this._documentMouseUpEventListener; 1184 delete this._lineNumberWithMousedDownBreakpoint; 1185 delete this._lineNumberWithDraggedBreakpoint; 1186 delete this._columnNumberWithMousedDownBreakpoint; 1187 delete this._columnNumberWithDraggedBreakpoint; 1188 delete this._previousColumnBreakpointInfo; 1189 delete this._mouseDragged; 1190 }, 1191 1192 _openClickedLinks: function(event) 1193 { 1194 // Get the position in the text and the token at that position. 1195 var position = this._codeMirror.coordsChar({left: event.pageX, top: event.pageY}); 1196 var tokenInfo = this._codeMirror.getTokenAt(position); 1197 if (!tokenInfo || !tokenInfo.type || !tokenInfo.string) 1198 return; 1199 1200 // If the token is not a link, then ignore it. 1201 if (!/\blink\b/.test(tokenInfo.type)) 1202 return; 1203 1204 // The token string is the URL we should open. It might be a relative URL. 1205 var url = tokenInfo.string; 1206 1207 // Get the base URL. 1208 var baseURL = ""; 1209 if (this._delegate && typeof this._delegate.textEditorBaseURL === "function") 1210 baseURL = this._delegate.textEditorBaseURL(this); 1211 1212 // Open the link after resolving the absolute URL from the base URL. 1213 WebInspector.openURL(absoluteURL(url, baseURL)); 1214 1215 // Stop processing the event. 1216 event.preventDefault(); 1217 event.stopPropagation(); 1218 }, 1219 1220 _isPositionVisible: function(position) 1221 { 1222 var scrollInfo = this._codeMirror.getScrollInfo(); 1223 var visibleRangeStart = scrollInfo.top; 1224 var visibleRangeEnd = visibleRangeStart + scrollInfo.clientHeight; 1225 var coords = this._codeMirror.charCoords(position, "local"); 1226 1227 return coords.top >= visibleRangeStart && coords.bottom <= visibleRangeEnd; 1228 }, 1229 1230 _scrollIntoViewCentered: function(position) 1231 { 1232 var scrollInfo = this._codeMirror.getScrollInfo(); 1233 var lineHeight = Math.ceil(this._codeMirror.defaultTextHeight()); 1234 var margin = Math.floor((scrollInfo.clientHeight - lineHeight) / 2); 1235 this._codeMirror.scrollIntoView(position, margin); 1236 }, 1237 1238 _prettyPrint: function(pretty) 1239 { 1240 function prettyPrintAndUpdateEditor() 1241 { 1242 const start = {line: 0, ch: 0}; 1243 const end = {line: this._codeMirror.lineCount() - 1}; 1244 1245 var oldSelectionAnchor = this._codeMirror.getCursor("anchor"); 1246 var oldSelectionHead = this._codeMirror.getCursor("head"); 1247 var newSelectionAnchor, newSelectionHead; 1248 var newExecutionLocation = null; 1249 1250 if (pretty) { 1251 // <rdar://problem/10593948> Provide a way to change the tab width in the Web Inspector 1252 const indentString = " "; 1253 var originalLineEndings = []; 1254 var formattedLineEndings = []; 1255 var mapping = {original: [0], formatted: [0]}; 1256 var builder = new FormatterContentBuilder(mapping, originalLineEndings, formattedLineEndings, 0, 0, indentString); 1257 var formatter = new Formatter(this._codeMirror, builder); 1258 formatter.format(start, end); 1259 1260 this._formatterSourceMap = WebInspector.FormatterSourceMap.fromBuilder(builder); 1261 1262 this._codeMirror.setValue(builder.formattedContent); 1263 1264 if (this._positionToReveal) { 1265 var newRevealPosition = this._formatterSourceMap.originalToFormatted(this._positionToReveal.lineNumber, this._positionToReveal.columnNumber); 1266 this._positionToReveal = new WebInspector.SourceCodePosition(newRevealPosition.lineNumber, newRevealPosition.columnNumber); 1267 } 1268 1269 if (this._textRangeToSelect) { 1270 var mappedRevealSelectionStart = this._formatterSourceMap.originalToFormatted(this._textRangeToSelect.startLine, this._textRangeToSelect.startColumn); 1271 var mappedRevealSelectionEnd = this._formatterSourceMap.originalToFormatted(this._textRangeToSelect.endLine, this._textRangeToSelect.endColumn); 1272 this._textRangeToSelect = new WebInspector.TextRange(mappedRevealSelectionStart.lineNumber, mappedRevealSelectionStart.columnNumber, mappedRevealSelectionEnd.lineNumber, mappedRevealSelectionEnd.columnNumber); 1273 } 1274 1275 if (!isNaN(this._executionLineNumber)) { 1276 console.assert(!isNaN(this._executionColumnNumber)); 1277 newExecutionLocation = this._formatterSourceMap.originalToFormatted(this._executionLineNumber, this._executionColumnNumber); 1278 } 1279 1280 var mappedAnchorLocation = this._formatterSourceMap.originalToFormatted(oldSelectionAnchor.line, oldSelectionAnchor.ch); 1281 var mappedHeadLocation = this._formatterSourceMap.originalToFormatted(oldSelectionHead.line, oldSelectionHead.ch); 1282 newSelectionAnchor = {line:mappedAnchorLocation.lineNumber, ch:mappedAnchorLocation.columnNumber}; 1283 newSelectionHead = {line:mappedHeadLocation.lineNumber, ch:mappedHeadLocation.columnNumber}; 1284 } else { 1285 this._codeMirror.undo(); 1286 1287 if (this._positionToReveal) { 1288 var newRevealPosition = this._formatterSourceMap.formattedToOriginal(this._positionToReveal.lineNumber, this._positionToReveal.columnNumber); 1289 this._positionToReveal = new WebInspector.SourceCodePosition(newRevealPosition.lineNumber, newRevealPosition.columnNumber); 1290 } 1291 1292 if (this._textRangeToSelect) { 1293 var mappedRevealSelectionStart = this._formatterSourceMap.formattedToOriginal(this._textRangeToSelect.startLine, this._textRangeToSelect.startColumn); 1294 var mappedRevealSelectionEnd = this._formatterSourceMap.formattedToOriginal(this._textRangeToSelect.endLine, this._textRangeToSelect.endColumn); 1295 this._textRangeToSelect = new WebInspector.TextRange(mappedRevealSelectionStart.lineNumber, mappedRevealSelectionStart.columnNumber, mappedRevealSelectionEnd.lineNumber, mappedRevealSelectionEnd.columnNumber); 1296 } 1297 1298 if (!isNaN(this._executionLineNumber)) { 1299 console.assert(!isNaN(this._executionColumnNumber)); 1300 newExecutionLocation = this._formatterSourceMap.formattedToOriginal(this._executionLineNumber, this._executionColumnNumber); 1301 } 1302 1303 var mappedAnchorLocation = this._formatterSourceMap.formattedToOriginal(oldSelectionAnchor.line, oldSelectionAnchor.ch); 1304 var mappedHeadLocation = this._formatterSourceMap.formattedToOriginal(oldSelectionHead.line, oldSelectionHead.ch); 1305 newSelectionAnchor = {line:mappedAnchorLocation.lineNumber, ch:mappedAnchorLocation.columnNumber}; 1306 newSelectionHead = {line:mappedHeadLocation.lineNumber, ch:mappedHeadLocation.columnNumber}; 1307 1308 this._formatterSourceMap = null; 1309 } 1310 1311 this._scrollIntoViewCentered(newSelectionAnchor); 1312 this._codeMirror.setSelection(newSelectionAnchor, newSelectionHead); 1313 1314 if (newExecutionLocation) { 1315 delete this._executionLineHandle; 1316 this.executionColumnNumber = newExecutionLocation.columnNumber; 1317 this.executionLineNumber = newExecutionLocation.lineNumber; 1318 } 1319 1320 // FIXME: <rdar://problem/13129955> FindBanner: New searches should not lose search position (start from current selection/caret) 1321 if (this.currentSearchQuery) { 1322 var searchQuery = this.currentSearchQuery; 1323 this.searchCleared(); 1324 // Set timeout so that this happens after the current CodeMirror operation. 1325 // The editor has to update for the value and selection changes. 1326 setTimeout(function(query) { 1327 this.performSearch(searchQuery); 1328 }.bind(this), 0); 1329 } 1330 1331 if (this._delegate && typeof this._delegate.textEditorUpdatedFormatting === "function") 1332 this._delegate.textEditorUpdatedFormatting(this); 1333 } 1334 1335 this._codeMirror.operation(prettyPrintAndUpdateEditor.bind(this)); 1336 } 1337}; 1338 1339WebInspector.TextEditor.prototype.__proto__ = WebInspector.Object.prototype; 1340