TextEditor.js 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237
  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. WebInspector.TextEditor = function(element, mimeType, delegate)
  26. {
  27. WebInspector.Object.call(this);
  28. var text = (element ? element.textContent : "");
  29. this._element = element || document.createElement("div");
  30. this._element.classList.add(WebInspector.TextEditor.StyleClassName);
  31. this._element.classList.add(WebInspector.SyntaxHighlightedStyleClassName);
  32. this._readOnly = true;
  33. this._codeMirror = CodeMirror(this.element, {
  34. readOnly: this._readOnly,
  35. indentWithTabs: true,
  36. indentUnit: 4,
  37. lineNumbers: true,
  38. lineWrapping: true,
  39. matchBrackets: true,
  40. autoCloseBrackets: true
  41. });
  42. this._codeMirror.on("change", this._contentChanged.bind(this));
  43. this._codeMirror.on("gutterClick", this._gutterMouseDown.bind(this));
  44. this._codeMirror.getScrollerElement().addEventListener("click", this._openClickedLinks.bind(this), true);
  45. this._completionController = new WebInspector.CodeMirrorCompletionController(this._codeMirror, this);
  46. this._tokenTrackingController = new WebInspector.CodeMirrorTokenTrackingController(this._codeMirror, this);
  47. this._initialStringNotSet = true;
  48. this.mimeType = mimeType;
  49. this._breakpoints = {};
  50. this._executionLineNumber = NaN;
  51. this._executionColumnNumber = NaN;
  52. this._searchQuery = null;
  53. this._searchResults = [];
  54. this._currentSearchResultIndex = -1;
  55. this._formatted = false
  56. this._formatterSourceMap = null;
  57. this._delegate = delegate || null;
  58. };
  59. WebInspector.Object.addConstructorFunctions(WebInspector.TextEditor);
  60. WebInspector.TextEditor.StyleClassName = "text-editor";
  61. WebInspector.TextEditor.HighlightedStyleClassName = "highlighted";
  62. WebInspector.TextEditor.SearchResultStyleClassName = "search-result";
  63. WebInspector.TextEditor.HasBreakpointStyleClassName = "has-breakpoint";
  64. WebInspector.TextEditor.BreakpointResolvedStyleClassName = "breakpoint-resolved";
  65. WebInspector.TextEditor.BreakpointDisabledStyleClassName = "breakpoint-disabled";
  66. WebInspector.TextEditor.MultipleBreakpointsStyleClassName = "multiple-breakpoints";
  67. WebInspector.TextEditor.ExecutionLineStyleClassName = "execution-line";
  68. WebInspector.TextEditor.BouncyHighlightStyleClassName = "bouncy-highlight";
  69. WebInspector.TextEditor.NumberOfFindsPerSearchBatch = 10;
  70. WebInspector.TextEditor.HighlightAnimationDuration = 2000;
  71. WebInspector.TextEditor.Event = {
  72. ExecutionLineNumberDidChange: "text-editor-execution-line-number-did-change",
  73. NumberOfSearchResultsDidChange: "text-editor-number-of-search-results-did-change",
  74. ContentDidChange: "text-editor-content-did-change",
  75. FormattingDidChange: "text-editor-formatting-did-change"
  76. };
  77. WebInspector.TextEditor.prototype = {
  78. constructor: WebInspector.TextEditor,
  79. // Public
  80. get element()
  81. {
  82. return this._element;
  83. },
  84. get string()
  85. {
  86. return this._codeMirror.getValue();
  87. },
  88. set string(newString)
  89. {
  90. function update()
  91. {
  92. this._codeMirror.setValue(newString);
  93. if (this._initialStringNotSet) {
  94. this._codeMirror.clearHistory();
  95. this._codeMirror.markClean();
  96. delete this._initialStringNotSet;
  97. }
  98. // Automatically format the content.
  99. if (this._autoFormat) {
  100. console.assert(!this.formatted);
  101. this.formatted = true;
  102. delete this._autoFormat;
  103. }
  104. // Update the execution line now that we might have content for that line.
  105. this._updateExecutionLine();
  106. // Set the breakpoint styles now that we might have content for those lines.
  107. for (var lineNumber in this._breakpoints)
  108. this._setBreakpointStylesOnLine(lineNumber);
  109. // Try revealing the pending line now that we might have content with enough lines.
  110. this._revealPendingPositionIfPossible();
  111. }
  112. this._ignoreCodeMirrorContentDidChangeEvent = true;
  113. this._codeMirror.operation(update.bind(this));
  114. delete this._ignoreCodeMirrorContentDidChangeEvent;
  115. },
  116. get readOnly()
  117. {
  118. return this._codeMirror.getOption("readOnly") || false;
  119. },
  120. set readOnly(readOnly)
  121. {
  122. this._readOnly = readOnly;
  123. this._updateCodeMirrorReadOnly();
  124. },
  125. get formatted()
  126. {
  127. return this._formatted;
  128. },
  129. set formatted(formatted)
  130. {
  131. if (this._formatted === formatted)
  132. return;
  133. console.assert(!formatted || this.canBeFormatted());
  134. if (formatted && !this.canBeFormatted())
  135. return;
  136. this._ignoreCodeMirrorContentDidChangeEvent = true;
  137. this._prettyPrint(formatted);
  138. delete this._ignoreCodeMirrorContentDidChangeEvent;
  139. this._formatted = formatted;
  140. this._updateCodeMirrorReadOnly();
  141. this.dispatchEventToListeners(WebInspector.TextEditor.Event.FormattingDidChange);
  142. },
  143. set autoFormat(auto)
  144. {
  145. this._autoFormat = auto;
  146. },
  147. hasFormatter: function()
  148. {
  149. const supportedModes = {
  150. "javascript": true,
  151. "css-base": true,
  152. };
  153. var mode = this._codeMirror.getMode();
  154. return mode.name in supportedModes;
  155. },
  156. canBeFormatted: function()
  157. {
  158. // Can be overriden by subclasses.
  159. return this.hasFormatter();
  160. },
  161. get selectedTextRange()
  162. {
  163. var start = this._codeMirror.getCursor(true);
  164. var end = this._codeMirror.getCursor(false);
  165. return this._textRangeFromCodeMirrorPosition(start, end);
  166. },
  167. set selectedTextRange(textRange)
  168. {
  169. var position = this._codeMirrorPositionFromTextRange(textRange);
  170. this._codeMirror.setSelection(position.start, position.end);
  171. },
  172. get mimeType()
  173. {
  174. return this._mimeType;
  175. },
  176. set mimeType(newMIMEType)
  177. {
  178. this._mimeType = newMIMEType;
  179. this._codeMirror.setOption("mode", newMIMEType);
  180. },
  181. get executionLineNumber()
  182. {
  183. return this._executionLineNumber;
  184. },
  185. set executionLineNumber(lineNumber)
  186. {
  187. // Only return early if there isn't a line handle and that isn't changing.
  188. if (!this._executionLineHandle && isNaN(lineNumber))
  189. return;
  190. this._executionLineNumber = lineNumber;
  191. this._updateExecutionLine();
  192. // Still dispatch the event even if the number didn't change. The execution state still
  193. // could have changed (e.g. continuing in a loop with a breakpoint inside).
  194. this.dispatchEventToListeners(WebInspector.TextEditor.Event.ExecutionLineNumberDidChange);
  195. },
  196. get executionColumnNumber()
  197. {
  198. return this._executionColumnNumber;
  199. },
  200. set executionColumnNumber(columnNumber)
  201. {
  202. this._executionColumnNumber = columnNumber;
  203. },
  204. get formatterSourceMap()
  205. {
  206. return this._formatterSourceMap;
  207. },
  208. get tokenTrackingController()
  209. {
  210. return this._tokenTrackingController;
  211. },
  212. get delegate()
  213. {
  214. return this._delegate;
  215. },
  216. set delegate(newDelegate)
  217. {
  218. this._delegate = newDelegate || null;
  219. },
  220. get numberOfSearchResults()
  221. {
  222. return this._searchResults.length;
  223. },
  224. get currentSearchQuery()
  225. {
  226. return this._searchQuery;
  227. },
  228. set automaticallyRevealFirstSearchResult(reveal)
  229. {
  230. this._automaticallyRevealFirstSearchResult = reveal;
  231. // If we haven't shown a search result yet, reveal one now.
  232. if (this._automaticallyRevealFirstSearchResult && this._searchResults.length > 0) {
  233. if (this._currentSearchResultIndex === -1)
  234. this._revealFirstSearchResultAfterCursor();
  235. }
  236. },
  237. performSearch: function(query)
  238. {
  239. if (this._searchQuery === query)
  240. return;
  241. this.searchCleared();
  242. this._searchQuery = query;
  243. // Allow subclasses to handle the searching if they have a better way.
  244. // If we are formatted, just use CodeMirror's search.
  245. if (typeof this.customPerformSearch === "function" && !this.formatted) {
  246. if (this.customPerformSearch(query))
  247. return;
  248. }
  249. // Go down the slow patch for all other text content.
  250. var searchCursor = this._codeMirror.getSearchCursor(query, {line: 0, ch: 0}, true);
  251. var boundBatchSearch = batchSearch.bind(this);
  252. var numberOfSearchResultsDidChangeTimeout = null;
  253. function reportNumberOfSearchResultsDidChange()
  254. {
  255. if (numberOfSearchResultsDidChangeTimeout) {
  256. clearTimeout(numberOfSearchResultsDidChangeTimeout);
  257. numberOfSearchResultsDidChangeTimeout = null;
  258. }
  259. this.dispatchEventToListeners(WebInspector.TextEditor.Event.NumberOfSearchResultsDidChange);
  260. }
  261. function batchSearch()
  262. {
  263. // Bail if the query changed since we started.
  264. if (this._searchQuery !== query)
  265. return;
  266. var newSearchResults = [];
  267. var foundResult = false;
  268. for (var i = 0; i < WebInspector.TextEditor.NumberOfFindsPerSearchBatch && (foundResult = searchCursor.findNext()); ++i) {
  269. var textRange = this._textRangeFromCodeMirrorPosition(searchCursor.from(), searchCursor.to());
  270. newSearchResults.push(textRange);
  271. }
  272. this.addSearchResults(newSearchResults);
  273. // Don't report immediately, coalesce updates so they come in no faster than half a second.
  274. if (!numberOfSearchResultsDidChangeTimeout)
  275. numberOfSearchResultsDidChangeTimeout = setTimeout(reportNumberOfSearchResultsDidChange.bind(this), 500);
  276. if (foundResult) {
  277. // More lines to search, set a timeout so we don't block the UI long.
  278. setTimeout(boundBatchSearch, 50);
  279. } else {
  280. // Report immediately now that we are finished, canceling any pending update.
  281. reportNumberOfSearchResultsDidChange.call(this);
  282. }
  283. }
  284. // Start the search.
  285. boundBatchSearch();
  286. },
  287. addSearchResults: function(textRanges)
  288. {
  289. console.assert(textRanges);
  290. if (!textRanges || !textRanges.length)
  291. return;
  292. function markRanges()
  293. {
  294. for (var i = 0; i < textRanges.length; ++i) {
  295. var position = this._codeMirrorPositionFromTextRange(textRanges[i]);
  296. var mark = this._codeMirror.markText(position.start, position.end, {className: WebInspector.TextEditor.SearchResultStyleClassName});
  297. this._searchResults.push(mark);
  298. }
  299. // If we haven't shown a search result yet, reveal one now.
  300. if (this._automaticallyRevealFirstSearchResult) {
  301. if (this._currentSearchResultIndex === -1)
  302. this._revealFirstSearchResultAfterCursor();
  303. }
  304. }
  305. this._codeMirror.operation(markRanges.bind(this));
  306. },
  307. searchCleared: function()
  308. {
  309. function clearResults() {
  310. for (var i = 0; i < this._searchResults.length; ++i)
  311. this._searchResults[i].clear();
  312. }
  313. this._codeMirror.operation(clearResults.bind(this));
  314. this._searchQuery = null;
  315. this._searchResults = [];
  316. this._currentSearchResultIndex = -1;
  317. },
  318. searchQueryWithSelection: function()
  319. {
  320. if (!this._codeMirror.somethingSelected())
  321. return null;
  322. return this._codeMirror.getSelection();
  323. },
  324. revealPreviousSearchResult: function(changeFocus)
  325. {
  326. if (!this._searchResults.length)
  327. return;
  328. if (this._currentSearchResultIndex === -1 || this._cursorDoesNotMatchLastRevealedSearchResult()) {
  329. this._revealFirstSearchResultBeforeCursor(changeFocus);
  330. return;
  331. }
  332. if (this._currentSearchResultIndex > 0)
  333. --this._currentSearchResultIndex;
  334. else
  335. this._currentSearchResultIndex = this._searchResults.length - 1;
  336. this._revealSearchResult(this._searchResults[this._currentSearchResultIndex], changeFocus, -1);
  337. },
  338. revealNextSearchResult: function(changeFocus)
  339. {
  340. if (!this._searchResults.length)
  341. return;
  342. if (this._currentSearchResultIndex === -1 || this._cursorDoesNotMatchLastRevealedSearchResult()) {
  343. this._revealFirstSearchResultAfterCursor(changeFocus);
  344. return;
  345. }
  346. if (this._currentSearchResultIndex + 1 < this._searchResults.length)
  347. ++this._currentSearchResultIndex;
  348. else
  349. this._currentSearchResultIndex = 0;
  350. this._revealSearchResult(this._searchResults[this._currentSearchResultIndex], changeFocus, 1);
  351. },
  352. line: function(lineNumber)
  353. {
  354. return this._codeMirror.getLine(lineNumber);
  355. },
  356. revealPosition: function(position, textRangeToSelect, forceUnformatted)
  357. {
  358. console.assert(position === undefined || position instanceof WebInspector.SourceCodePosition, "revealPosition called without a SourceCodePosition");
  359. if (!(position instanceof WebInspector.SourceCodePosition))
  360. return;
  361. var lineHandle = this._codeMirror.getLineHandle(position.lineNumber);
  362. if (!lineHandle || !this._visible || this._initialStringNotSet) {
  363. // If we can't get a line handle or are not visible then we wait to do the reveal.
  364. this._positionToReveal = position;
  365. this._textRangeToSelect = textRangeToSelect;
  366. this._forceUnformatted = forceUnformatted;
  367. return;
  368. }
  369. // Delete now that the reveal is happening.
  370. delete this._positionToReveal;
  371. delete this._textRangeToSelect;
  372. delete this._forceUnformatted;
  373. // If we need to unformat, reveal the line after a wait.
  374. // Otherwise the line highlight doesn't work properly.
  375. if (this._formatted && forceUnformatted) {
  376. this.formatted = false;
  377. setTimeout(this.revealPosition.bind(this), 0, position, textRangeToSelect);
  378. return;
  379. }
  380. if (!textRangeToSelect)
  381. textRangeToSelect = new WebInspector.TextRange(position.lineNumber, position.columnNumber, position.lineNumber, position.columnNumber);
  382. function removeStyleClass()
  383. {
  384. this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.HighlightedStyleClassName);
  385. }
  386. function revealAndHighlightLine()
  387. {
  388. // If the line is not visible, reveal it as the center line in the editor.
  389. var position = this._codeMirrorPositionFromTextRange(textRangeToSelect);
  390. if (!this._isPositionVisible(position.start))
  391. this._scrollIntoViewCentered(position.start);
  392. this.selectedTextRange = textRangeToSelect;
  393. this._codeMirror.addLineClass(lineHandle, "wrap", WebInspector.TextEditor.HighlightedStyleClassName);
  394. // Use a timeout instead of a webkitAnimationEnd event listener because the line element might
  395. // be removed if the user scrolls during the animation. In that case webkitAnimationEnd isn't
  396. // fired, and the line would highlight again the next time it scrolls into view.
  397. setTimeout(removeStyleClass.bind(this), WebInspector.TextEditor.HighlightAnimationDuration);
  398. }
  399. this._codeMirror.operation(revealAndHighlightLine.bind(this));
  400. },
  401. updateLayout: function(force)
  402. {
  403. this._codeMirror.refresh();
  404. },
  405. shown: function()
  406. {
  407. this._visible = true;
  408. // Refresh since our size might have changed.
  409. this._codeMirror.refresh();
  410. // Try revealing the pending line now that we are visible.
  411. // This needs to be done as a separate operation from the refresh
  412. // so that the scrollInfo coordinates are correct.
  413. this._revealPendingPositionIfPossible();
  414. },
  415. hidden: function()
  416. {
  417. this._visible = false;
  418. },
  419. setBreakpointInfoForLineAndColumn: function(lineNumber, columnNumber, breakpointInfo)
  420. {
  421. if (this._ignoreSetBreakpointInfoCalls)
  422. return;
  423. if (breakpointInfo)
  424. this._addBreakpointToLineAndColumnWithInfo(lineNumber, columnNumber, breakpointInfo);
  425. else
  426. this._removeBreakpointFromLineAndColumn(lineNumber, columnNumber);
  427. },
  428. updateBreakpointLineAndColumn: function(oldLineNumber, oldColumnNumber, newLineNumber, newColumnNumber)
  429. {
  430. console.assert(this._breakpoints[oldLineNumber]);
  431. if (!this._breakpoints[oldLineNumber])
  432. return;
  433. console.assert(this._breakpoints[oldLineNumber][oldColumnNumber]);
  434. if (!this._breakpoints[oldLineNumber][oldColumnNumber])
  435. return;
  436. var breakpointInfo = this._breakpoints[oldLineNumber][oldColumnNumber];
  437. this._removeBreakpointFromLineAndColumn(oldLineNumber, oldColumnNumber);
  438. this._addBreakpointToLineAndColumnWithInfo(newLineNumber, newColumnNumber, breakpointInfo);
  439. },
  440. addStyleClassToLine: function(lineNumber, styleClassName)
  441. {
  442. var lineHandle = this._codeMirror.getLineHandle(lineNumber);
  443. console.assert(lineHandle);
  444. if (!lineHandle)
  445. return;
  446. return this._codeMirror.addLineClass(lineHandle, "wrap", styleClassName);
  447. },
  448. removeStyleClassFromLine: function(lineNumber, styleClassName)
  449. {
  450. var lineHandle = this._codeMirror.getLineHandle(lineNumber);
  451. console.assert(lineHandle);
  452. if (!lineHandle)
  453. return;
  454. return this._codeMirror.removeLineClass(lineHandle, "wrap", styleClassName);
  455. },
  456. toggleStyleClassForLine: function(lineNumber, styleClassName)
  457. {
  458. var lineHandle = this._codeMirror.getLineHandle(lineNumber);
  459. console.assert(lineHandle);
  460. if (!lineHandle)
  461. return;
  462. return this._codeMirror.toggleLineClass(lineHandle, "wrap", styleClassName);
  463. },
  464. // Private
  465. _updateCodeMirrorReadOnly: function()
  466. {
  467. this._codeMirror.setOption("readOnly", this._readOnly || this._formatted);
  468. },
  469. _contentChanged: function(codeMirror, change)
  470. {
  471. if (this._ignoreCodeMirrorContentDidChangeEvent)
  472. return;
  473. this.dispatchEventToListeners(WebInspector.TextEditor.Event.ContentDidChange);
  474. },
  475. _textRangeFromCodeMirrorPosition: function(start, end)
  476. {
  477. console.assert(start);
  478. console.assert(end);
  479. return new WebInspector.TextRange(start.line, start.ch, end.line, end.ch);
  480. },
  481. _codeMirrorPositionFromTextRange: function(textRange)
  482. {
  483. console.assert(textRange);
  484. var start = {line: textRange.startLine, ch: textRange.startColumn};
  485. var end = {line: textRange.endLine, ch: textRange.endColumn};
  486. return {start: start, end: end};
  487. },
  488. _revealPendingPositionIfPossible: function()
  489. {
  490. // Nothing to do if we don't have a pending position.
  491. if (!this._positionToReveal)
  492. return;
  493. // Don't try to reveal unless we are visible.
  494. if (!this._visible)
  495. return;
  496. this.revealPosition(this._positionToReveal, this._textRangeToSelect, this._forceUnformatted);
  497. },
  498. _revealSearchResult: function(result, changeFocus, directionInCaseOfRevalidation)
  499. {
  500. var position = result.find();
  501. // Check for a valid position, it might have been removed from editing by the user.
  502. // If the position is invalide, revalidate all positions reveal as needed.
  503. if (!position) {
  504. this._revalidateSearchResults(directionInCaseOfRevalidation);
  505. return;
  506. }
  507. // If the line is not visible, reveal it as the center line in the editor.
  508. if (!this._isPositionVisible(position.from))
  509. this._scrollIntoViewCentered(position.from);
  510. // Update the text selection to select the search result.
  511. this.selectedTextRange = this._textRangeFromCodeMirrorPosition(position.from, position.to);
  512. // Remove the automatically reveal state now that we have revealed a search result.
  513. this._automaticallyRevealFirstSearchResult = false;
  514. // Focus the editor if requested.
  515. if (changeFocus)
  516. this._codeMirror.focus();
  517. // Remove the bouncy highlight if it is still around. The animation will not
  518. // start unless we remove it and add it back to the document.
  519. if (this._bouncyHighlightElement)
  520. this._bouncyHighlightElement.remove();
  521. // Create the bouncy highlight.
  522. this._bouncyHighlightElement = document.createElement("div");
  523. this._bouncyHighlightElement.className = WebInspector.TextEditor.BouncyHighlightStyleClassName;
  524. // Collect info for the bouncy highlight.
  525. var textContent = this._codeMirror.getSelection();
  526. var coordinates = this._codeMirror.cursorCoords(true, "page");
  527. // Adjust the coordinates to be based in the text editor's space.
  528. var textEditorRect = this._element.getBoundingClientRect();
  529. coordinates.top -= textEditorRect.top;
  530. coordinates.left -= textEditorRect.left;
  531. // Position and show the bouncy highlight.
  532. this._bouncyHighlightElement.textContent = textContent;
  533. this._bouncyHighlightElement.style.top = coordinates.top + "px";
  534. this._bouncyHighlightElement.style.left = coordinates.left + "px";
  535. this._element.appendChild(this._bouncyHighlightElement);
  536. function animationEnded()
  537. {
  538. if (!this._bouncyHighlightElement)
  539. return;
  540. this._bouncyHighlightElement.remove();
  541. delete this._bouncyHighlightElement;
  542. }
  543. // Listen for the end of the animation so we can remove the element.
  544. this._bouncyHighlightElement.addEventListener("webkitAnimationEnd", animationEnded.bind(this));
  545. },
  546. _binarySearchInsertionIndexInSearchResults: function(object, comparator)
  547. {
  548. // It is possible that markers in the search results array may have been deleted.
  549. // In those cases the comparator will return "null" and we immediately stop
  550. // the binary search and return null. The search results list needs to be updated.
  551. var array = this._searchResults;
  552. var first = 0;
  553. var last = array.length - 1;
  554. while (first <= last) {
  555. var mid = (first + last) >> 1;
  556. var c = comparator(object, array[mid]);
  557. if (c === null)
  558. return null;
  559. if (c > 0)
  560. first = mid + 1;
  561. else if (c < 0)
  562. last = mid - 1;
  563. else
  564. return mid;
  565. }
  566. return first - 1;
  567. },
  568. _revealFirstSearchResultBeforeCursor: function(changeFocus)
  569. {
  570. console.assert(this._searchResults.length);
  571. var currentCursorPosition = this._codeMirror.getCursor("start");
  572. if (currentCursorPosition.line === 0 && currentCursorPosition.ch === 0) {
  573. this._currentSearchResultIndex = this._searchResults.length - 1;
  574. this._revealSearchResult(this._searchResults[this._currentSearchResultIndex], changeFocus, -1);
  575. return;
  576. }
  577. var index = this._binarySearchInsertionIndexInSearchResults(currentCursorPosition, function(current, searchResult) {
  578. var searchResultMarker = searchResult.find();
  579. if (!searchResultMarker)
  580. return null;
  581. return WebInspector.compareCodeMirrorPositions(current, searchResultMarker.from);
  582. });
  583. if (index === null) {
  584. this._revalidateSearchResults(-1);
  585. return;
  586. }
  587. this._currentSearchResultIndex = index;
  588. this._revealSearchResult(this._searchResults[this._currentSearchResultIndex], changeFocus);
  589. },
  590. _revealFirstSearchResultAfterCursor: function(changeFocus)
  591. {
  592. console.assert(this._searchResults.length);
  593. var currentCursorPosition = this._codeMirror.getCursor("start");
  594. if (currentCursorPosition.line === 0 && currentCursorPosition.ch === 0) {
  595. this._currentSearchResultIndex = 0;
  596. this._revealSearchResult(this._searchResults[this._currentSearchResultIndex], changeFocus, 1);
  597. return;
  598. }
  599. var index = this._binarySearchInsertionIndexInSearchResults(currentCursorPosition, function(current, searchResult) {
  600. var searchResultMarker = searchResult.find();
  601. if (!searchResultMarker)
  602. return null;
  603. return WebInspector.compareCodeMirrorPositions(current, searchResultMarker.from);
  604. });
  605. if (index === null) {
  606. this._revalidateSearchResults(1);
  607. return;
  608. }
  609. if (index + 1 < this._searchResults.length)
  610. ++index;
  611. else
  612. index = 0;
  613. this._currentSearchResultIndex = index;
  614. this._revealSearchResult(this._searchResults[this._currentSearchResultIndex], changeFocus);
  615. },
  616. _cursorDoesNotMatchLastRevealedSearchResult: function()
  617. {
  618. console.assert(this._currentSearchResultIndex !== -1);
  619. console.assert(this._searchResults.length);
  620. var lastRevealedSearchResultMarker = this._searchResults[this._currentSearchResultIndex].find();
  621. if (!lastRevealedSearchResultMarker)
  622. return true;
  623. var currentCursorPosition = this._codeMirror.getCursor("start");
  624. var lastRevealedSearchResultPosition = lastRevealedSearchResultMarker.from;
  625. return WebInspector.compareCodeMirrorPositions(currentCursorPosition, lastRevealedSearchResultPosition) !== 0;
  626. },
  627. _revalidateSearchResults: function(direction)
  628. {
  629. console.assert(direction !== undefined);
  630. this._currentSearchResultIndex = -1;
  631. var updatedSearchResults = [];
  632. for (var i = 0; i < this._searchResults.length; ++i) {
  633. if (this._searchResults[i].find())
  634. updatedSearchResults.push(this._searchResults[i]);
  635. }
  636. console.assert(updatedSearchResults.length !== this._searchResults.length);
  637. this._searchResults = updatedSearchResults;
  638. this.dispatchEventToListeners(WebInspector.TextEditor.Event.NumberOfSearchResultsDidChange);
  639. if (this._searchResults.length) {
  640. if (direction > 0)
  641. this._revealFirstSearchResultAfterCursor();
  642. else
  643. this._revealFirstSearchResultBeforeCursor();
  644. }
  645. },
  646. _updateExecutionLine: function()
  647. {
  648. function update()
  649. {
  650. if (this._executionLineHandle)
  651. this._codeMirror.removeLineClass(this._executionLineHandle, "wrap", WebInspector.TextEditor.ExecutionLineStyleClassName);
  652. this._executionLineHandle = !isNaN(this._executionLineNumber) ? this._codeMirror.getLineHandle(this._executionLineNumber) : null;
  653. if (this._executionLineHandle)
  654. this._codeMirror.addLineClass(this._executionLineHandle, "wrap", WebInspector.TextEditor.ExecutionLineStyleClassName);
  655. }
  656. this._codeMirror.operation(update.bind(this));
  657. },
  658. _setBreakpointStylesOnLine: function(lineNumber)
  659. {
  660. var columnBreakpoints = this._breakpoints[lineNumber];
  661. console.assert(columnBreakpoints);
  662. if (!columnBreakpoints)
  663. return;
  664. var allDisabled = true;
  665. var allResolved = true;
  666. var multiple = Object.keys(columnBreakpoints).length > 1;
  667. for (var columnNumber in columnBreakpoints) {
  668. var breakpointInfo = columnBreakpoints[columnNumber];
  669. if (!breakpointInfo.disabled)
  670. allDisabled = false;
  671. if (!breakpointInfo.resolved)
  672. allResolved = false;
  673. }
  674. function updateStyles()
  675. {
  676. // We might not have a line if the content isn't fully populated yet.
  677. // This will be called again when the content is available.
  678. var lineHandle = this._codeMirror.getLineHandle(lineNumber);
  679. if (!lineHandle)
  680. return;
  681. this._codeMirror.addLineClass(lineHandle, "wrap", WebInspector.TextEditor.HasBreakpointStyleClassName);
  682. if (allResolved)
  683. this._codeMirror.addLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointResolvedStyleClassName);
  684. else
  685. this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointResolvedStyleClassName);
  686. if (allDisabled)
  687. this._codeMirror.addLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointDisabledStyleClassName);
  688. else
  689. this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointDisabledStyleClassName);
  690. if (multiple)
  691. this._codeMirror.addLineClass(lineHandle, "wrap", WebInspector.TextEditor.MultipleBreakpointsStyleClassName);
  692. else
  693. this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.MultipleBreakpointsStyleClassName);
  694. }
  695. this._codeMirror.operation(updateStyles.bind(this));
  696. },
  697. _addBreakpointToLineAndColumnWithInfo: function(lineNumber, columnNumber, breakpointInfo)
  698. {
  699. if (!this._breakpoints[lineNumber])
  700. this._breakpoints[lineNumber] = {};
  701. this._breakpoints[lineNumber][columnNumber] = breakpointInfo;
  702. this._setBreakpointStylesOnLine(lineNumber);
  703. },
  704. _removeBreakpointFromLineAndColumn: function(lineNumber, columnNumber)
  705. {
  706. console.assert(columnNumber in this._breakpoints[lineNumber]);
  707. delete this._breakpoints[lineNumber][columnNumber];
  708. // There are still breakpoints on the line. Update the breakpoint style.
  709. if (!isEmptyObject(this._breakpoints[lineNumber])) {
  710. this._setBreakpointStylesOnLine(lineNumber);
  711. return;
  712. }
  713. delete this._breakpoints[lineNumber];
  714. function updateStyles()
  715. {
  716. var lineHandle = this._codeMirror.getLineHandle(lineNumber);
  717. if (!lineHandle)
  718. return;
  719. this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.HasBreakpointStyleClassName);
  720. this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointResolvedStyleClassName);
  721. this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.BreakpointDisabledStyleClassName);
  722. this._codeMirror.removeLineClass(lineHandle, "wrap", WebInspector.TextEditor.MultipleBreakpointsStyleClassName);
  723. }
  724. this._codeMirror.operation(updateStyles.bind(this));
  725. },
  726. _allColumnBreakpointInfoForLine: function(lineNumber)
  727. {
  728. return this._breakpoints[lineNumber];
  729. },
  730. _setColumnBreakpointInfoForLine: function(lineNumber, columnBreakpointInfo)
  731. {
  732. console.assert(columnBreakpointInfo);
  733. this._breakpoints[lineNumber] = columnBreakpointInfo;
  734. this._setBreakpointStylesOnLine(lineNumber);
  735. },
  736. _gutterMouseDown: function(codeMirror, lineNumber, gutterElement, event)
  737. {
  738. if (event.button !== 0 || event.ctrlKey)
  739. return;
  740. if (!this._codeMirror.hasLineClass(lineNumber, "wrap", WebInspector.TextEditor.HasBreakpointStyleClassName)) {
  741. console.assert(!(lineNumber in this._breakpoints));
  742. // No breakpoint, add a new one.
  743. if (this._delegate && typeof this._delegate.textEditorBreakpointAdded === "function") {
  744. var data = this._delegate.textEditorBreakpointAdded(this, lineNumber, 0);
  745. if (data) {
  746. var breakpointInfo = data.breakpointInfo;
  747. if (breakpointInfo)
  748. this._addBreakpointToLineAndColumnWithInfo(data.lineNumber, data.columnNumber, breakpointInfo);
  749. }
  750. }
  751. return;
  752. }
  753. console.assert(lineNumber in this._breakpoints);
  754. if (this._codeMirror.hasLineClass(lineNumber, "wrap", WebInspector.TextEditor.MultipleBreakpointsStyleClassName)) {
  755. console.assert(!isEmptyObject(this._breakpoints[lineNumber]));
  756. return;
  757. }
  758. // Single existing breakpoint, start tracking it for dragging.
  759. console.assert(Object.keys(this._breakpoints[lineNumber]).length === 1);
  760. var columnNumber = Object.keys(this._breakpoints[lineNumber])[0];
  761. this._draggingBreakpointInfo = this._breakpoints[lineNumber][columnNumber];
  762. this._lineNumberWithMousedDownBreakpoint = lineNumber;
  763. this._lineNumberWithDraggedBreakpoint = lineNumber;
  764. this._columnNumberWithMousedDownBreakpoint = columnNumber;
  765. this._columnNumberWithDraggedBreakpoint = columnNumber;
  766. this._documentMouseMovedEventListener = this._documentMouseMoved.bind(this);
  767. this._documentMouseUpEventListener = this._documentMouseUp.bind(this);
  768. // Register these listeners on the document so we can track the mouse if it leaves the gutter.
  769. document.addEventListener("mousemove", this._documentMouseMovedEventListener, true);
  770. document.addEventListener("mouseup", this._documentMouseUpEventListener, true);
  771. },
  772. _documentMouseMoved: function(event)
  773. {
  774. console.assert("_lineNumberWithMousedDownBreakpoint" in this);
  775. if (!("_lineNumberWithMousedDownBreakpoint" in this))
  776. return;
  777. event.preventDefault();
  778. var lineNumber;
  779. var position = this._codeMirror.coordsChar({left: event.pageX, top: event.pageY});
  780. // CodeMirror's coordsChar returns a position even if it is outside the bounds. Nullify the position
  781. // if the event is outside the bounds of the gutter so we will remove the breakpoint.
  782. var gutterBounds = this._codeMirror.getGutterElement().getBoundingClientRect();
  783. if (event.pageX < gutterBounds.left || event.pageX > gutterBounds.right || event.pageY < gutterBounds.top || event.pageY > gutterBounds.bottom)
  784. position = null;
  785. // If we have a position and it has a line then use it.
  786. if (position && "line" in position)
  787. lineNumber = position.line;
  788. // The _lineNumberWithDraggedBreakpoint property can be undefined if the user drags
  789. // outside of the gutter. The lineNumber variable can be undefined for the same reason.
  790. if (lineNumber === this._lineNumberWithDraggedBreakpoint)
  791. return;
  792. // Record that the mouse dragged some so when mouse up fires we know to do the
  793. // work of removing and moving the breakpoint.
  794. this._mouseDragged = true;
  795. if ("_lineNumberWithDraggedBreakpoint" in this) {
  796. // We have a line that is currently showing the dragged breakpoint. Remove that breakpoint
  797. // and restore the previous one (if any.)
  798. if (this._previousColumnBreakpointInfo)
  799. this._setColumnBreakpointInfoForLine(this._lineNumberWithDraggedBreakpoint, this._previousColumnBreakpointInfo);
  800. else
  801. this._removeBreakpointFromLineAndColumn(this._lineNumberWithDraggedBreakpoint, this._columnNumberWithDraggedBreakpoint);
  802. delete this._previousColumnBreakpointInfo;
  803. delete this._lineNumberWithDraggedBreakpoint;
  804. delete this._columnNumberWithDraggedBreakpoint;
  805. }
  806. if (lineNumber !== undefined) {
  807. // We have a new line that will now show the dragged breakpoint.
  808. var newColumnBreakpoints = {};
  809. var columnNumber = (lineNumber === this._lineNumberWithMousedDownBreakpoint ? this._columnNumberWithDraggedBreakpoint : 0)
  810. newColumnBreakpoints[columnNumber] = this._draggingBreakpointInfo;
  811. this._previousColumnBreakpointInfo = this._allColumnBreakpointInfoForLine(lineNumber);
  812. this._setColumnBreakpointInfoForLine(lineNumber, newColumnBreakpoints);
  813. this._lineNumberWithDraggedBreakpoint = lineNumber;
  814. this._columnNumberWithDraggedBreakpoint = columnNumber;
  815. }
  816. },
  817. _documentMouseUp: function(event)
  818. {
  819. console.assert("_lineNumberWithMousedDownBreakpoint" in this);
  820. if (!("_lineNumberWithMousedDownBreakpoint" in this))
  821. return;
  822. event.preventDefault();
  823. document.removeEventListener("mousemove", this._documentMouseMovedEventListener, true);
  824. document.removeEventListener("mouseup", this._documentMouseUpEventListener, true);
  825. const delegateImplementsBreakpointToggled = this._delegate && typeof this._delegate.textEditorBreakpointToggled === "function";
  826. const delegateImplementsBreakpointRemoved = this._delegate && typeof this._delegate.textEditorBreakpointRemoved === "function";
  827. const delegateImplementsBreakpointMoved = this._delegate && typeof this._delegate.textEditorBreakpointMoved === "function";
  828. if (this._mouseDragged) {
  829. if (!("_lineNumberWithDraggedBreakpoint" in this)) {
  830. // The breakpoint was dragged off the gutter, remove it.
  831. if (delegateImplementsBreakpointRemoved) {
  832. this._ignoreSetBreakpointInfoCalls = true;
  833. this._delegate.textEditorBreakpointRemoved(this, this._lineNumberWithMousedDownBreakpoint, this._columnNumberWithMousedDownBreakpoint);
  834. delete this._ignoreSetBreakpointInfoCalls;
  835. }
  836. } else if (this._lineNumberWithMousedDownBreakpoint !== this._lineNumberWithDraggedBreakpoint) {
  837. // The dragged breakpoint was moved to a new line.
  838. // If there is are breakpoints already at the drop line, tell the delegate to remove them.
  839. // We have already updated the breakpoint info internally, so when the delegate removes the breakpoints
  840. // and tells us to clear the breakpoint info, we can ignore those calls.
  841. if (this._previousColumnBreakpointInfo && delegateImplementsBreakpointRemoved) {
  842. this._ignoreSetBreakpointInfoCalls = true;
  843. for (var columnNumber in this._previousColumnBreakpointInfo)
  844. this._delegate.textEditorBreakpointRemoved(this, this._lineNumberWithDraggedBreakpoint, columnNumber);
  845. delete this._ignoreSetBreakpointInfoCalls;
  846. }
  847. // Tell the delegate to move the breakpoint from one line to another.
  848. if (delegateImplementsBreakpointMoved) {
  849. this._ignoreSetBreakpointInfoCalls = true;
  850. this._delegate.textEditorBreakpointMoved(this, this._lineNumberWithMousedDownBreakpoint, this._columnNumberWithMousedDownBreakpoint, this._lineNumberWithDraggedBreakpoint, this._columnNumberWithDraggedBreakpoint);
  851. delete this._ignoreSetBreakpointInfoCalls;
  852. }
  853. }
  854. } else {
  855. // Toggle the disabled state of the breakpoint.
  856. console.assert(this._lineNumberWithMousedDownBreakpoint in this._breakpoints);
  857. console.assert(this._columnNumberWithMousedDownBreakpoint in this._breakpoints[this._lineNumberWithMousedDownBreakpoint]);
  858. if (this._lineNumberWithMousedDownBreakpoint in this._breakpoints && this._columnNumberWithMousedDownBreakpoint in this._breakpoints[this._lineNumberWithMousedDownBreakpoint] && delegateImplementsBreakpointToggled) {
  859. var disabled = this._codeMirror.toggleLineClass(this._lineNumberWithMousedDownBreakpoint, "wrap", WebInspector.TextEditor.BreakpointDisabledStyleClassName);
  860. this._breakpoints[this._lineNumberWithMousedDownBreakpoint][this._columnNumberWithMousedDownBreakpoint].disabled = disabled;
  861. this._delegate.textEditorBreakpointToggled(this, this._lineNumberWithMousedDownBreakpoint, this._columnNumberWithMousedDownBreakpoint, disabled);
  862. }
  863. }
  864. delete this._documentMouseMovedEventListener;
  865. delete this._documentMouseUpEventListener;
  866. delete this._lineNumberWithMousedDownBreakpoint;
  867. delete this._lineNumberWithDraggedBreakpoint;
  868. delete this._columnNumberWithMousedDownBreakpoint;
  869. delete this._columnNumberWithDraggedBreakpoint;
  870. delete this._previousColumnBreakpointInfo;
  871. delete this._mouseDragged;
  872. },
  873. _openClickedLinks: function(event)
  874. {
  875. // Get the position in the text and the token at that position.
  876. var position = this._codeMirror.coordsChar({left: event.pageX, top: event.pageY});
  877. var tokenInfo = this._codeMirror.getTokenAt(position);
  878. if (!tokenInfo || !tokenInfo.type || !tokenInfo.string)
  879. return;
  880. // If the token is not a link, then ignore it.
  881. if (!/\blink\b/.test(tokenInfo.type))
  882. return;
  883. // The token string is the URL we should open. It might be a relative URL.
  884. var url = tokenInfo.string;
  885. // Get the base URL.
  886. var baseURL = "";
  887. if (this._delegate && typeof this._delegate.textEditorBaseURL === "function")
  888. baseURL = this._delegate.textEditorBaseURL(this);
  889. // Open the link after resolving the absolute URL from the base URL.
  890. WebInspector.openURL(absoluteURL(url, baseURL));
  891. // Stop processing the event.
  892. event.preventDefault();
  893. event.stopPropagation();
  894. },
  895. _isPositionVisible: function(position)
  896. {
  897. var scrollInfo = this._codeMirror.getScrollInfo();
  898. var visibleRangeStart = scrollInfo.top;
  899. var visibleRangeEnd = visibleRangeStart + scrollInfo.clientHeight;
  900. var coords = this._codeMirror.charCoords(position, "local");
  901. return coords.top >= visibleRangeStart && coords.bottom <= visibleRangeEnd;
  902. },
  903. _scrollIntoViewCentered: function(position)
  904. {
  905. var scrollInfo = this._codeMirror.getScrollInfo();
  906. var lineHeight = Math.ceil(this._codeMirror.defaultTextHeight());
  907. var margin = Math.floor((scrollInfo.clientHeight - lineHeight) / 2);
  908. this._codeMirror.scrollIntoView(position, margin);
  909. },
  910. _prettyPrint: function(pretty)
  911. {
  912. function prettyPrintAndUpdateEditor()
  913. {
  914. const start = {line: 0, ch: 0};
  915. const end = {line: this._codeMirror.lineCount() - 1};
  916. var oldSelectionAnchor = this._codeMirror.getCursor("anchor");
  917. var oldSelectionHead = this._codeMirror.getCursor("head");
  918. var newSelectionAnchor, newSelectionHead;
  919. var newExecutionLocation = null;
  920. if (pretty) {
  921. // <rdar://problem/10593948> Provide a way to change the tab width in the Web Inspector
  922. const indentString = " ";
  923. var originalLineEndings = [];
  924. var formattedLineEndings = [];
  925. var mapping = {original: [0], formatted: [0]};
  926. var builder = new FormatterContentBuilder(mapping, originalLineEndings, formattedLineEndings, 0, 0, indentString);
  927. var formatter = new Formatter(this._codeMirror, builder);
  928. formatter.format(start, end);
  929. this._formatterSourceMap = WebInspector.FormatterSourceMap.fromBuilder(builder);
  930. this._codeMirror.setValue(builder.formattedContent);
  931. if (this._positionToReveal) {
  932. var newRevealPosition = this._formatterSourceMap.originalToFormatted(this._positionToReveal.lineNumber, this._positionToReveal.columnNumber);
  933. this._positionToReveal = new WebInspector.SourceCodePosition(newRevealPosition.lineNumber, newRevealPosition.columnNumber);
  934. }
  935. if (this._textRangeToSelect) {
  936. var mappedRevealSelectionStart = this._formatterSourceMap.originalToFormatted(this._textRangeToSelect.startLine, this._textRangeToSelect.startColumn);
  937. var mappedRevealSelectionEnd = this._formatterSourceMap.originalToFormatted(this._textRangeToSelect.endLine, this._textRangeToSelect.endColumn);
  938. this._textRangeToSelect = new WebInspector.TextRange(mappedRevealSelectionStart.lineNumber, mappedRevealSelectionStart.columnNumber, mappedRevealSelectionEnd.lineNumber, mappedRevealSelectionEnd.columnNumber);
  939. }
  940. if (!isNaN(this._executionLineNumber)) {
  941. console.assert(this._executionLineHandle);
  942. console.assert(!isNaN(this._executionColumnNumber));
  943. newExecutionLocation = this._formatterSourceMap.originalToFormatted(this._executionLineNumber, this._executionColumnNumber);
  944. }
  945. var mappedAnchorLocation = this._formatterSourceMap.originalToFormatted(oldSelectionAnchor.line, oldSelectionAnchor.ch);
  946. var mappedHeadLocation = this._formatterSourceMap.originalToFormatted(oldSelectionHead.line, oldSelectionHead.ch);
  947. newSelectionAnchor = {line:mappedAnchorLocation.lineNumber, ch:mappedAnchorLocation.columnNumber};
  948. newSelectionHead = {line:mappedHeadLocation.lineNumber, ch:mappedHeadLocation.columnNumber};
  949. } else {
  950. this._codeMirror.undo();
  951. if (this._positionToReveal) {
  952. var newRevealPosition = this._formatterSourceMap.formattedToOriginal(this._positionToReveal.lineNumber, this._positionToReveal.columnNumber);
  953. this._positionToReveal = new WebInspector.SourceCodePosition(newRevealPosition.lineNumber, newRevealPosition.columnNumber);
  954. }
  955. if (this._textRangeToSelect) {
  956. var mappedRevealSelectionStart = this._formatterSourceMap.formattedToOriginal(this._textRangeToSelect.startLine, this._textRangeToSelect.startColumn);
  957. var mappedRevealSelectionEnd = this._formatterSourceMap.formattedToOriginal(this._textRangeToSelect.endLine, this._textRangeToSelect.endColumn);
  958. this._textRangeToSelect = new WebInspector.TextRange(mappedRevealSelectionStart.lineNumber, mappedRevealSelectionStart.columnNumber, mappedRevealSelectionEnd.lineNumber, mappedRevealSelectionEnd.columnNumber);
  959. }
  960. if (!isNaN(this._executionLineNumber)) {
  961. console.assert(this._executionLineHandle);
  962. console.assert(!isNaN(this._executionColumnNumber));
  963. newExecutionLocation = this._formatterSourceMap.formattedToOriginal(this._executionLineNumber, this._executionColumnNumber);
  964. }
  965. var mappedAnchorLocation = this._formatterSourceMap.formattedToOriginal(oldSelectionAnchor.line, oldSelectionAnchor.ch);
  966. var mappedHeadLocation = this._formatterSourceMap.formattedToOriginal(oldSelectionHead.line, oldSelectionHead.ch);
  967. newSelectionAnchor = {line:mappedAnchorLocation.lineNumber, ch:mappedAnchorLocation.columnNumber};
  968. newSelectionHead = {line:mappedHeadLocation.lineNumber, ch:mappedHeadLocation.columnNumber};
  969. this._formatterSourceMap = null;
  970. }
  971. this._scrollIntoViewCentered(newSelectionAnchor);
  972. this._codeMirror.setSelection(newSelectionAnchor, newSelectionHead);
  973. if (newExecutionLocation) {
  974. delete this._executionLineHandle;
  975. this.executionColumnNumber = newExecutionLocation.columnNumber;
  976. this.executionLineNumber = newExecutionLocation.lineNumber;
  977. }
  978. // FIXME: <rdar://problem/13129955> FindBanner: New searches should not lose search position (start from current selection/caret)
  979. if (this.currentSearchQuery) {
  980. var searchQuery = this.currentSearchQuery;
  981. this.searchCleared();
  982. // Set timeout so that this happens after the current CodeMirror operation.
  983. // The editor has to update for the value and selection changes.
  984. setTimeout(function(query) {
  985. this.performSearch(searchQuery);
  986. }.bind(this), 0);
  987. }
  988. if (this._delegate && typeof this._delegate.textEditorUpdatedFormatting === "function")
  989. this._delegate.textEditorUpdatedFormatting(this);
  990. }
  991. this._codeMirror.operation(prettyPrintAndUpdateEditor.bind(this));
  992. }
  993. };
  994. WebInspector.TextEditor.prototype.__proto__ = WebInspector.Object.prototype;