1/* 2 * Copyright (C) 2011 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 31importScript("../cm/codemirror.js"); 32importScript("../cm/css.js"); 33importScript("../cm/javascript.js"); 34importScript("../cm/xml.js"); 35importScript("../cm/htmlmixed.js"); 36 37importScript("../cm/matchbrackets.js"); 38importScript("../cm/closebrackets.js"); 39importScript("../cm/markselection.js"); 40importScript("../cm/comment.js"); 41importScript("../cm/overlay.js"); 42 43importScript("../cm/htmlembedded.js"); 44importScript("../cm/clike.js"); 45importScript("../cm/coffeescript.js"); 46importScript("../cm/php.js"); 47importScript("../cm/python.js"); 48importScript("../cm/shell.js"); 49importScript("CodeMirrorUtils.js"); 50importScript("CodeMirrorTextEditor.js"); 51 52/** 53 * @extends {WebInspector.VBox} 54 * @constructor 55 * @implements {WebInspector.Replaceable} 56 * @param {!WebInspector.ContentProvider} contentProvider 57 */ 58WebInspector.SourceFrame = function(contentProvider) 59{ 60 WebInspector.VBox.call(this); 61 this.element.classList.add("script-view"); 62 63 this._url = contentProvider.contentURL(); 64 this._contentProvider = contentProvider; 65 66 var textEditorDelegate = new WebInspector.TextEditorDelegateForSourceFrame(this); 67 68 this._textEditor = new WebInspector.CodeMirrorTextEditor(this._url, textEditorDelegate); 69 70 this._currentSearchResultIndex = -1; 71 this._searchResults = []; 72 73 this._messages = []; 74 this._rowMessages = {}; 75 this._messageBubbles = {}; 76 77 this._textEditor.setReadOnly(!this.canEditSource()); 78 79 this._shortcuts = {}; 80 this.element.addEventListener("keydown", this._handleKeyDown.bind(this), false); 81 82 this._sourcePosition = new WebInspector.StatusBarText("", "source-frame-cursor-position"); 83} 84 85/** 86 * @param {string} query 87 * @param {string=} modifiers 88 * @return {!RegExp} 89 */ 90WebInspector.SourceFrame.createSearchRegex = function(query, modifiers) 91{ 92 var regex; 93 modifiers = modifiers || ""; 94 95 // First try creating regex if user knows the / / hint. 96 try { 97 if (/^\/.+\/$/.test(query)) { 98 regex = new RegExp(query.substring(1, query.length - 1), modifiers); 99 regex.__fromRegExpQuery = true; 100 } 101 } catch (e) { 102 // Silent catch. 103 } 104 105 // Otherwise just do case-insensitive search. 106 if (!regex) 107 regex = createPlainTextSearchRegex(query, "i" + modifiers); 108 109 return regex; 110} 111 112WebInspector.SourceFrame.Events = { 113 ScrollChanged: "ScrollChanged", 114 SelectionChanged: "SelectionChanged", 115 JumpHappened: "JumpHappened" 116} 117 118WebInspector.SourceFrame.prototype = { 119 /** 120 * @param {number} key 121 * @param {function()} handler 122 */ 123 addShortcut: function(key, handler) 124 { 125 this._shortcuts[key] = handler; 126 }, 127 128 wasShown: function() 129 { 130 this._ensureContentLoaded(); 131 this._textEditor.show(this.element); 132 this._editorAttached = true; 133 this._wasShownOrLoaded(); 134 }, 135 136 /** 137 * @return {boolean} 138 */ 139 _isEditorShowing: function() 140 { 141 return this.isShowing() && this._editorAttached; 142 }, 143 144 willHide: function() 145 { 146 WebInspector.View.prototype.willHide.call(this); 147 148 this._clearPositionToReveal(); 149 }, 150 151 /** 152 * @return {?Element} 153 */ 154 statusBarText: function() 155 { 156 return this._sourcePosition.element; 157 }, 158 159 /** 160 * @return {!Array.<!Element>} 161 */ 162 statusBarItems: function() 163 { 164 return []; 165 }, 166 167 /** 168 * @return {!Element} 169 */ 170 defaultFocusedElement: function() 171 { 172 return this._textEditor.defaultFocusedElement(); 173 }, 174 175 get loaded() 176 { 177 return this._loaded; 178 }, 179 180 /** 181 * @return {boolean} 182 */ 183 hasContent: function() 184 { 185 return true; 186 }, 187 188 get textEditor() 189 { 190 return this._textEditor; 191 }, 192 193 _ensureContentLoaded: function() 194 { 195 if (!this._contentRequested) { 196 this._contentRequested = true; 197 this._contentProvider.requestContent(this.setContent.bind(this)); 198 } 199 }, 200 201 addMessage: function(msg) 202 { 203 this._messages.push(msg); 204 if (this.loaded) 205 this.addMessageToSource(msg.line - 1, msg); 206 }, 207 208 clearMessages: function() 209 { 210 for (var line in this._messageBubbles) { 211 var bubble = this._messageBubbles[line]; 212 var lineNumber = parseInt(line, 10); 213 this._textEditor.removeDecoration(lineNumber, bubble); 214 } 215 216 this._messages = []; 217 this._rowMessages = {}; 218 this._messageBubbles = {}; 219 }, 220 221 /** 222 * @param {number} line 223 * @param {number=} column 224 * @param {boolean=} shouldHighlight 225 */ 226 revealPosition: function(line, column, shouldHighlight) 227 { 228 this._clearLineToScrollTo(); 229 this._clearSelectionToSet(); 230 this._positionToReveal = { line: line, column: column, shouldHighlight: shouldHighlight }; 231 this._innerRevealPositionIfNeeded(); 232 }, 233 234 _innerRevealPositionIfNeeded: function() 235 { 236 if (!this._positionToReveal) 237 return; 238 239 if (!this.loaded || !this._isEditorShowing()) 240 return; 241 242 this._textEditor.revealPosition(this._positionToReveal.line, this._positionToReveal.column, this._positionToReveal.shouldHighlight); 243 delete this._positionToReveal; 244 }, 245 246 _clearPositionToReveal: function() 247 { 248 this._textEditor.clearPositionHighlight(); 249 delete this._positionToReveal; 250 }, 251 252 /** 253 * @param {number} line 254 */ 255 scrollToLine: function(line) 256 { 257 this._clearPositionToReveal(); 258 this._lineToScrollTo = line; 259 this._innerScrollToLineIfNeeded(); 260 }, 261 262 _innerScrollToLineIfNeeded: function() 263 { 264 if (typeof this._lineToScrollTo === "number") { 265 if (this.loaded && this._isEditorShowing()) { 266 this._textEditor.scrollToLine(this._lineToScrollTo); 267 delete this._lineToScrollTo; 268 } 269 } 270 }, 271 272 _clearLineToScrollTo: function() 273 { 274 delete this._lineToScrollTo; 275 }, 276 277 /** 278 * @return {!WebInspector.TextRange} 279 */ 280 selection: function() 281 { 282 return this.textEditor.selection(); 283 }, 284 285 /** 286 * @param {!WebInspector.TextRange} textRange 287 */ 288 setSelection: function(textRange) 289 { 290 this._selectionToSet = textRange; 291 this._innerSetSelectionIfNeeded(); 292 }, 293 294 _innerSetSelectionIfNeeded: function() 295 { 296 if (this._selectionToSet && this.loaded && this._isEditorShowing()) { 297 this._textEditor.setSelection(this._selectionToSet); 298 delete this._selectionToSet; 299 } 300 }, 301 302 _clearSelectionToSet: function() 303 { 304 delete this._selectionToSet; 305 }, 306 307 _wasShownOrLoaded: function() 308 { 309 this._innerRevealPositionIfNeeded(); 310 this._innerSetSelectionIfNeeded(); 311 this._innerScrollToLineIfNeeded(); 312 }, 313 314 onTextChanged: function(oldRange, newRange) 315 { 316 if (this._searchResultsChangedCallback && !this._isReplacing) 317 this._searchResultsChangedCallback(); 318 this.clearMessages(); 319 }, 320 321 _simplifyMimeType: function(content, mimeType) 322 { 323 if (!mimeType) 324 return ""; 325 if (mimeType.indexOf("javascript") >= 0 || 326 mimeType.indexOf("jscript") >= 0 || 327 mimeType.indexOf("ecmascript") >= 0) 328 return "text/javascript"; 329 // A hack around the fact that files with "php" extension might be either standalone or html embedded php scripts. 330 if (mimeType === "text/x-php" && content.match(/\<\?.*\?\>/g)) 331 return "application/x-httpd-php"; 332 return mimeType; 333 }, 334 335 /** 336 * @param {string} highlighterType 337 */ 338 setHighlighterType: function(highlighterType) 339 { 340 this._highlighterType = highlighterType; 341 this._updateHighlighterType(""); 342 }, 343 344 /** 345 * @param {string} content 346 */ 347 _updateHighlighterType: function(content) 348 { 349 this._textEditor.setMimeType(this._simplifyMimeType(content, this._highlighterType)); 350 }, 351 352 /** 353 * @param {?string} content 354 */ 355 setContent: function(content) 356 { 357 if (!this._loaded) { 358 this._loaded = true; 359 this._textEditor.setText(content || ""); 360 this._textEditor.markClean(); 361 } else { 362 var firstLine = this._textEditor.firstVisibleLine(); 363 var selection = this._textEditor.selection(); 364 this._textEditor.setText(content || ""); 365 this._textEditor.scrollToLine(firstLine); 366 this._textEditor.setSelection(selection); 367 } 368 369 this._updateHighlighterType(content || ""); 370 371 this._textEditor.beginUpdates(); 372 373 this._setTextEditorDecorations(); 374 375 this._wasShownOrLoaded(); 376 377 if (this._delayedFindSearchMatches) { 378 this._delayedFindSearchMatches(); 379 delete this._delayedFindSearchMatches; 380 } 381 382 this.onTextEditorContentLoaded(); 383 384 this._textEditor.endUpdates(); 385 }, 386 387 onTextEditorContentLoaded: function() {}, 388 389 _setTextEditorDecorations: function() 390 { 391 this._rowMessages = {}; 392 this._messageBubbles = {}; 393 394 this._textEditor.beginUpdates(); 395 396 this._addExistingMessagesToSource(); 397 398 this._textEditor.endUpdates(); 399 }, 400 401 /** 402 * @param {string} query 403 * @param {boolean} shouldJump 404 * @param {boolean} jumpBackwards 405 * @param {function(!WebInspector.View, number)} callback 406 * @param {function(number)} currentMatchChangedCallback 407 * @param {function()} searchResultsChangedCallback 408 */ 409 performSearch: function(query, shouldJump, jumpBackwards, callback, currentMatchChangedCallback, searchResultsChangedCallback) 410 { 411 /** 412 * @param {string} query 413 * @this {WebInspector.SourceFrame} 414 */ 415 function doFindSearchMatches(query) 416 { 417 this._currentSearchResultIndex = -1; 418 this._searchResults = []; 419 420 var regex = WebInspector.SourceFrame.createSearchRegex(query); 421 this._searchRegex = regex; 422 this._searchResults = this._collectRegexMatches(regex); 423 if (!this._searchResults.length) 424 this._textEditor.cancelSearchResultsHighlight(); 425 else if (shouldJump && jumpBackwards) 426 this.jumpToPreviousSearchResult(); 427 else if (shouldJump) 428 this.jumpToNextSearchResult(); 429 else 430 this._textEditor.highlightSearchResults(regex, null); 431 callback(this, this._searchResults.length); 432 } 433 434 this._resetSearch(); 435 this._currentSearchMatchChangedCallback = currentMatchChangedCallback; 436 this._searchResultsChangedCallback = searchResultsChangedCallback; 437 if (this.loaded) 438 doFindSearchMatches.call(this, query); 439 else 440 this._delayedFindSearchMatches = doFindSearchMatches.bind(this, query); 441 442 this._ensureContentLoaded(); 443 }, 444 445 _editorFocused: function() 446 { 447 this._resetCurrentSearchResultIndex(); 448 }, 449 450 _resetCurrentSearchResultIndex: function() 451 { 452 if (!this._searchResults.length) 453 return; 454 this._currentSearchResultIndex = -1; 455 if (this._currentSearchMatchChangedCallback) 456 this._currentSearchMatchChangedCallback(this._currentSearchResultIndex); 457 this._textEditor.highlightSearchResults(this._searchRegex, null); 458 }, 459 460 _resetSearch: function() 461 { 462 delete this._delayedFindSearchMatches; 463 delete this._currentSearchMatchChangedCallback; 464 delete this._searchResultsChangedCallback; 465 this._currentSearchResultIndex = -1; 466 this._searchResults = []; 467 delete this._searchRegex; 468 }, 469 470 searchCanceled: function() 471 { 472 var range = this._currentSearchResultIndex !== -1 ? this._searchResults[this._currentSearchResultIndex] : null; 473 this._resetSearch(); 474 if (!this.loaded) 475 return; 476 this._textEditor.cancelSearchResultsHighlight(); 477 if (range) 478 this._textEditor.setSelection(range); 479 }, 480 481 /** 482 * @return {boolean} 483 */ 484 hasSearchResults: function() 485 { 486 return this._searchResults.length > 0; 487 }, 488 489 jumpToFirstSearchResult: function() 490 { 491 this.jumpToSearchResult(0); 492 }, 493 494 jumpToLastSearchResult: function() 495 { 496 this.jumpToSearchResult(this._searchResults.length - 1); 497 }, 498 499 /** 500 * @return {number} 501 */ 502 _searchResultIndexForCurrentSelection: function() 503 { 504 return insertionIndexForObjectInListSortedByFunction(this._textEditor.selection(), this._searchResults, WebInspector.TextRange.comparator); 505 }, 506 507 jumpToNextSearchResult: function() 508 { 509 var currentIndex = this._searchResultIndexForCurrentSelection(); 510 var nextIndex = this._currentSearchResultIndex === -1 ? currentIndex : currentIndex + 1; 511 this.jumpToSearchResult(nextIndex); 512 }, 513 514 jumpToPreviousSearchResult: function() 515 { 516 var currentIndex = this._searchResultIndexForCurrentSelection(); 517 this.jumpToSearchResult(currentIndex - 1); 518 }, 519 520 /** 521 * @return {boolean} 522 */ 523 showingFirstSearchResult: function() 524 { 525 return this._searchResults.length && this._currentSearchResultIndex === 0; 526 }, 527 528 /** 529 * @return {boolean} 530 */ 531 showingLastSearchResult: function() 532 { 533 return this._searchResults.length && this._currentSearchResultIndex === (this._searchResults.length - 1); 534 }, 535 536 get currentSearchResultIndex() 537 { 538 return this._currentSearchResultIndex; 539 }, 540 541 jumpToSearchResult: function(index) 542 { 543 if (!this.loaded || !this._searchResults.length) 544 return; 545 this._currentSearchResultIndex = (index + this._searchResults.length) % this._searchResults.length; 546 if (this._currentSearchMatchChangedCallback) 547 this._currentSearchMatchChangedCallback(this._currentSearchResultIndex); 548 this._textEditor.highlightSearchResults(this._searchRegex, this._searchResults[this._currentSearchResultIndex]); 549 }, 550 551 /** 552 * @param {string} text 553 */ 554 replaceSelectionWith: function(text) 555 { 556 var range = this._searchResults[this._currentSearchResultIndex]; 557 if (!range) 558 return; 559 this._textEditor.highlightSearchResults(this._searchRegex, null); 560 561 this._isReplacing = true; 562 var newRange = this._textEditor.editRange(range, text); 563 delete this._isReplacing; 564 565 this._textEditor.setSelection(newRange.collapseToEnd()); 566 }, 567 568 /** 569 * @param {string} query 570 * @param {string} replacement 571 */ 572 replaceAllWith: function(query, replacement) 573 { 574 this._resetCurrentSearchResultIndex(); 575 576 var text = this._textEditor.text(); 577 var range = this._textEditor.range(); 578 var regex = WebInspector.SourceFrame.createSearchRegex(query, "g"); 579 if (regex.__fromRegExpQuery) 580 text = text.replace(regex, replacement); 581 else 582 text = text.replace(regex, function() { return replacement; }); 583 584 var ranges = this._collectRegexMatches(regex); 585 if (!ranges.length) 586 return; 587 588 // Calculate the position of the end of the last range to be edited. 589 var currentRangeIndex = insertionIndexForObjectInListSortedByFunction(this._textEditor.selection(), ranges, WebInspector.TextRange.comparator); 590 var lastRangeIndex = mod(currentRangeIndex - 1, ranges.length); 591 var lastRange = ranges[lastRangeIndex]; 592 var replacementLineEndings = replacement.lineEndings(); 593 var replacementLineCount = replacementLineEndings.length; 594 var lastLineNumber = lastRange.startLine + replacementLineEndings.length - 1; 595 var lastColumnNumber = lastRange.startColumn; 596 if (replacementLineEndings.length > 1) 597 lastColumnNumber = replacementLineEndings[replacementLineCount - 1] - replacementLineEndings[replacementLineCount - 2] - 1; 598 599 this._isReplacing = true; 600 this._textEditor.editRange(range, text); 601 this._textEditor.revealPosition(lastLineNumber, lastColumnNumber); 602 this._textEditor.setSelection(WebInspector.TextRange.createFromLocation(lastLineNumber, lastColumnNumber)); 603 delete this._isReplacing; 604 }, 605 606 _collectRegexMatches: function(regexObject) 607 { 608 var ranges = []; 609 for (var i = 0; i < this._textEditor.linesCount; ++i) { 610 var line = this._textEditor.line(i); 611 var offset = 0; 612 do { 613 var match = regexObject.exec(line); 614 if (match) { 615 if (match[0].length) 616 ranges.push(new WebInspector.TextRange(i, offset + match.index, i, offset + match.index + match[0].length)); 617 offset += match.index + 1; 618 line = line.substring(match.index + 1); 619 } 620 } while (match && line); 621 } 622 return ranges; 623 }, 624 625 _addExistingMessagesToSource: function() 626 { 627 var length = this._messages.length; 628 for (var i = 0; i < length; ++i) 629 this.addMessageToSource(this._messages[i].line - 1, this._messages[i]); 630 }, 631 632 /** 633 * @param {number} lineNumber 634 * @param {!WebInspector.ConsoleMessage} msg 635 */ 636 addMessageToSource: function(lineNumber, msg) 637 { 638 if (lineNumber >= this._textEditor.linesCount) 639 lineNumber = this._textEditor.linesCount - 1; 640 if (lineNumber < 0) 641 lineNumber = 0; 642 643 var rowMessages = this._rowMessages[lineNumber]; 644 if (!rowMessages) { 645 rowMessages = []; 646 this._rowMessages[lineNumber] = rowMessages; 647 } 648 649 for (var i = 0; i < rowMessages.length; ++i) { 650 if (rowMessages[i].consoleMessage.isEqual(msg)) { 651 rowMessages[i].repeatCount++; 652 this._updateMessageRepeatCount(rowMessages[i]); 653 return; 654 } 655 } 656 657 var rowMessage = { consoleMessage: msg }; 658 rowMessages.push(rowMessage); 659 660 this._textEditor.beginUpdates(); 661 var messageBubbleElement = this._messageBubbles[lineNumber]; 662 if (!messageBubbleElement) { 663 messageBubbleElement = document.createElement("div"); 664 messageBubbleElement.className = "webkit-html-message-bubble"; 665 this._messageBubbles[lineNumber] = messageBubbleElement; 666 this._textEditor.addDecoration(lineNumber, messageBubbleElement); 667 } 668 669 var imageElement = document.createElement("div"); 670 switch (msg.level) { 671 case WebInspector.ConsoleMessage.MessageLevel.Error: 672 messageBubbleElement.classList.add("webkit-html-error-message"); 673 imageElement.className = "error-icon-small"; 674 break; 675 case WebInspector.ConsoleMessage.MessageLevel.Warning: 676 messageBubbleElement.classList.add("webkit-html-warning-message"); 677 imageElement.className = "warning-icon-small"; 678 break; 679 } 680 681 var messageLineElement = document.createElement("div"); 682 messageLineElement.className = "webkit-html-message-line"; 683 messageBubbleElement.appendChild(messageLineElement); 684 685 // Create the image element in the Inspector's document so we can use relative image URLs. 686 messageLineElement.appendChild(imageElement); 687 messageLineElement.appendChild(document.createTextNode(msg.messageText)); 688 689 rowMessage.element = messageLineElement; 690 rowMessage.repeatCount = 1; 691 this._updateMessageRepeatCount(rowMessage); 692 this._textEditor.endUpdates(); 693 }, 694 695 _updateMessageRepeatCount: function(rowMessage) 696 { 697 if (rowMessage.repeatCount < 2) 698 return; 699 700 if (!rowMessage.repeatCountElement) { 701 var repeatCountElement = document.createElement("span"); 702 rowMessage.element.appendChild(repeatCountElement); 703 rowMessage.repeatCountElement = repeatCountElement; 704 } 705 706 rowMessage.repeatCountElement.textContent = WebInspector.UIString(" (repeated %d times)", rowMessage.repeatCount); 707 }, 708 709 /** 710 * @param {number} lineNumber 711 * @param {!WebInspector.ConsoleMessage} msg 712 */ 713 removeMessageFromSource: function(lineNumber, msg) 714 { 715 if (lineNumber >= this._textEditor.linesCount) 716 lineNumber = this._textEditor.linesCount - 1; 717 if (lineNumber < 0) 718 lineNumber = 0; 719 720 var rowMessages = this._rowMessages[lineNumber]; 721 for (var i = 0; rowMessages && i < rowMessages.length; ++i) { 722 var rowMessage = rowMessages[i]; 723 if (rowMessage.consoleMessage !== msg) 724 continue; 725 726 var messageLineElement = rowMessage.element; 727 var messageBubbleElement = messageLineElement.parentElement; 728 messageBubbleElement.removeChild(messageLineElement); 729 rowMessages.remove(rowMessage); 730 if (!rowMessages.length) 731 delete this._rowMessages[lineNumber]; 732 if (!messageBubbleElement.childElementCount) { 733 this._textEditor.removeDecoration(lineNumber, messageBubbleElement); 734 delete this._messageBubbles[lineNumber]; 735 } 736 break; 737 } 738 }, 739 740 populateLineGutterContextMenu: function(contextMenu, lineNumber) 741 { 742 }, 743 744 populateTextAreaContextMenu: function(contextMenu, lineNumber) 745 { 746 }, 747 748 /** 749 * @param {?WebInspector.TextRange} from 750 * @param {?WebInspector.TextRange} to 751 */ 752 onJumpToPosition: function(from, to) 753 { 754 this.dispatchEventToListeners(WebInspector.SourceFrame.Events.JumpHappened, { 755 from: from, 756 to: to 757 }); 758 }, 759 760 inheritScrollPositions: function(sourceFrame) 761 { 762 this._textEditor.inheritScrollPositions(sourceFrame._textEditor); 763 }, 764 765 /** 766 * @return {boolean} 767 */ 768 canEditSource: function() 769 { 770 return false; 771 }, 772 773 /** 774 * @param {!WebInspector.TextRange} textRange 775 */ 776 selectionChanged: function(textRange) 777 { 778 this._updateSourcePosition(); 779 this.dispatchEventToListeners(WebInspector.SourceFrame.Events.SelectionChanged, textRange); 780 WebInspector.notifications.dispatchEventToListeners(WebInspector.SourceFrame.Events.SelectionChanged, textRange); 781 }, 782 783 _updateSourcePosition: function() 784 { 785 var selections = this._textEditor.selections(); 786 if (!selections.length) 787 return; 788 if (selections.length > 1) { 789 this._sourcePosition.setText(WebInspector.UIString("%d selection regions", selections.length)); 790 return; 791 } 792 var textRange = selections[0]; 793 if (textRange.isEmpty()) { 794 this._sourcePosition.setText(WebInspector.UIString("Line %d, Column %d", textRange.endLine + 1, textRange.endColumn + 1)); 795 return; 796 } 797 textRange = textRange.normalize(); 798 799 var selectedText = this._textEditor.copyRange(textRange); 800 if (textRange.startLine === textRange.endLine) 801 this._sourcePosition.setText(WebInspector.UIString("%d characters selected", selectedText.length)); 802 else 803 this._sourcePosition.setText(WebInspector.UIString("%d lines, %d characters selected", textRange.endLine - textRange.startLine + 1, selectedText.length)); 804 }, 805 806 /** 807 * @param {number} lineNumber 808 */ 809 scrollChanged: function(lineNumber) 810 { 811 this.dispatchEventToListeners(WebInspector.SourceFrame.Events.ScrollChanged, lineNumber); 812 }, 813 814 _handleKeyDown: function(e) 815 { 816 var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEvent(e); 817 var handler = this._shortcuts[shortcutKey]; 818 if (handler && handler()) 819 e.consume(true); 820 }, 821 822 __proto__: WebInspector.VBox.prototype 823} 824 825 826/** 827 * @implements {WebInspector.TextEditorDelegate} 828 * @constructor 829 */ 830WebInspector.TextEditorDelegateForSourceFrame = function(sourceFrame) 831{ 832 this._sourceFrame = sourceFrame; 833} 834 835WebInspector.TextEditorDelegateForSourceFrame.prototype = { 836 onTextChanged: function(oldRange, newRange) 837 { 838 this._sourceFrame.onTextChanged(oldRange, newRange); 839 }, 840 841 /** 842 * @param {!WebInspector.TextRange} textRange 843 */ 844 selectionChanged: function(textRange) 845 { 846 this._sourceFrame.selectionChanged(textRange); 847 }, 848 849 /** 850 * @param {number} lineNumber 851 */ 852 scrollChanged: function(lineNumber) 853 { 854 this._sourceFrame.scrollChanged(lineNumber); 855 }, 856 857 editorFocused: function() 858 { 859 this._sourceFrame._editorFocused(); 860 }, 861 862 populateLineGutterContextMenu: function(contextMenu, lineNumber) 863 { 864 this._sourceFrame.populateLineGutterContextMenu(contextMenu, lineNumber); 865 }, 866 867 populateTextAreaContextMenu: function(contextMenu, lineNumber) 868 { 869 this._sourceFrame.populateTextAreaContextMenu(contextMenu, lineNumber); 870 }, 871 872 /** 873 * @param {string} hrefValue 874 * @param {boolean} isExternal 875 * @return {!Element} 876 */ 877 createLink: function(hrefValue, isExternal) 878 { 879 var targetLocation = WebInspector.ParsedURL.completeURL(this._sourceFrame._url, hrefValue); 880 return WebInspector.linkifyURLAsNode(targetLocation || hrefValue, hrefValue, undefined, isExternal); 881 }, 882 883 /** 884 * @param {?WebInspector.TextRange} from 885 * @param {?WebInspector.TextRange} to 886 */ 887 onJumpToPosition: function(from, to) 888 { 889 this._sourceFrame.onJumpToPosition(from, to); 890 } 891} 892 893importScript("GoToLineDialog.js"); 894importScript("ResourceView.js"); 895importScript("FontView.js"); 896importScript("ImageView.js"); 897