OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2011 Google Inc. All rights reserved. | 2 * Copyright (C) 2011 Google Inc. All rights reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
6 * met: | 6 * met: |
7 * | 7 * |
8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
11 * copyright notice, this list of conditions and the following disclaimer | 11 * copyright notice, this list of conditions and the following disclaimer |
12 * in the documentation and/or other materials provided with the | 12 * in the documentation and/or other materials provided with the |
13 * distribution. | 13 * distribution. |
14 * * Neither the name of Google Inc. nor the names of its | 14 * * Neither the name of Google Inc. nor the names of its |
15 * contributors may be used to endorse or promote products derived from | 15 * contributors may be used to endorse or promote products derived from |
16 * this software without specific prior written permission. | 16 * this software without specific prior written permission. |
17 * | 17 * |
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 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. | 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
29 */ | 29 */ |
30 | |
31 /** | 30 /** |
32 * @constructor | |
33 * @extends {WebInspector.SimpleView} | |
34 * @implements {WebInspector.Searchable} | 31 * @implements {WebInspector.Searchable} |
35 * @implements {WebInspector.Replaceable} | 32 * @implements {WebInspector.Replaceable} |
36 * @implements {WebInspector.SourcesTextEditorDelegate} | 33 * @implements {WebInspector.SourcesTextEditorDelegate} |
37 * @param {string} url | 34 * @unrestricted |
38 * @param {function(): !Promise<?string>} lazyContent | |
39 */ | 35 */ |
40 WebInspector.SourceFrame = function(url, lazyContent) | 36 WebInspector.SourceFrame = class extends WebInspector.SimpleView { |
41 { | 37 /** |
42 WebInspector.SimpleView.call(this, WebInspector.UIString("Source")); | 38 * @param {string} url |
| 39 * @param {function(): !Promise<?string>} lazyContent |
| 40 */ |
| 41 constructor(url, lazyContent) { |
| 42 super(WebInspector.UIString('Source')); |
43 | 43 |
44 this._url = url; | 44 this._url = url; |
45 this._lazyContent = lazyContent; | 45 this._lazyContent = lazyContent; |
46 | 46 |
47 this._textEditor = new WebInspector.SourcesTextEditor(this); | 47 this._textEditor = new WebInspector.SourcesTextEditor(this); |
48 | 48 |
49 this._currentSearchResultIndex = -1; | 49 this._currentSearchResultIndex = -1; |
50 this._searchResults = []; | 50 this._searchResults = []; |
51 | 51 |
52 this._textEditor.addEventListener(WebInspector.SourcesTextEditor.Events.Edit
orFocused, this._resetCurrentSearchResultIndex, this); | 52 this._textEditor.addEventListener( |
53 this._textEditor.addEventListener(WebInspector.SourcesTextEditor.Events.Sele
ctionChanged, this._updateSourcePosition, this); | 53 WebInspector.SourcesTextEditor.Events.EditorFocused, this._resetCurrentS
earchResultIndex, this); |
54 this._textEditor.addEventListener(WebInspector.SourcesTextEditor.Events.Text
Changed, event => this.onTextChanged(event.data.oldRange, event.data.newRange)); | 54 this._textEditor.addEventListener( |
| 55 WebInspector.SourcesTextEditor.Events.SelectionChanged, this._updateSour
cePosition, this); |
| 56 this._textEditor.addEventListener( |
| 57 WebInspector.SourcesTextEditor.Events.TextChanged, |
| 58 event => this.onTextChanged(event.data.oldRange, event.data.newRange)); |
55 | 59 |
56 this._shortcuts = {}; | 60 this._shortcuts = {}; |
57 this.element.addEventListener("keydown", this._handleKeyDown.bind(this), fal
se); | 61 this.element.addEventListener('keydown', this._handleKeyDown.bind(this), fal
se); |
58 | 62 |
59 this._sourcePosition = new WebInspector.ToolbarText(); | 63 this._sourcePosition = new WebInspector.ToolbarText(); |
60 | 64 |
61 /** | 65 /** |
62 * @type {?WebInspector.SearchableView} | 66 * @type {?WebInspector.SearchableView} |
63 */ | 67 */ |
64 this._searchableView = null; | 68 this._searchableView = null; |
65 this._editable = false; | 69 this._editable = false; |
| 70 } |
| 71 |
| 72 /** |
| 73 * @param {boolean} editable |
| 74 */ |
| 75 setEditable(editable) { |
| 76 this._editable = editable; |
| 77 this._textEditor.setReadOnly(editable); |
| 78 } |
| 79 |
| 80 /** |
| 81 * @param {number} key |
| 82 * @param {function():boolean} handler |
| 83 */ |
| 84 addShortcut(key, handler) { |
| 85 this._shortcuts[key] = handler; |
| 86 } |
| 87 |
| 88 /** |
| 89 * @override |
| 90 */ |
| 91 wasShown() { |
| 92 this._ensureContentLoaded(); |
| 93 this._textEditor.show(this.element); |
| 94 this._editorAttached = true; |
| 95 this._wasShownOrLoaded(); |
| 96 } |
| 97 |
| 98 /** |
| 99 * @return {boolean} |
| 100 */ |
| 101 isEditorShowing() { |
| 102 return this.isShowing() && this._editorAttached; |
| 103 } |
| 104 |
| 105 /** |
| 106 * @override |
| 107 */ |
| 108 willHide() { |
| 109 super.willHide(); |
| 110 |
| 111 this._clearPositionToReveal(); |
| 112 } |
| 113 |
| 114 /** |
| 115 * @override |
| 116 * @return {!Array<!WebInspector.ToolbarItem>} |
| 117 */ |
| 118 syncToolbarItems() { |
| 119 return [this._sourcePosition]; |
| 120 } |
| 121 |
| 122 get loaded() { |
| 123 return this._loaded; |
| 124 } |
| 125 |
| 126 get textEditor() { |
| 127 return this._textEditor; |
| 128 } |
| 129 |
| 130 _ensureContentLoaded() { |
| 131 if (!this._contentRequested) { |
| 132 this._contentRequested = true; |
| 133 this._lazyContent().then(this.setContent.bind(this)); |
| 134 } |
| 135 } |
| 136 |
| 137 /** |
| 138 * @param {number} line 0-based |
| 139 * @param {number=} column |
| 140 * @param {boolean=} shouldHighlight |
| 141 */ |
| 142 revealPosition(line, column, shouldHighlight) { |
| 143 this._clearLineToScrollTo(); |
| 144 this._clearSelectionToSet(); |
| 145 this._positionToReveal = {line: line, column: column, shouldHighlight: shoul
dHighlight}; |
| 146 this._innerRevealPositionIfNeeded(); |
| 147 } |
| 148 |
| 149 _innerRevealPositionIfNeeded() { |
| 150 if (!this._positionToReveal) |
| 151 return; |
| 152 |
| 153 if (!this.loaded || !this.isEditorShowing()) |
| 154 return; |
| 155 |
| 156 this._textEditor.revealPosition( |
| 157 this._positionToReveal.line, this._positionToReveal.column, this._positi
onToReveal.shouldHighlight); |
| 158 delete this._positionToReveal; |
| 159 } |
| 160 |
| 161 _clearPositionToReveal() { |
| 162 this._textEditor.clearPositionHighlight(); |
| 163 delete this._positionToReveal; |
| 164 } |
| 165 |
| 166 /** |
| 167 * @param {number} line |
| 168 */ |
| 169 scrollToLine(line) { |
| 170 this._clearPositionToReveal(); |
| 171 this._lineToScrollTo = line; |
| 172 this._innerScrollToLineIfNeeded(); |
| 173 } |
| 174 |
| 175 _innerScrollToLineIfNeeded() { |
| 176 if (typeof this._lineToScrollTo === 'number') { |
| 177 if (this.loaded && this.isEditorShowing()) { |
| 178 this._textEditor.scrollToLine(this._lineToScrollTo); |
| 179 delete this._lineToScrollTo; |
| 180 } |
| 181 } |
| 182 } |
| 183 |
| 184 _clearLineToScrollTo() { |
| 185 delete this._lineToScrollTo; |
| 186 } |
| 187 |
| 188 /** |
| 189 * @return {!WebInspector.TextRange} |
| 190 */ |
| 191 selection() { |
| 192 return this.textEditor.selection(); |
| 193 } |
| 194 |
| 195 /** |
| 196 * @param {!WebInspector.TextRange} textRange |
| 197 */ |
| 198 setSelection(textRange) { |
| 199 this._selectionToSet = textRange; |
| 200 this._innerSetSelectionIfNeeded(); |
| 201 } |
| 202 |
| 203 _innerSetSelectionIfNeeded() { |
| 204 if (this._selectionToSet && this.loaded && this.isEditorShowing()) { |
| 205 this._textEditor.setSelection(this._selectionToSet); |
| 206 delete this._selectionToSet; |
| 207 } |
| 208 } |
| 209 |
| 210 _clearSelectionToSet() { |
| 211 delete this._selectionToSet; |
| 212 } |
| 213 |
| 214 _wasShownOrLoaded() { |
| 215 this._innerRevealPositionIfNeeded(); |
| 216 this._innerSetSelectionIfNeeded(); |
| 217 this._innerScrollToLineIfNeeded(); |
| 218 } |
| 219 |
| 220 /** |
| 221 * @param {!WebInspector.TextRange} oldRange |
| 222 * @param {!WebInspector.TextRange} newRange |
| 223 */ |
| 224 onTextChanged(oldRange, newRange) { |
| 225 if (this._searchConfig && this._searchableView) |
| 226 this.performSearch(this._searchConfig, false, false); |
| 227 } |
| 228 |
| 229 /** |
| 230 * @param {string} content |
| 231 * @param {string} mimeType |
| 232 * @return {string} |
| 233 */ |
| 234 _simplifyMimeType(content, mimeType) { |
| 235 if (!mimeType) |
| 236 return ''; |
| 237 if (mimeType.indexOf('javascript') >= 0 || mimeType.indexOf('jscript') >= 0
|| mimeType.indexOf('ecmascript') >= 0) |
| 238 return 'text/javascript'; |
| 239 // A hack around the fact that files with "php" extension might be either st
andalone or html embedded php scripts. |
| 240 if (mimeType === 'text/x-php' && content.match(/\<\?.*\?\>/g)) |
| 241 return 'application/x-httpd-php'; |
| 242 return mimeType; |
| 243 } |
| 244 |
| 245 /** |
| 246 * @param {string} highlighterType |
| 247 */ |
| 248 setHighlighterType(highlighterType) { |
| 249 this._highlighterType = highlighterType; |
| 250 this._updateHighlighterType(''); |
| 251 } |
| 252 |
| 253 /** |
| 254 * @param {string} content |
| 255 */ |
| 256 _updateHighlighterType(content) { |
| 257 this._textEditor.setMimeType(this._simplifyMimeType(content, this._highlight
erType)); |
| 258 } |
| 259 |
| 260 /** |
| 261 * @param {?string} content |
| 262 */ |
| 263 setContent(content) { |
| 264 if (!this._loaded) { |
| 265 this._loaded = true; |
| 266 this._textEditor.setText(content || ''); |
| 267 this._textEditor.markClean(); |
| 268 } else { |
| 269 var scrollTop = this._textEditor.scrollTop(); |
| 270 var selection = this._textEditor.selection(); |
| 271 this._textEditor.setText(content || ''); |
| 272 this._textEditor.setScrollTop(scrollTop); |
| 273 this._textEditor.setSelection(selection); |
| 274 } |
| 275 |
| 276 this._updateHighlighterType(content || ''); |
| 277 this._wasShownOrLoaded(); |
| 278 |
| 279 if (this._delayedFindSearchMatches) { |
| 280 this._delayedFindSearchMatches(); |
| 281 delete this._delayedFindSearchMatches; |
| 282 } |
| 283 this.onTextEditorContentSet(); |
| 284 } |
| 285 |
| 286 onTextEditorContentSet() { |
| 287 } |
| 288 |
| 289 /** |
| 290 * @param {?WebInspector.SearchableView} view |
| 291 */ |
| 292 setSearchableView(view) { |
| 293 this._searchableView = view; |
| 294 } |
| 295 |
| 296 /** |
| 297 * @param {!WebInspector.SearchableView.SearchConfig} searchConfig |
| 298 * @param {boolean} shouldJump |
| 299 * @param {boolean} jumpBackwards |
| 300 */ |
| 301 _doFindSearchMatches(searchConfig, shouldJump, jumpBackwards) { |
| 302 this._currentSearchResultIndex = -1; |
| 303 this._searchResults = []; |
| 304 |
| 305 var regex = searchConfig.toSearchRegex(); |
| 306 this._searchRegex = regex; |
| 307 this._searchResults = this._collectRegexMatches(regex); |
| 308 |
| 309 if (this._searchableView) |
| 310 this._searchableView.updateSearchMatchesCount(this._searchResults.length); |
| 311 |
| 312 if (!this._searchResults.length) |
| 313 this._textEditor.cancelSearchResultsHighlight(); |
| 314 else if (shouldJump && jumpBackwards) |
| 315 this.jumpToPreviousSearchResult(); |
| 316 else if (shouldJump) |
| 317 this.jumpToNextSearchResult(); |
| 318 else |
| 319 this._textEditor.highlightSearchResults(regex, null); |
| 320 } |
| 321 |
| 322 /** |
| 323 * @override |
| 324 * @param {!WebInspector.SearchableView.SearchConfig} searchConfig |
| 325 * @param {boolean} shouldJump |
| 326 * @param {boolean=} jumpBackwards |
| 327 */ |
| 328 performSearch(searchConfig, shouldJump, jumpBackwards) { |
| 329 if (this._searchableView) |
| 330 this._searchableView.updateSearchMatchesCount(0); |
| 331 |
| 332 this._resetSearch(); |
| 333 this._searchConfig = searchConfig; |
| 334 if (this.loaded) |
| 335 this._doFindSearchMatches(searchConfig, shouldJump, !!jumpBackwards); |
| 336 else |
| 337 this._delayedFindSearchMatches = this._doFindSearchMatches.bind(this, sear
chConfig, shouldJump, !!jumpBackwards); |
| 338 |
| 339 this._ensureContentLoaded(); |
| 340 } |
| 341 |
| 342 _resetCurrentSearchResultIndex() { |
| 343 if (!this._searchResults.length) |
| 344 return; |
| 345 this._currentSearchResultIndex = -1; |
| 346 if (this._searchableView) |
| 347 this._searchableView.updateCurrentMatchIndex(this._currentSearchResultInde
x); |
| 348 this._textEditor.highlightSearchResults(this._searchRegex, null); |
| 349 } |
| 350 |
| 351 _resetSearch() { |
| 352 delete this._searchConfig; |
| 353 delete this._delayedFindSearchMatches; |
| 354 this._currentSearchResultIndex = -1; |
| 355 this._searchResults = []; |
| 356 delete this._searchRegex; |
| 357 } |
| 358 |
| 359 /** |
| 360 * @override |
| 361 */ |
| 362 searchCanceled() { |
| 363 var range = this._currentSearchResultIndex !== -1 ? this._searchResults[this
._currentSearchResultIndex] : null; |
| 364 this._resetSearch(); |
| 365 if (!this.loaded) |
| 366 return; |
| 367 this._textEditor.cancelSearchResultsHighlight(); |
| 368 if (range) |
| 369 this.setSelection(range); |
| 370 } |
| 371 |
| 372 /** |
| 373 * @return {boolean} |
| 374 */ |
| 375 hasSearchResults() { |
| 376 return this._searchResults.length > 0; |
| 377 } |
| 378 |
| 379 jumpToFirstSearchResult() { |
| 380 this.jumpToSearchResult(0); |
| 381 } |
| 382 |
| 383 jumpToLastSearchResult() { |
| 384 this.jumpToSearchResult(this._searchResults.length - 1); |
| 385 } |
| 386 |
| 387 /** |
| 388 * @return {number} |
| 389 */ |
| 390 _searchResultIndexForCurrentSelection() { |
| 391 return this._searchResults.lowerBound( |
| 392 this._textEditor.selection().collapseToEnd(), WebInspector.TextRange.com
parator); |
| 393 } |
| 394 |
| 395 /** |
| 396 * @override |
| 397 */ |
| 398 jumpToNextSearchResult() { |
| 399 var currentIndex = this._searchResultIndexForCurrentSelection(); |
| 400 var nextIndex = this._currentSearchResultIndex === -1 ? currentIndex : curre
ntIndex + 1; |
| 401 this.jumpToSearchResult(nextIndex); |
| 402 } |
| 403 |
| 404 /** |
| 405 * @override |
| 406 */ |
| 407 jumpToPreviousSearchResult() { |
| 408 var currentIndex = this._searchResultIndexForCurrentSelection(); |
| 409 this.jumpToSearchResult(currentIndex - 1); |
| 410 } |
| 411 |
| 412 /** |
| 413 * @override |
| 414 * @return {boolean} |
| 415 */ |
| 416 supportsCaseSensitiveSearch() { |
| 417 return true; |
| 418 } |
| 419 |
| 420 /** |
| 421 * @override |
| 422 * @return {boolean} |
| 423 */ |
| 424 supportsRegexSearch() { |
| 425 return true; |
| 426 } |
| 427 |
| 428 get currentSearchResultIndex() { |
| 429 return this._currentSearchResultIndex; |
| 430 } |
| 431 |
| 432 jumpToSearchResult(index) { |
| 433 if (!this.loaded || !this._searchResults.length) |
| 434 return; |
| 435 this._currentSearchResultIndex = (index + this._searchResults.length) % this
._searchResults.length; |
| 436 if (this._searchableView) |
| 437 this._searchableView.updateCurrentMatchIndex(this._currentSearchResultInde
x); |
| 438 this._textEditor.highlightSearchResults(this._searchRegex, this._searchResul
ts[this._currentSearchResultIndex]); |
| 439 } |
| 440 |
| 441 /** |
| 442 * @override |
| 443 * @param {!WebInspector.SearchableView.SearchConfig} searchConfig |
| 444 * @param {string} replacement |
| 445 */ |
| 446 replaceSelectionWith(searchConfig, replacement) { |
| 447 var range = this._searchResults[this._currentSearchResultIndex]; |
| 448 if (!range) |
| 449 return; |
| 450 this._textEditor.highlightSearchResults(this._searchRegex, null); |
| 451 |
| 452 var oldText = this._textEditor.text(range); |
| 453 var regex = searchConfig.toSearchRegex(); |
| 454 var text; |
| 455 if (regex.__fromRegExpQuery) |
| 456 text = oldText.replace(regex, replacement); |
| 457 else |
| 458 text = oldText.replace(regex, function() { |
| 459 return replacement; |
| 460 }); |
| 461 |
| 462 var newRange = this._textEditor.editRange(range, text); |
| 463 this._textEditor.setSelection(newRange.collapseToEnd()); |
| 464 } |
| 465 |
| 466 /** |
| 467 * @override |
| 468 * @param {!WebInspector.SearchableView.SearchConfig} searchConfig |
| 469 * @param {string} replacement |
| 470 */ |
| 471 replaceAllWith(searchConfig, replacement) { |
| 472 this._resetCurrentSearchResultIndex(); |
| 473 |
| 474 var text = this._textEditor.text(); |
| 475 var range = this._textEditor.fullRange(); |
| 476 |
| 477 var regex = searchConfig.toSearchRegex(true); |
| 478 if (regex.__fromRegExpQuery) |
| 479 text = text.replace(regex, replacement); |
| 480 else |
| 481 text = text.replace(regex, function() { |
| 482 return replacement; |
| 483 }); |
| 484 |
| 485 var ranges = this._collectRegexMatches(regex); |
| 486 if (!ranges.length) |
| 487 return; |
| 488 |
| 489 // Calculate the position of the end of the last range to be edited. |
| 490 var currentRangeIndex = ranges.lowerBound(this._textEditor.selection(), WebI
nspector.TextRange.comparator); |
| 491 var lastRangeIndex = mod(currentRangeIndex - 1, ranges.length); |
| 492 var lastRange = ranges[lastRangeIndex]; |
| 493 var replacementLineEndings = replacement.computeLineEndings(); |
| 494 var replacementLineCount = replacementLineEndings.length; |
| 495 var lastLineNumber = lastRange.startLine + replacementLineEndings.length - 1
; |
| 496 var lastColumnNumber = lastRange.startColumn; |
| 497 if (replacementLineEndings.length > 1) |
| 498 lastColumnNumber = |
| 499 replacementLineEndings[replacementLineCount - 1] - replacementLineEndi
ngs[replacementLineCount - 2] - 1; |
| 500 |
| 501 this._textEditor.editRange(range, text); |
| 502 this._textEditor.revealPosition(lastLineNumber, lastColumnNumber); |
| 503 this._textEditor.setSelection(WebInspector.TextRange.createFromLocation(last
LineNumber, lastColumnNumber)); |
| 504 } |
| 505 |
| 506 _collectRegexMatches(regexObject) { |
| 507 var ranges = []; |
| 508 for (var i = 0; i < this._textEditor.linesCount; ++i) { |
| 509 var line = this._textEditor.line(i); |
| 510 var offset = 0; |
| 511 do { |
| 512 var match = regexObject.exec(line); |
| 513 if (match) { |
| 514 var matchEndIndex = match.index + Math.max(match[0].length, 1); |
| 515 if (match[0].length) |
| 516 ranges.push(new WebInspector.TextRange(i, offset + match.index, i, o
ffset + matchEndIndex)); |
| 517 offset += matchEndIndex; |
| 518 line = line.substring(matchEndIndex); |
| 519 } |
| 520 } while (match && line); |
| 521 } |
| 522 return ranges; |
| 523 } |
| 524 |
| 525 /** |
| 526 * @override |
| 527 * @return {!Promise} |
| 528 */ |
| 529 populateLineGutterContextMenu(contextMenu, lineNumber) { |
| 530 return Promise.resolve(); |
| 531 } |
| 532 |
| 533 /** |
| 534 * @override |
| 535 * @return {!Promise} |
| 536 */ |
| 537 populateTextAreaContextMenu(contextMenu, lineNumber, columnNumber) { |
| 538 return Promise.resolve(); |
| 539 } |
| 540 |
| 541 /** |
| 542 * @return {boolean} |
| 543 */ |
| 544 canEditSource() { |
| 545 return this._editable; |
| 546 } |
| 547 |
| 548 _updateSourcePosition() { |
| 549 var selections = this._textEditor.selections(); |
| 550 if (!selections.length) |
| 551 return; |
| 552 if (selections.length > 1) { |
| 553 this._sourcePosition.setText(WebInspector.UIString('%d selection regions',
selections.length)); |
| 554 return; |
| 555 } |
| 556 var textRange = selections[0]; |
| 557 if (textRange.isEmpty()) { |
| 558 this._sourcePosition.setText( |
| 559 WebInspector.UIString('Line %d, Column %d', textRange.endLine + 1, tex
tRange.endColumn + 1)); |
| 560 return; |
| 561 } |
| 562 textRange = textRange.normalize(); |
| 563 |
| 564 var selectedText = this._textEditor.text(textRange); |
| 565 if (textRange.startLine === textRange.endLine) |
| 566 this._sourcePosition.setText(WebInspector.UIString('%d characters selected
', selectedText.length)); |
| 567 else |
| 568 this._sourcePosition.setText(WebInspector.UIString( |
| 569 '%d lines, %d characters selected', textRange.endLine - textRange.star
tLine + 1, selectedText.length)); |
| 570 } |
| 571 |
| 572 _handleKeyDown(e) { |
| 573 var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEvent(e); |
| 574 var handler = this._shortcuts[shortcutKey]; |
| 575 if (handler && handler()) |
| 576 e.consume(true); |
| 577 } |
66 }; | 578 }; |
67 | |
68 WebInspector.SourceFrame.prototype = { | |
69 /** | |
70 * @param {boolean} editable | |
71 */ | |
72 setEditable: function(editable) | |
73 { | |
74 this._editable = editable; | |
75 this._textEditor.setReadOnly(editable); | |
76 }, | |
77 | |
78 /** | |
79 * @param {number} key | |
80 * @param {function():boolean} handler | |
81 */ | |
82 addShortcut: function(key, handler) | |
83 { | |
84 this._shortcuts[key] = handler; | |
85 }, | |
86 | |
87 /** | |
88 * @override | |
89 */ | |
90 wasShown: function() | |
91 { | |
92 this._ensureContentLoaded(); | |
93 this._textEditor.show(this.element); | |
94 this._editorAttached = true; | |
95 this._wasShownOrLoaded(); | |
96 }, | |
97 | |
98 /** | |
99 * @return {boolean} | |
100 */ | |
101 isEditorShowing: function() | |
102 { | |
103 return this.isShowing() && this._editorAttached; | |
104 }, | |
105 | |
106 willHide: function() | |
107 { | |
108 WebInspector.Widget.prototype.willHide.call(this); | |
109 | |
110 this._clearPositionToReveal(); | |
111 }, | |
112 | |
113 /** | |
114 * @override | |
115 * @return {!Array<!WebInspector.ToolbarItem>} | |
116 */ | |
117 syncToolbarItems: function() | |
118 { | |
119 return [this._sourcePosition]; | |
120 }, | |
121 | |
122 get loaded() | |
123 { | |
124 return this._loaded; | |
125 }, | |
126 | |
127 get textEditor() | |
128 { | |
129 return this._textEditor; | |
130 }, | |
131 | |
132 _ensureContentLoaded: function() | |
133 { | |
134 if (!this._contentRequested) { | |
135 this._contentRequested = true; | |
136 this._lazyContent().then(this.setContent.bind(this)); | |
137 } | |
138 }, | |
139 | |
140 /** | |
141 * @param {number} line 0-based | |
142 * @param {number=} column | |
143 * @param {boolean=} shouldHighlight | |
144 */ | |
145 revealPosition: function(line, column, shouldHighlight) | |
146 { | |
147 this._clearLineToScrollTo(); | |
148 this._clearSelectionToSet(); | |
149 this._positionToReveal = { line: line, column: column, shouldHighlight:
shouldHighlight }; | |
150 this._innerRevealPositionIfNeeded(); | |
151 }, | |
152 | |
153 _innerRevealPositionIfNeeded: function() | |
154 { | |
155 if (!this._positionToReveal) | |
156 return; | |
157 | |
158 if (!this.loaded || !this.isEditorShowing()) | |
159 return; | |
160 | |
161 this._textEditor.revealPosition(this._positionToReveal.line, this._posit
ionToReveal.column, this._positionToReveal.shouldHighlight); | |
162 delete this._positionToReveal; | |
163 }, | |
164 | |
165 _clearPositionToReveal: function() | |
166 { | |
167 this._textEditor.clearPositionHighlight(); | |
168 delete this._positionToReveal; | |
169 }, | |
170 | |
171 /** | |
172 * @param {number} line | |
173 */ | |
174 scrollToLine: function(line) | |
175 { | |
176 this._clearPositionToReveal(); | |
177 this._lineToScrollTo = line; | |
178 this._innerScrollToLineIfNeeded(); | |
179 }, | |
180 | |
181 _innerScrollToLineIfNeeded: function() | |
182 { | |
183 if (typeof this._lineToScrollTo === "number") { | |
184 if (this.loaded && this.isEditorShowing()) { | |
185 this._textEditor.scrollToLine(this._lineToScrollTo); | |
186 delete this._lineToScrollTo; | |
187 } | |
188 } | |
189 }, | |
190 | |
191 _clearLineToScrollTo: function() | |
192 { | |
193 delete this._lineToScrollTo; | |
194 }, | |
195 | |
196 /** | |
197 * @return {!WebInspector.TextRange} | |
198 */ | |
199 selection: function() | |
200 { | |
201 return this.textEditor.selection(); | |
202 }, | |
203 | |
204 /** | |
205 * @param {!WebInspector.TextRange} textRange | |
206 */ | |
207 setSelection: function(textRange) | |
208 { | |
209 this._selectionToSet = textRange; | |
210 this._innerSetSelectionIfNeeded(); | |
211 }, | |
212 | |
213 _innerSetSelectionIfNeeded: function() | |
214 { | |
215 if (this._selectionToSet && this.loaded && this.isEditorShowing()) { | |
216 this._textEditor.setSelection(this._selectionToSet); | |
217 delete this._selectionToSet; | |
218 } | |
219 }, | |
220 | |
221 _clearSelectionToSet: function() | |
222 { | |
223 delete this._selectionToSet; | |
224 }, | |
225 | |
226 _wasShownOrLoaded: function() | |
227 { | |
228 this._innerRevealPositionIfNeeded(); | |
229 this._innerSetSelectionIfNeeded(); | |
230 this._innerScrollToLineIfNeeded(); | |
231 }, | |
232 | |
233 /** | |
234 * @param {!WebInspector.TextRange} oldRange | |
235 * @param {!WebInspector.TextRange} newRange | |
236 */ | |
237 onTextChanged: function(oldRange, newRange) | |
238 { | |
239 if (this._searchConfig && this._searchableView) | |
240 this.performSearch(this._searchConfig, false, false); | |
241 }, | |
242 | |
243 /** | |
244 * @param {string} content | |
245 * @param {string} mimeType | |
246 * @return {string} | |
247 */ | |
248 _simplifyMimeType: function(content, mimeType) | |
249 { | |
250 if (!mimeType) | |
251 return ""; | |
252 if (mimeType.indexOf("javascript") >= 0 || | |
253 mimeType.indexOf("jscript") >= 0 || | |
254 mimeType.indexOf("ecmascript") >= 0) | |
255 return "text/javascript"; | |
256 // A hack around the fact that files with "php" extension might be eithe
r standalone or html embedded php scripts. | |
257 if (mimeType === "text/x-php" && content.match(/\<\?.*\?\>/g)) | |
258 return "application/x-httpd-php"; | |
259 return mimeType; | |
260 }, | |
261 | |
262 /** | |
263 * @param {string} highlighterType | |
264 */ | |
265 setHighlighterType: function(highlighterType) | |
266 { | |
267 this._highlighterType = highlighterType; | |
268 this._updateHighlighterType(""); | |
269 }, | |
270 | |
271 /** | |
272 * @param {string} content | |
273 */ | |
274 _updateHighlighterType: function(content) | |
275 { | |
276 this._textEditor.setMimeType(this._simplifyMimeType(content, this._highl
ighterType)); | |
277 }, | |
278 | |
279 /** | |
280 * @param {?string} content | |
281 */ | |
282 setContent: function(content) | |
283 { | |
284 if (!this._loaded) { | |
285 this._loaded = true; | |
286 this._textEditor.setText(content || ""); | |
287 this._textEditor.markClean(); | |
288 } else { | |
289 var scrollTop = this._textEditor.scrollTop(); | |
290 var selection = this._textEditor.selection(); | |
291 this._textEditor.setText(content || ""); | |
292 this._textEditor.setScrollTop(scrollTop); | |
293 this._textEditor.setSelection(selection); | |
294 } | |
295 | |
296 this._updateHighlighterType(content || ""); | |
297 this._wasShownOrLoaded(); | |
298 | |
299 if (this._delayedFindSearchMatches) { | |
300 this._delayedFindSearchMatches(); | |
301 delete this._delayedFindSearchMatches; | |
302 } | |
303 this.onTextEditorContentSet(); | |
304 }, | |
305 | |
306 onTextEditorContentSet: function() {}, | |
307 | |
308 /** | |
309 * @param {?WebInspector.SearchableView} view | |
310 */ | |
311 setSearchableView: function(view) | |
312 { | |
313 this._searchableView = view; | |
314 }, | |
315 | |
316 /** | |
317 * @param {!WebInspector.SearchableView.SearchConfig} searchConfig | |
318 * @param {boolean} shouldJump | |
319 * @param {boolean} jumpBackwards | |
320 */ | |
321 _doFindSearchMatches: function(searchConfig, shouldJump, jumpBackwards) | |
322 { | |
323 this._currentSearchResultIndex = -1; | |
324 this._searchResults = []; | |
325 | |
326 var regex = searchConfig.toSearchRegex(); | |
327 this._searchRegex = regex; | |
328 this._searchResults = this._collectRegexMatches(regex); | |
329 | |
330 if (this._searchableView) | |
331 this._searchableView.updateSearchMatchesCount(this._searchResults.le
ngth); | |
332 | |
333 if (!this._searchResults.length) | |
334 this._textEditor.cancelSearchResultsHighlight(); | |
335 else if (shouldJump && jumpBackwards) | |
336 this.jumpToPreviousSearchResult(); | |
337 else if (shouldJump) | |
338 this.jumpToNextSearchResult(); | |
339 else | |
340 this._textEditor.highlightSearchResults(regex, null); | |
341 }, | |
342 | |
343 /** | |
344 * @override | |
345 * @param {!WebInspector.SearchableView.SearchConfig} searchConfig | |
346 * @param {boolean} shouldJump | |
347 * @param {boolean=} jumpBackwards | |
348 */ | |
349 performSearch: function(searchConfig, shouldJump, jumpBackwards) | |
350 { | |
351 if (this._searchableView) | |
352 this._searchableView.updateSearchMatchesCount(0); | |
353 | |
354 this._resetSearch(); | |
355 this._searchConfig = searchConfig; | |
356 if (this.loaded) | |
357 this._doFindSearchMatches(searchConfig, shouldJump, !!jumpBackwards)
; | |
358 else | |
359 this._delayedFindSearchMatches = this._doFindSearchMatches.bind(this
, searchConfig, shouldJump, !!jumpBackwards); | |
360 | |
361 this._ensureContentLoaded(); | |
362 }, | |
363 | |
364 _resetCurrentSearchResultIndex: function() | |
365 { | |
366 if (!this._searchResults.length) | |
367 return; | |
368 this._currentSearchResultIndex = -1; | |
369 if (this._searchableView) | |
370 this._searchableView.updateCurrentMatchIndex(this._currentSearchResu
ltIndex); | |
371 this._textEditor.highlightSearchResults(this._searchRegex, null); | |
372 }, | |
373 | |
374 _resetSearch: function() | |
375 { | |
376 delete this._searchConfig; | |
377 delete this._delayedFindSearchMatches; | |
378 this._currentSearchResultIndex = -1; | |
379 this._searchResults = []; | |
380 delete this._searchRegex; | |
381 }, | |
382 | |
383 /** | |
384 * @override | |
385 */ | |
386 searchCanceled: function() | |
387 { | |
388 var range = this._currentSearchResultIndex !== -1 ? this._searchResults[
this._currentSearchResultIndex] : null; | |
389 this._resetSearch(); | |
390 if (!this.loaded) | |
391 return; | |
392 this._textEditor.cancelSearchResultsHighlight(); | |
393 if (range) | |
394 this.setSelection(range); | |
395 }, | |
396 | |
397 /** | |
398 * @return {boolean} | |
399 */ | |
400 hasSearchResults: function() | |
401 { | |
402 return this._searchResults.length > 0; | |
403 }, | |
404 | |
405 jumpToFirstSearchResult: function() | |
406 { | |
407 this.jumpToSearchResult(0); | |
408 }, | |
409 | |
410 jumpToLastSearchResult: function() | |
411 { | |
412 this.jumpToSearchResult(this._searchResults.length - 1); | |
413 }, | |
414 | |
415 /** | |
416 * @return {number} | |
417 */ | |
418 _searchResultIndexForCurrentSelection: function() | |
419 { | |
420 return this._searchResults.lowerBound(this._textEditor.selection().colla
pseToEnd(), WebInspector.TextRange.comparator); | |
421 }, | |
422 | |
423 /** | |
424 * @override | |
425 */ | |
426 jumpToNextSearchResult: function() | |
427 { | |
428 var currentIndex = this._searchResultIndexForCurrentSelection(); | |
429 var nextIndex = this._currentSearchResultIndex === -1 ? currentIndex : c
urrentIndex + 1; | |
430 this.jumpToSearchResult(nextIndex); | |
431 }, | |
432 | |
433 /** | |
434 * @override | |
435 */ | |
436 jumpToPreviousSearchResult: function() | |
437 { | |
438 var currentIndex = this._searchResultIndexForCurrentSelection(); | |
439 this.jumpToSearchResult(currentIndex - 1); | |
440 }, | |
441 | |
442 /** | |
443 * @override | |
444 * @return {boolean} | |
445 */ | |
446 supportsCaseSensitiveSearch: function() | |
447 { | |
448 return true; | |
449 }, | |
450 | |
451 /** | |
452 * @override | |
453 * @return {boolean} | |
454 */ | |
455 supportsRegexSearch: function() | |
456 { | |
457 return true; | |
458 }, | |
459 | |
460 get currentSearchResultIndex() | |
461 { | |
462 return this._currentSearchResultIndex; | |
463 }, | |
464 | |
465 jumpToSearchResult: function(index) | |
466 { | |
467 if (!this.loaded || !this._searchResults.length) | |
468 return; | |
469 this._currentSearchResultIndex = (index + this._searchResults.length) %
this._searchResults.length; | |
470 if (this._searchableView) | |
471 this._searchableView.updateCurrentMatchIndex(this._currentSearchResu
ltIndex); | |
472 this._textEditor.highlightSearchResults(this._searchRegex, this._searchR
esults[this._currentSearchResultIndex]); | |
473 }, | |
474 | |
475 /** | |
476 * @override | |
477 * @param {!WebInspector.SearchableView.SearchConfig} searchConfig | |
478 * @param {string} replacement | |
479 */ | |
480 replaceSelectionWith: function(searchConfig, replacement) | |
481 { | |
482 var range = this._searchResults[this._currentSearchResultIndex]; | |
483 if (!range) | |
484 return; | |
485 this._textEditor.highlightSearchResults(this._searchRegex, null); | |
486 | |
487 var oldText = this._textEditor.text(range); | |
488 var regex = searchConfig.toSearchRegex(); | |
489 var text; | |
490 if (regex.__fromRegExpQuery) | |
491 text = oldText.replace(regex, replacement); | |
492 else | |
493 text = oldText.replace(regex, function() { return replacement; }); | |
494 | |
495 var newRange = this._textEditor.editRange(range, text); | |
496 this._textEditor.setSelection(newRange.collapseToEnd()); | |
497 }, | |
498 | |
499 /** | |
500 * @override | |
501 * @param {!WebInspector.SearchableView.SearchConfig} searchConfig | |
502 * @param {string} replacement | |
503 */ | |
504 replaceAllWith: function(searchConfig, replacement) | |
505 { | |
506 this._resetCurrentSearchResultIndex(); | |
507 | |
508 var text = this._textEditor.text(); | |
509 var range = this._textEditor.fullRange(); | |
510 | |
511 var regex = searchConfig.toSearchRegex(true); | |
512 if (regex.__fromRegExpQuery) | |
513 text = text.replace(regex, replacement); | |
514 else | |
515 text = text.replace(regex, function() { return replacement; }); | |
516 | |
517 var ranges = this._collectRegexMatches(regex); | |
518 if (!ranges.length) | |
519 return; | |
520 | |
521 // Calculate the position of the end of the last range to be edited. | |
522 var currentRangeIndex = ranges.lowerBound(this._textEditor.selection(),
WebInspector.TextRange.comparator); | |
523 var lastRangeIndex = mod(currentRangeIndex - 1, ranges.length); | |
524 var lastRange = ranges[lastRangeIndex]; | |
525 var replacementLineEndings = replacement.computeLineEndings(); | |
526 var replacementLineCount = replacementLineEndings.length; | |
527 var lastLineNumber = lastRange.startLine + replacementLineEndings.length
- 1; | |
528 var lastColumnNumber = lastRange.startColumn; | |
529 if (replacementLineEndings.length > 1) | |
530 lastColumnNumber = replacementLineEndings[replacementLineCount - 1]
- replacementLineEndings[replacementLineCount - 2] - 1; | |
531 | |
532 this._textEditor.editRange(range, text); | |
533 this._textEditor.revealPosition(lastLineNumber, lastColumnNumber); | |
534 this._textEditor.setSelection(WebInspector.TextRange.createFromLocation(
lastLineNumber, lastColumnNumber)); | |
535 }, | |
536 | |
537 _collectRegexMatches: function(regexObject) | |
538 { | |
539 var ranges = []; | |
540 for (var i = 0; i < this._textEditor.linesCount; ++i) { | |
541 var line = this._textEditor.line(i); | |
542 var offset = 0; | |
543 do { | |
544 var match = regexObject.exec(line); | |
545 if (match) { | |
546 var matchEndIndex = match.index + Math.max(match[0].length,
1); | |
547 if (match[0].length) | |
548 ranges.push(new WebInspector.TextRange(i, offset + match
.index, i, offset + matchEndIndex)); | |
549 offset += matchEndIndex; | |
550 line = line.substring(matchEndIndex); | |
551 } | |
552 } while (match && line); | |
553 } | |
554 return ranges; | |
555 }, | |
556 | |
557 /** | |
558 * @override | |
559 * @return {!Promise} | |
560 */ | |
561 populateLineGutterContextMenu: function(contextMenu, lineNumber) | |
562 { | |
563 return Promise.resolve(); | |
564 }, | |
565 | |
566 /** | |
567 * @override | |
568 * @return {!Promise} | |
569 */ | |
570 populateTextAreaContextMenu: function(contextMenu, lineNumber, columnNumber) | |
571 { | |
572 return Promise.resolve(); | |
573 }, | |
574 | |
575 /** | |
576 * @return {boolean} | |
577 */ | |
578 canEditSource: function() | |
579 { | |
580 return this._editable; | |
581 }, | |
582 | |
583 _updateSourcePosition: function() | |
584 { | |
585 var selections = this._textEditor.selections(); | |
586 if (!selections.length) | |
587 return; | |
588 if (selections.length > 1) { | |
589 this._sourcePosition.setText(WebInspector.UIString("%d selection reg
ions", selections.length)); | |
590 return; | |
591 } | |
592 var textRange = selections[0]; | |
593 if (textRange.isEmpty()) { | |
594 this._sourcePosition.setText(WebInspector.UIString("Line %d, Column
%d", textRange.endLine + 1, textRange.endColumn + 1)); | |
595 return; | |
596 } | |
597 textRange = textRange.normalize(); | |
598 | |
599 var selectedText = this._textEditor.text(textRange); | |
600 if (textRange.startLine === textRange.endLine) | |
601 this._sourcePosition.setText(WebInspector.UIString("%d characters se
lected", selectedText.length)); | |
602 else | |
603 this._sourcePosition.setText(WebInspector.UIString("%d lines, %d cha
racters selected", textRange.endLine - textRange.startLine + 1, selectedText.len
gth)); | |
604 }, | |
605 | |
606 _handleKeyDown: function(e) | |
607 { | |
608 var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEvent(e); | |
609 var handler = this._shortcuts[shortcutKey]; | |
610 if (handler && handler()) | |
611 e.consume(true); | |
612 }, | |
613 | |
614 __proto__: WebInspector.SimpleView.prototype | |
615 }; | |
OLD | NEW |