PageRenderTime 59ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/WebCore/inspector/front-end/TextViewer.js

https://bitbucket.org/sevear/webkit-ios
JavaScript | 2000 lines | 1608 code | 308 blank | 84 comment | 325 complexity | 3f48458149bf889163c9b4564543f5e8 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.0, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. /*
  2. * Copyright (C) 2011 Google Inc. All rights reserved.
  3. * Copyright (C) 2010 Apple Inc. All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are
  7. * met:
  8. *
  9. * * Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * * Redistributions in binary form must reproduce the above
  12. * copyright notice, this list of conditions and the following disclaimer
  13. * in the documentation and/or other materials provided with the
  14. * distribution.
  15. * * Neither the name of Google Inc. nor the names of its
  16. * contributors may be used to endorse or promote products derived from
  17. * this software without specific prior written permission.
  18. *
  19. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  20. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  21. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  22. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  23. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  24. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  25. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  26. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  27. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  29. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. */
  31. WebInspector.TextViewer = function(textModel, platform, url, delegate)
  32. {
  33. WebInspector.View.call(this);
  34. this._textModel = textModel;
  35. this._textModel.changeListener = this._textChanged.bind(this);
  36. this._textModel.resetUndoStack();
  37. this._delegate = delegate;
  38. this.element.className = "text-editor monospace";
  39. var enterTextChangeMode = this._enterInternalTextChangeMode.bind(this);
  40. var exitTextChangeMode = this._exitInternalTextChangeMode.bind(this);
  41. var syncScrollListener = this._syncScroll.bind(this);
  42. var syncDecorationsForLineListener = this._syncDecorationsForLine.bind(this);
  43. this._mainPanel = new WebInspector.TextEditorMainPanel(this._textModel, url, syncScrollListener, syncDecorationsForLineListener, enterTextChangeMode, exitTextChangeMode);
  44. this._gutterPanel = new WebInspector.TextEditorGutterPanel(this._textModel, syncDecorationsForLineListener);
  45. this.element.appendChild(this._mainPanel.element);
  46. this.element.appendChild(this._gutterPanel.element);
  47. // Forward mouse wheel events from the unscrollable gutter to the main panel.
  48. this._gutterPanel.element.addEventListener("mousewheel", function(e) {
  49. this._mainPanel.element.dispatchEvent(e);
  50. }.bind(this), false);
  51. this.element.addEventListener("dblclick", this._doubleClick.bind(this), true);
  52. this.element.addEventListener("keydown", this._handleKeyDown.bind(this), false);
  53. this.element.addEventListener("contextmenu", this._contextMenu.bind(this), true);
  54. this._registerShortcuts();
  55. }
  56. WebInspector.TextViewer.prototype = {
  57. set mimeType(mimeType)
  58. {
  59. this._mainPanel.mimeType = mimeType;
  60. },
  61. set readOnly(readOnly)
  62. {
  63. if (this._mainPanel.readOnly === readOnly)
  64. return;
  65. this._mainPanel.readOnly = readOnly;
  66. },
  67. get readOnly()
  68. {
  69. return this._mainPanel.readOnly;
  70. },
  71. get textModel()
  72. {
  73. return this._textModel;
  74. },
  75. revealLine: function(lineNumber)
  76. {
  77. this._mainPanel.revealLine(lineNumber);
  78. },
  79. addDecoration: function(lineNumber, decoration)
  80. {
  81. this._mainPanel.addDecoration(lineNumber, decoration);
  82. this._gutterPanel.addDecoration(lineNumber, decoration);
  83. },
  84. removeDecoration: function(lineNumber, decoration)
  85. {
  86. this._mainPanel.removeDecoration(lineNumber, decoration);
  87. this._gutterPanel.removeDecoration(lineNumber, decoration);
  88. },
  89. markAndRevealRange: function(range)
  90. {
  91. this._mainPanel.markAndRevealRange(range);
  92. },
  93. highlightLine: function(lineNumber)
  94. {
  95. if (typeof lineNumber !== "number" || lineNumber < 0)
  96. return;
  97. lineNumber = Math.min(lineNumber, this._textModel.linesCount - 1);
  98. this._mainPanel.highlightLine(lineNumber);
  99. },
  100. clearLineHighlight: function()
  101. {
  102. this._mainPanel.clearLineHighlight();
  103. },
  104. freeCachedElements: function()
  105. {
  106. this._mainPanel.freeCachedElements();
  107. this._gutterPanel.freeCachedElements();
  108. },
  109. get scrollTop()
  110. {
  111. return this._mainPanel.element.scrollTop;
  112. },
  113. set scrollTop(scrollTop)
  114. {
  115. this._mainPanel.element.scrollTop = scrollTop;
  116. },
  117. get scrollLeft()
  118. {
  119. return this._mainPanel.element.scrollLeft;
  120. },
  121. set scrollLeft(scrollLeft)
  122. {
  123. this._mainPanel.element.scrollLeft = scrollLeft;
  124. },
  125. beginUpdates: function()
  126. {
  127. this._mainPanel.beginUpdates();
  128. this._gutterPanel.beginUpdates();
  129. },
  130. endUpdates: function()
  131. {
  132. this._mainPanel.endUpdates();
  133. this._gutterPanel.endUpdates();
  134. this._updatePanelOffsets();
  135. },
  136. resize: function()
  137. {
  138. this._mainPanel.resize();
  139. this._gutterPanel.resize();
  140. this._updatePanelOffsets();
  141. },
  142. // WebInspector.TextModel listener
  143. _textChanged: function(oldRange, newRange, oldText, newText)
  144. {
  145. if (!this._internalTextChangeMode)
  146. this._textModel.resetUndoStack();
  147. this._mainPanel.textChanged(oldRange, newRange);
  148. this._gutterPanel.textChanged(oldRange, newRange);
  149. this._updatePanelOffsets();
  150. },
  151. _enterInternalTextChangeMode: function()
  152. {
  153. this._internalTextChangeMode = true;
  154. this._delegate.beforeTextChanged();
  155. },
  156. _exitInternalTextChangeMode: function(oldRange, newRange)
  157. {
  158. this._internalTextChangeMode = false;
  159. this._delegate.afterTextChanged(oldRange, newRange);
  160. },
  161. _updatePanelOffsets: function()
  162. {
  163. var lineNumbersWidth = this._gutterPanel.element.offsetWidth;
  164. if (lineNumbersWidth)
  165. this._mainPanel.element.style.setProperty("left", lineNumbersWidth + "px");
  166. else
  167. this._mainPanel.element.style.removeProperty("left"); // Use default value set in CSS.
  168. },
  169. _syncScroll: function()
  170. {
  171. // Async call due to performance reasons.
  172. setTimeout(function() {
  173. var mainElement = this._mainPanel.element;
  174. var gutterElement = this._gutterPanel.element;
  175. // Handle horizontal scroll bar at the bottom of the main panel.
  176. this._gutterPanel.syncClientHeight(mainElement.clientHeight);
  177. gutterElement.scrollTop = mainElement.scrollTop;
  178. }.bind(this), 0);
  179. },
  180. _syncDecorationsForLine: function(lineNumber)
  181. {
  182. if (lineNumber >= this._textModel.linesCount)
  183. return;
  184. var mainChunk = this._mainPanel.chunkForLine(lineNumber);
  185. if (mainChunk.linesCount === 1 && mainChunk.decorated) {
  186. var gutterChunk = this._gutterPanel.makeLineAChunk(lineNumber);
  187. var height = mainChunk.height;
  188. if (height)
  189. gutterChunk.element.style.setProperty("height", height + "px");
  190. else
  191. gutterChunk.element.style.removeProperty("height");
  192. } else {
  193. var gutterChunk = this._gutterPanel.chunkForLine(lineNumber);
  194. if (gutterChunk.linesCount === 1)
  195. gutterChunk.element.style.removeProperty("height");
  196. }
  197. },
  198. _doubleClick: function(event)
  199. {
  200. if (!this.readOnly)
  201. return;
  202. var lineRow = event.target.enclosingNodeOrSelfWithClass("webkit-line-content");
  203. if (!lineRow)
  204. return; // Do not trigger editing from line numbers.
  205. this._delegate.doubleClick(lineRow.lineNumber);
  206. window.getSelection().collapseToStart();
  207. },
  208. _registerShortcuts: function()
  209. {
  210. var keys = WebInspector.KeyboardShortcut.Keys;
  211. var modifiers = WebInspector.KeyboardShortcut.Modifiers;
  212. this._shortcuts = {};
  213. var commitEditing = this._commitEditing.bind(this);
  214. var cancelEditing = this._cancelEditing.bind(this);
  215. this._shortcuts[WebInspector.KeyboardShortcut.makeKey("s", modifiers.CtrlOrMeta)] = commitEditing;
  216. this._shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Enter.code, modifiers.CtrlOrMeta)] = commitEditing;
  217. this._shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Esc.code)] = cancelEditing;
  218. var handleUndo = this._mainPanel.handleUndoRedo.bind(this._mainPanel, false);
  219. var handleRedo = this._mainPanel.handleUndoRedo.bind(this._mainPanel, true);
  220. this._shortcuts[WebInspector.KeyboardShortcut.makeKey("z", modifiers.CtrlOrMeta)] = handleUndo;
  221. this._shortcuts[WebInspector.KeyboardShortcut.makeKey("z", modifiers.Shift | modifiers.CtrlOrMeta)] = handleRedo;
  222. var handleTabKey = this._mainPanel.handleTabKeyPress.bind(this._mainPanel, false);
  223. var handleShiftTabKey = this._mainPanel.handleTabKeyPress.bind(this._mainPanel, true);
  224. this._shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Tab.code)] = handleTabKey;
  225. this._shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Tab.code, modifiers.Shift)] = handleShiftTabKey;
  226. },
  227. _handleKeyDown: function(e)
  228. {
  229. var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEvent(e);
  230. var handler = this._shortcuts[shortcutKey];
  231. if (handler && handler.call(this)) {
  232. e.preventDefault();
  233. e.stopPropagation();
  234. }
  235. },
  236. _contextMenu: function(event)
  237. {
  238. var contextMenu = new WebInspector.ContextMenu();
  239. var target = event.target.enclosingNodeOrSelfWithClass("webkit-line-number");
  240. if (target)
  241. this._delegate.populateLineGutterContextMenu(target.lineNumber, contextMenu);
  242. else {
  243. var selection = this._mainPanel._getSelection();
  244. if (selection && !selection.isEmpty())
  245. return; // Show default context menu for selection.
  246. this._delegate.populateTextAreaContextMenu(contextMenu);
  247. }
  248. var fileName = this._delegate.suggestedFileName();
  249. if (fileName)
  250. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save as..." : "Save As..."), InspectorFrontendHost.saveAs.bind(InspectorFrontendHost, fileName, this._textModel.text));
  251. contextMenu.show(event);
  252. },
  253. _commitEditing: function()
  254. {
  255. if (this.readOnly)
  256. return false;
  257. this._delegate.commitEditing();
  258. return true;
  259. },
  260. _cancelEditing: function()
  261. {
  262. if (this.readOnly)
  263. return false;
  264. this._delegate.cancelEditing();
  265. return true;
  266. }
  267. }
  268. WebInspector.TextViewer.prototype.__proto__ = WebInspector.View.prototype;
  269. WebInspector.TextViewerDelegate = function()
  270. {
  271. }
  272. WebInspector.TextViewerDelegate.prototype = {
  273. doubleClick: function(lineNumber)
  274. {
  275. // Should be implemented by subclasses.
  276. },
  277. beforeTextChanged: function()
  278. {
  279. // Should be implemented by subclasses.
  280. },
  281. afterTextChanged: function(oldRange, newRange)
  282. {
  283. // Should be implemented by subclasses.
  284. },
  285. commitEditing: function()
  286. {
  287. // Should be implemented by subclasses.
  288. },
  289. cancelEditing: function()
  290. {
  291. // Should be implemented by subclasses.
  292. },
  293. populateLineGutterContextMenu: function(contextMenu)
  294. {
  295. // Should be implemented by subclasses.
  296. },
  297. populateTextAreaContextMenu: function(contextMenu)
  298. {
  299. // Should be implemented by subclasses.
  300. },
  301. suggestedFileName: function()
  302. {
  303. // Should be implemented by subclasses.
  304. }
  305. }
  306. WebInspector.TextViewerDelegate.prototype.__proto__ = WebInspector.Object.prototype;
  307. WebInspector.TextEditorChunkedPanel = function(textModel)
  308. {
  309. this._textModel = textModel;
  310. this._defaultChunkSize = 50;
  311. this._paintCoalescingLevel = 0;
  312. this._domUpdateCoalescingLevel = 0;
  313. }
  314. WebInspector.TextEditorChunkedPanel.prototype = {
  315. get textModel()
  316. {
  317. return this._textModel;
  318. },
  319. revealLine: function(lineNumber)
  320. {
  321. if (lineNumber >= this._textModel.linesCount)
  322. return;
  323. var chunk = this.makeLineAChunk(lineNumber);
  324. chunk.element.scrollIntoViewIfNeeded();
  325. },
  326. addDecoration: function(lineNumber, decoration)
  327. {
  328. if (lineNumber >= this._textModel.linesCount)
  329. return;
  330. var chunk = this.makeLineAChunk(lineNumber);
  331. chunk.addDecoration(decoration);
  332. },
  333. removeDecoration: function(lineNumber, decoration)
  334. {
  335. if (lineNumber >= this._textModel.linesCount)
  336. return;
  337. var chunk = this.chunkForLine(lineNumber);
  338. chunk.removeDecoration(decoration);
  339. },
  340. _buildChunks: function()
  341. {
  342. this.beginDomUpdates();
  343. this._container.removeChildren();
  344. this._textChunks = [];
  345. for (var i = 0; i < this._textModel.linesCount; i += this._defaultChunkSize) {
  346. var chunk = this._createNewChunk(i, i + this._defaultChunkSize);
  347. this._textChunks.push(chunk);
  348. this._container.appendChild(chunk.element);
  349. }
  350. this._repaintAll();
  351. this.endDomUpdates();
  352. },
  353. makeLineAChunk: function(lineNumber)
  354. {
  355. var chunkNumber = this._chunkNumberForLine(lineNumber);
  356. var oldChunk = this._textChunks[chunkNumber];
  357. if (!oldChunk) {
  358. console.error("No chunk for line number: " + lineNumber);
  359. return;
  360. }
  361. if (oldChunk.linesCount === 1)
  362. return oldChunk;
  363. return this._splitChunkOnALine(lineNumber, chunkNumber, true);
  364. },
  365. _splitChunkOnALine: function(lineNumber, chunkNumber, createSuffixChunk)
  366. {
  367. this.beginDomUpdates();
  368. var oldChunk = this._textChunks[chunkNumber];
  369. var wasExpanded = oldChunk.expanded;
  370. oldChunk.expanded = false;
  371. var insertIndex = chunkNumber + 1;
  372. // Prefix chunk.
  373. if (lineNumber > oldChunk.startLine) {
  374. var prefixChunk = this._createNewChunk(oldChunk.startLine, lineNumber);
  375. prefixChunk.readOnly = oldChunk.readOnly;
  376. this._textChunks.splice(insertIndex++, 0, prefixChunk);
  377. this._container.insertBefore(prefixChunk.element, oldChunk.element);
  378. }
  379. // Line chunk.
  380. var endLine = createSuffixChunk ? lineNumber + 1 : oldChunk.startLine + oldChunk.linesCount;
  381. var lineChunk = this._createNewChunk(lineNumber, endLine);
  382. lineChunk.readOnly = oldChunk.readOnly;
  383. this._textChunks.splice(insertIndex++, 0, lineChunk);
  384. this._container.insertBefore(lineChunk.element, oldChunk.element);
  385. // Suffix chunk.
  386. if (oldChunk.startLine + oldChunk.linesCount > endLine) {
  387. var suffixChunk = this._createNewChunk(endLine, oldChunk.startLine + oldChunk.linesCount);
  388. suffixChunk.readOnly = oldChunk.readOnly;
  389. this._textChunks.splice(insertIndex, 0, suffixChunk);
  390. this._container.insertBefore(suffixChunk.element, oldChunk.element);
  391. }
  392. // Remove enclosing chunk.
  393. this._textChunks.splice(chunkNumber, 1);
  394. this._container.removeChild(oldChunk.element);
  395. if (wasExpanded) {
  396. if (prefixChunk)
  397. prefixChunk.expanded = true;
  398. lineChunk.expanded = true;
  399. if (suffixChunk)
  400. suffixChunk.expanded = true;
  401. }
  402. this.endDomUpdates();
  403. return lineChunk;
  404. },
  405. _scroll: function()
  406. {
  407. // FIXME: Replace the "2" with the padding-left value from CSS.
  408. if (this.element.scrollLeft <= 2)
  409. this.element.scrollLeft = 0;
  410. this._scheduleRepaintAll();
  411. if (this._syncScrollListener)
  412. this._syncScrollListener();
  413. },
  414. _scheduleRepaintAll: function()
  415. {
  416. if (this._repaintAllTimer)
  417. clearTimeout(this._repaintAllTimer);
  418. this._repaintAllTimer = setTimeout(this._repaintAll.bind(this), 50);
  419. },
  420. beginUpdates: function()
  421. {
  422. this._paintCoalescingLevel++;
  423. },
  424. endUpdates: function()
  425. {
  426. this._paintCoalescingLevel--;
  427. if (!this._paintCoalescingLevel)
  428. this._repaintAll();
  429. },
  430. beginDomUpdates: function()
  431. {
  432. this._domUpdateCoalescingLevel++;
  433. },
  434. endDomUpdates: function()
  435. {
  436. this._domUpdateCoalescingLevel--;
  437. },
  438. _chunkNumberForLine: function(lineNumber)
  439. {
  440. function compareLineNumbers(value, chunk)
  441. {
  442. return value < chunk.startLine ? -1 : 1;
  443. }
  444. var insertBefore = insertionIndexForObjectInListSortedByFunction(lineNumber, this._textChunks, compareLineNumbers);
  445. return insertBefore - 1;
  446. },
  447. chunkForLine: function(lineNumber)
  448. {
  449. return this._textChunks[this._chunkNumberForLine(lineNumber)];
  450. },
  451. _findFirstVisibleChunkNumber: function(visibleFrom)
  452. {
  453. function compareOffsetTops(value, chunk)
  454. {
  455. return value < chunk.offsetTop ? -1 : 1;
  456. }
  457. var insertBefore = insertionIndexForObjectInListSortedByFunction(visibleFrom, this._textChunks, compareOffsetTops);
  458. return insertBefore - 1;
  459. },
  460. _findVisibleChunks: function(visibleFrom, visibleTo)
  461. {
  462. var from = this._findFirstVisibleChunkNumber(visibleFrom);
  463. for (var to = from + 1; to < this._textChunks.length; ++to) {
  464. if (this._textChunks[to].offsetTop >= visibleTo)
  465. break;
  466. }
  467. return { start: from, end: to };
  468. },
  469. _findFirstVisibleLineNumber: function(visibleFrom)
  470. {
  471. var chunk = this._textChunks[this._findFirstVisibleChunkNumber(visibleFrom)];
  472. if (!chunk.expanded)
  473. return chunk.startLine;
  474. var lineNumbers = [];
  475. for (var i = 0; i < chunk.linesCount; ++i) {
  476. lineNumbers.push(chunk.startLine + i);
  477. }
  478. function compareLineRowOffsetTops(value, lineNumber)
  479. {
  480. var lineRow = chunk.getExpandedLineRow(lineNumber);
  481. return value < lineRow.offsetTop ? -1 : 1;
  482. }
  483. var insertBefore = insertionIndexForObjectInListSortedByFunction(visibleFrom, lineNumbers, compareLineRowOffsetTops);
  484. return lineNumbers[insertBefore - 1];
  485. },
  486. _repaintAll: function()
  487. {
  488. delete this._repaintAllTimer;
  489. if (this._paintCoalescingLevel || this._dirtyLines)
  490. return;
  491. var visibleFrom = this.element.scrollTop;
  492. var visibleTo = this.element.scrollTop + this.element.clientHeight;
  493. if (visibleTo) {
  494. var result = this._findVisibleChunks(visibleFrom, visibleTo);
  495. this._expandChunks(result.start, result.end);
  496. }
  497. },
  498. _expandChunks: function(fromIndex, toIndex)
  499. {
  500. // First collapse chunks to collect the DOM elements into a cache to reuse them later.
  501. for (var i = 0; i < fromIndex; ++i)
  502. this._textChunks[i].expanded = false;
  503. for (var i = toIndex; i < this._textChunks.length; ++i)
  504. this._textChunks[i].expanded = false;
  505. for (var i = fromIndex; i < toIndex; ++i)
  506. this._textChunks[i].expanded = true;
  507. },
  508. _totalHeight: function(firstElement, lastElement)
  509. {
  510. lastElement = (lastElement || firstElement).nextElementSibling;
  511. if (lastElement)
  512. return lastElement.offsetTop - firstElement.offsetTop;
  513. var offsetParent = firstElement.offsetParent;
  514. if (offsetParent && offsetParent.scrollHeight > offsetParent.clientHeight)
  515. return offsetParent.scrollHeight - firstElement.offsetTop;
  516. var total = 0;
  517. while (firstElement && firstElement !== lastElement) {
  518. total += firstElement.offsetHeight;
  519. firstElement = firstElement.nextElementSibling;
  520. }
  521. return total;
  522. },
  523. resize: function()
  524. {
  525. this._repaintAll();
  526. }
  527. }
  528. WebInspector.TextEditorGutterPanel = function(textModel, syncDecorationsForLineListener)
  529. {
  530. WebInspector.TextEditorChunkedPanel.call(this, textModel);
  531. this._syncDecorationsForLineListener = syncDecorationsForLineListener;
  532. this.element = document.createElement("div");
  533. this.element.className = "text-editor-lines";
  534. this._container = document.createElement("div");
  535. this._container.className = "inner-container";
  536. this.element.appendChild(this._container);
  537. this.element.addEventListener("scroll", this._scroll.bind(this), false);
  538. this.freeCachedElements();
  539. this._buildChunks();
  540. }
  541. WebInspector.TextEditorGutterPanel.prototype = {
  542. freeCachedElements: function()
  543. {
  544. this._cachedRows = [];
  545. },
  546. _createNewChunk: function(startLine, endLine)
  547. {
  548. return new WebInspector.TextEditorGutterChunk(this, startLine, endLine);
  549. },
  550. textChanged: function(oldRange, newRange)
  551. {
  552. this.beginDomUpdates();
  553. var linesDiff = newRange.linesCount - oldRange.linesCount;
  554. if (linesDiff) {
  555. // Remove old chunks (if needed).
  556. for (var chunkNumber = this._textChunks.length - 1; chunkNumber >= 0 ; --chunkNumber) {
  557. var chunk = this._textChunks[chunkNumber];
  558. if (chunk.startLine + chunk.linesCount <= this._textModel.linesCount)
  559. break;
  560. chunk.expanded = false;
  561. this._container.removeChild(chunk.element);
  562. }
  563. this._textChunks.length = chunkNumber + 1;
  564. // Add new chunks (if needed).
  565. var totalLines = 0;
  566. if (this._textChunks.length) {
  567. var lastChunk = this._textChunks[this._textChunks.length - 1];
  568. totalLines = lastChunk.startLine + lastChunk.linesCount;
  569. }
  570. for (var i = totalLines; i < this._textModel.linesCount; i += this._defaultChunkSize) {
  571. var chunk = this._createNewChunk(i, i + this._defaultChunkSize);
  572. this._textChunks.push(chunk);
  573. this._container.appendChild(chunk.element);
  574. }
  575. this._repaintAll();
  576. } else {
  577. // Decorations may have been removed, so we may have to sync those lines.
  578. var chunkNumber = this._chunkNumberForLine(newRange.startLine);
  579. var chunk = this._textChunks[chunkNumber];
  580. while (chunk && chunk.startLine <= newRange.endLine) {
  581. if (chunk.linesCount === 1)
  582. this._syncDecorationsForLineListener(chunk.startLine);
  583. chunk = this._textChunks[++chunkNumber];
  584. }
  585. }
  586. this.endDomUpdates();
  587. },
  588. syncClientHeight: function(clientHeight)
  589. {
  590. if (this.element.offsetHeight > clientHeight)
  591. this._container.style.setProperty("padding-bottom", (this.element.offsetHeight - clientHeight) + "px");
  592. else
  593. this._container.style.removeProperty("padding-bottom");
  594. }
  595. }
  596. WebInspector.TextEditorGutterPanel.prototype.__proto__ = WebInspector.TextEditorChunkedPanel.prototype;
  597. WebInspector.TextEditorGutterChunk = function(textViewer, startLine, endLine)
  598. {
  599. this._textViewer = textViewer;
  600. this._textModel = textViewer._textModel;
  601. this.startLine = startLine;
  602. endLine = Math.min(this._textModel.linesCount, endLine);
  603. this.linesCount = endLine - startLine;
  604. this._expanded = false;
  605. this.element = document.createElement("div");
  606. this.element.lineNumber = startLine;
  607. this.element.className = "webkit-line-number";
  608. if (this.linesCount === 1) {
  609. // Single line chunks are typically created for decorations. Host line number in
  610. // the sub-element in order to allow flexible border / margin management.
  611. var innerSpan = document.createElement("span");
  612. innerSpan.className = "webkit-line-number-inner";
  613. innerSpan.textContent = startLine + 1;
  614. var outerSpan = document.createElement("div");
  615. outerSpan.className = "webkit-line-number-outer";
  616. outerSpan.appendChild(innerSpan);
  617. this.element.appendChild(outerSpan);
  618. } else {
  619. var lineNumbers = [];
  620. for (var i = startLine; i < endLine; ++i)
  621. lineNumbers.push(i + 1);
  622. this.element.textContent = lineNumbers.join("\n");
  623. }
  624. }
  625. WebInspector.TextEditorGutterChunk.prototype = {
  626. addDecoration: function(decoration)
  627. {
  628. this._textViewer.beginDomUpdates();
  629. if (typeof decoration === "string")
  630. this.element.addStyleClass(decoration);
  631. this._textViewer.endDomUpdates();
  632. },
  633. removeDecoration: function(decoration)
  634. {
  635. this._textViewer.beginDomUpdates();
  636. if (typeof decoration === "string")
  637. this.element.removeStyleClass(decoration);
  638. this._textViewer.endDomUpdates();
  639. },
  640. get expanded()
  641. {
  642. return this._expanded;
  643. },
  644. set expanded(expanded)
  645. {
  646. if (this.linesCount === 1)
  647. this._textViewer._syncDecorationsForLineListener(this.startLine);
  648. if (this._expanded === expanded)
  649. return;
  650. this._expanded = expanded;
  651. if (this.linesCount === 1)
  652. return;
  653. this._textViewer.beginDomUpdates();
  654. if (expanded) {
  655. this._expandedLineRows = [];
  656. var parentElement = this.element.parentElement;
  657. for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) {
  658. var lineRow = this._createRow(i);
  659. parentElement.insertBefore(lineRow, this.element);
  660. this._expandedLineRows.push(lineRow);
  661. }
  662. parentElement.removeChild(this.element);
  663. } else {
  664. var elementInserted = false;
  665. for (var i = 0; i < this._expandedLineRows.length; ++i) {
  666. var lineRow = this._expandedLineRows[i];
  667. var parentElement = lineRow.parentElement;
  668. if (parentElement) {
  669. if (!elementInserted) {
  670. elementInserted = true;
  671. parentElement.insertBefore(this.element, lineRow);
  672. }
  673. parentElement.removeChild(lineRow);
  674. }
  675. this._textViewer._cachedRows.push(lineRow);
  676. }
  677. delete this._expandedLineRows;
  678. }
  679. this._textViewer.endDomUpdates();
  680. },
  681. get height()
  682. {
  683. if (!this._expandedLineRows)
  684. return this._textViewer._totalHeight(this.element);
  685. return this._textViewer._totalHeight(this._expandedLineRows[0], this._expandedLineRows[this._expandedLineRows.length - 1]);
  686. },
  687. get offsetTop()
  688. {
  689. return (this._expandedLineRows && this._expandedLineRows.length) ? this._expandedLineRows[0].offsetTop : this.element.offsetTop;
  690. },
  691. _createRow: function(lineNumber)
  692. {
  693. var lineRow = this._textViewer._cachedRows.pop() || document.createElement("div");
  694. lineRow.lineNumber = lineNumber;
  695. lineRow.className = "webkit-line-number";
  696. lineRow.textContent = lineNumber + 1;
  697. return lineRow;
  698. }
  699. }
  700. WebInspector.TextEditorMainPanel = function(textModel, url, syncScrollListener, syncDecorationsForLineListener, enterTextChangeMode, exitTextChangeMode)
  701. {
  702. WebInspector.TextEditorChunkedPanel.call(this, textModel);
  703. this._syncScrollListener = syncScrollListener;
  704. this._syncDecorationsForLineListener = syncDecorationsForLineListener;
  705. this._enterTextChangeMode = enterTextChangeMode;
  706. this._exitTextChangeMode = exitTextChangeMode;
  707. this._url = url;
  708. this._highlighter = new WebInspector.TextEditorHighlighter(textModel, this._highlightDataReady.bind(this));
  709. this._readOnly = true;
  710. this.element = document.createElement("div");
  711. this.element.className = "text-editor-contents";
  712. this.element.tabIndex = 0;
  713. this._container = document.createElement("div");
  714. this._container.className = "inner-container";
  715. this._container.tabIndex = 0;
  716. this.element.appendChild(this._container);
  717. this.element.addEventListener("scroll", this._scroll.bind(this), false);
  718. // In WebKit the DOMNodeRemoved event is fired AFTER the node is removed, thus it should be
  719. // attached to all DOM nodes that we want to track. Instead, we attach the DOMNodeRemoved
  720. // listeners only on the line rows, and use DOMSubtreeModified to track node removals inside
  721. // the line rows. For more info see: https://bugs.webkit.org/show_bug.cgi?id=55666
  722. this._handleDOMUpdatesCallback = this._handleDOMUpdates.bind(this);
  723. this._container.addEventListener("DOMCharacterDataModified", this._handleDOMUpdatesCallback, false);
  724. this._container.addEventListener("DOMNodeInserted", this._handleDOMUpdatesCallback, false);
  725. this._container.addEventListener("DOMSubtreeModified", this._handleDOMUpdatesCallback, false);
  726. this.freeCachedElements();
  727. this._buildChunks();
  728. }
  729. WebInspector.TextEditorMainPanel.prototype = {
  730. set mimeType(mimeType)
  731. {
  732. this._highlighter.mimeType = mimeType;
  733. },
  734. set readOnly(readOnly)
  735. {
  736. if (this._readOnly === readOnly)
  737. return;
  738. this.beginDomUpdates();
  739. this._readOnly = readOnly;
  740. if (this._readOnly)
  741. this._container.removeStyleClass("text-editor-editable");
  742. else
  743. this._container.addStyleClass("text-editor-editable");
  744. this.endDomUpdates();
  745. },
  746. get readOnly()
  747. {
  748. return this._readOnly;
  749. },
  750. setEditableRange: function(startLine, endLine)
  751. {
  752. this.beginDomUpdates();
  753. var firstChunkNumber = this._chunkNumberForLine(startLine);
  754. var firstChunk = this._textChunks[firstChunkNumber];
  755. if (firstChunk.startLine !== startLine) {
  756. this._splitChunkOnALine(startLine, firstChunkNumber);
  757. firstChunkNumber += 1;
  758. }
  759. var lastChunkNumber = this._textChunks.length;
  760. if (endLine !== this._textModel.linesCount) {
  761. lastChunkNumber = this._chunkNumberForLine(endLine);
  762. var lastChunk = this._textChunks[lastChunkNumber];
  763. if (lastChunk && lastChunk.startLine !== endLine) {
  764. this._splitChunkOnALine(endLine, lastChunkNumber);
  765. lastChunkNumber += 1;
  766. }
  767. }
  768. for (var chunkNumber = 0; chunkNumber < firstChunkNumber; ++chunkNumber)
  769. this._textChunks[chunkNumber].readOnly = true;
  770. for (var chunkNumber = firstChunkNumber; chunkNumber < lastChunkNumber; ++chunkNumber)
  771. this._textChunks[chunkNumber].readOnly = false;
  772. for (var chunkNumber = lastChunkNumber; chunkNumber < this._textChunks.length; ++chunkNumber)
  773. this._textChunks[chunkNumber].readOnly = true;
  774. this.endDomUpdates();
  775. },
  776. clearEditableRange: function()
  777. {
  778. for (var chunkNumber = 0; chunkNumber < this._textChunks.length; ++chunkNumber)
  779. this._textChunks[chunkNumber].readOnly = false;
  780. },
  781. markAndRevealRange: function(range)
  782. {
  783. if (this._rangeToMark) {
  784. var markedLine = this._rangeToMark.startLine;
  785. delete this._rangeToMark;
  786. // Remove the marked region immediately.
  787. if (!this._dirtyLines) {
  788. this.beginDomUpdates();
  789. var chunk = this.chunkForLine(markedLine);
  790. var wasExpanded = chunk.expanded;
  791. chunk.expanded = false;
  792. chunk.updateCollapsedLineRow();
  793. chunk.expanded = wasExpanded;
  794. this.endDomUpdates();
  795. } else
  796. this._paintLines(markedLine, markedLine + 1);
  797. }
  798. if (range) {
  799. this._rangeToMark = range;
  800. this.revealLine(range.startLine);
  801. var chunk = this.makeLineAChunk(range.startLine);
  802. this._paintLine(chunk.element);
  803. if (this._markedRangeElement)
  804. this._markedRangeElement.scrollIntoViewIfNeeded();
  805. }
  806. delete this._markedRangeElement;
  807. },
  808. highlightLine: function(lineNumber)
  809. {
  810. this.clearLineHighlight();
  811. this._highlightedLine = lineNumber;
  812. this.revealLine(lineNumber);
  813. this.addDecoration(lineNumber, "webkit-highlighted-line");
  814. },
  815. clearLineHighlight: function()
  816. {
  817. if (typeof this._highlightedLine === "number") {
  818. this.removeDecoration(this._highlightedLine, "webkit-highlighted-line");
  819. delete this._highlightedLine;
  820. }
  821. },
  822. freeCachedElements: function()
  823. {
  824. this._cachedSpans = [];
  825. this._cachedTextNodes = [];
  826. this._cachedRows = [];
  827. },
  828. handleUndoRedo: function(redo)
  829. {
  830. if (this._readOnly || this._dirtyLines)
  831. return false;
  832. this.beginUpdates();
  833. this._enterTextChangeMode();
  834. var callback = function(oldRange, newRange) {
  835. this._exitTextChangeMode(oldRange, newRange);
  836. this._enterTextChangeMode();
  837. }.bind(this);
  838. var range = redo ? this._textModel.redo(callback) : this._textModel.undo(callback);
  839. if (range)
  840. this._setCaretLocation(range.endLine, range.endColumn, true);
  841. this._exitTextChangeMode(null, null);
  842. this.endUpdates();
  843. return true;
  844. },
  845. handleTabKeyPress: function(shiftKey)
  846. {
  847. if (this._readOnly || this._dirtyLines)
  848. return false;
  849. var selection = this._getSelection();
  850. if (!selection)
  851. return false;
  852. if (shiftKey)
  853. return true;
  854. this.beginUpdates();
  855. this._enterTextChangeMode();
  856. var range = selection;
  857. if (range.startLine > range.endLine || (range.startLine === range.endLine && range.startColumn > range.endColumn))
  858. range = new WebInspector.TextRange(range.endLine, range.endColumn, range.startLine, range.startColumn);
  859. var newRange = this._setText(range, "\t");
  860. this._exitTextChangeMode(range, newRange);
  861. this.endUpdates();
  862. this._setCaretLocation(newRange.endLine, newRange.endColumn, true);
  863. return true;
  864. },
  865. _splitChunkOnALine: function(lineNumber, chunkNumber, createSuffixChunk)
  866. {
  867. var selection = this._getSelection();
  868. var chunk = WebInspector.TextEditorChunkedPanel.prototype._splitChunkOnALine.call(this, lineNumber, chunkNumber, createSuffixChunk);
  869. this._restoreSelection(selection);
  870. return chunk;
  871. },
  872. _buildChunks: function()
  873. {
  874. for (var i = 0; i < this._textModel.linesCount; ++i)
  875. this._textModel.removeAttribute(i, "highlight");
  876. WebInspector.TextEditorChunkedPanel.prototype._buildChunks.call(this);
  877. },
  878. _createNewChunk: function(startLine, endLine)
  879. {
  880. return new WebInspector.TextEditorMainChunk(this, startLine, endLine);
  881. },
  882. _expandChunks: function(fromIndex, toIndex)
  883. {
  884. var lastChunk = this._textChunks[toIndex - 1];
  885. var lastVisibleLine = lastChunk.startLine + lastChunk.linesCount;
  886. var selection = this._getSelection();
  887. this._muteHighlightListener = true;
  888. this._highlighter.highlight(lastVisibleLine);
  889. delete this._muteHighlightListener;
  890. this._restorePaintLinesOperationsCredit();
  891. WebInspector.TextEditorChunkedPanel.prototype._expandChunks.call(this, fromIndex, toIndex);
  892. this._adjustPaintLinesOperationsRefreshValue();
  893. this._restoreSelection(selection);
  894. },
  895. _highlightDataReady: function(fromLine, toLine)
  896. {
  897. if (this._muteHighlightListener)
  898. return;
  899. this._restorePaintLinesOperationsCredit();
  900. this._paintLines(fromLine, toLine, true /*restoreSelection*/);
  901. },
  902. _schedulePaintLines: function(startLine, endLine)
  903. {
  904. if (startLine >= endLine)
  905. return;
  906. if (!this._scheduledPaintLines) {
  907. this._scheduledPaintLines = [ { startLine: startLine, endLine: endLine } ];
  908. this._paintScheduledLinesTimer = setTimeout(this._paintScheduledLines.bind(this), 0);
  909. } else {
  910. for (var i = 0; i < this._scheduledPaintLines.length; ++i) {
  911. var chunk = this._scheduledPaintLines[i];
  912. if (chunk.startLine <= endLine && chunk.endLine >= startLine) {
  913. chunk.startLine = Math.min(chunk.startLine, startLine);
  914. chunk.endLine = Math.max(chunk.endLine, endLine);
  915. return;
  916. }
  917. if (chunk.startLine > endLine) {
  918. this._scheduledPaintLines.splice(i, 0, { startLine: startLine, endLine: endLine });
  919. return;
  920. }
  921. }
  922. this._scheduledPaintLines.push({ startLine: startLine, endLine: endLine });
  923. }
  924. },
  925. _paintScheduledLines: function(skipRestoreSelection)
  926. {
  927. if (this._paintScheduledLinesTimer)
  928. clearTimeout(this._paintScheduledLinesTimer);
  929. delete this._paintScheduledLinesTimer;
  930. if (!this._scheduledPaintLines)
  931. return;
  932. // Reschedule the timer if we can not paint the lines yet, or the user is scrolling.
  933. if (this._dirtyLines || this._repaintAllTimer) {
  934. this._paintScheduledLinesTimer = setTimeout(this._paintScheduledLines.bind(this), 50);
  935. return;
  936. }
  937. var scheduledPaintLines = this._scheduledPaintLines;
  938. delete this._scheduledPaintLines;
  939. this._restorePaintLinesOperationsCredit();
  940. this._paintLineChunks(scheduledPaintLines, !skipRestoreSelection);
  941. this._adjustPaintLinesOperationsRefreshValue();
  942. },
  943. _restorePaintLinesOperationsCredit: function()
  944. {
  945. if (!this._paintLinesOperationsRefreshValue)
  946. this._paintLinesOperationsRefreshValue = 250;
  947. this._paintLinesOperationsCredit = this._paintLinesOperationsRefreshValue;
  948. this._paintLinesOperationsLastRefresh = Date.now();
  949. },
  950. _adjustPaintLinesOperationsRefreshValue: function()
  951. {
  952. var operationsDone = this._paintLinesOperationsRefreshValue - this._paintLinesOperationsCredit;
  953. if (operationsDone <= 0)
  954. return;
  955. var timePast = Date.now() - this._paintLinesOperationsLastRefresh;
  956. if (timePast <= 0)
  957. return;
  958. // Make the synchronous CPU chunk for painting the lines 50 msec.
  959. var value = Math.floor(operationsDone / timePast * 50);
  960. this._paintLinesOperationsRefreshValue = Number.constrain(value, 150, 1500);
  961. },
  962. _paintLines: function(fromLine, toLine, restoreSelection)
  963. {
  964. this._paintLineChunks([ { startLine: fromLine, endLine: toLine } ], restoreSelection);
  965. },
  966. _paintLineChunks: function(lineChunks, restoreSelection)
  967. {
  968. // First, paint visible lines, so that in case of long lines we should start highlighting
  969. // the visible area immediately, instead of waiting for the lines above the visible area.
  970. var visibleFrom = this.element.scrollTop;
  971. var firstVisibleLineNumber = this._findFirstVisibleLineNumber(visibleFrom);
  972. var chunk;
  973. var selection;
  974. var invisibleLineRows = [];
  975. for (var i = 0; i < lineChunks.length; ++i) {
  976. var lineChunk = lineChunks[i];
  977. if (this._dirtyLines || this._scheduledPaintLines) {
  978. this._schedulePaintLines(lineChunk.startLine, lineChunk.endLine);
  979. continue;
  980. }
  981. for (var lineNumber = lineChunk.startLine; lineNumber < lineChunk.endLine; ++lineNumber) {
  982. if (!chunk || lineNumber < chunk.startLine || lineNumber >= chunk.startLine + chunk.linesCount)
  983. chunk = this.chunkForLine(lineNumber);
  984. var lineRow = chunk.getExpandedLineRow(lineNumber);
  985. if (!lineRow)
  986. continue;
  987. if (lineNumber < firstVisibleLineNumber) {
  988. invisibleLineRows.push(lineRow);
  989. continue;
  990. }
  991. if (restoreSelection && !selection)
  992. selection = this._getSelection();
  993. this._paintLine(lineRow);
  994. if (this._paintLinesOperationsCredit < 0) {
  995. this._schedulePaintLines(lineNumber + 1, lineChunk.endLine);
  996. break;
  997. }
  998. }
  999. }
  1000. for (var i = 0; i < invisibleLineRows.length; ++i) {
  1001. if (restoreSelection && !selection)
  1002. selection = this._getSelection();
  1003. this._paintLine(invisibleLineRows[i]);
  1004. }
  1005. if (restoreSelection)
  1006. this._restoreSelection(selection);
  1007. },
  1008. _paintLine: function(lineRow)
  1009. {
  1010. var lineNumber = lineRow.lineNumber;
  1011. if (this._dirtyLines) {
  1012. this._schedulePaintLines(lineNumber, lineNumber + 1);
  1013. return;
  1014. }
  1015. this.beginDomUpdates();
  1016. try {
  1017. if (this._scheduledPaintLines || this._paintLinesOperationsCredit < 0) {
  1018. this._schedulePaintLines(lineNumber, lineNumber + 1);
  1019. return;
  1020. }
  1021. var highlight = this._textModel.getAttribute(lineNumber, "highlight");
  1022. if (!highlight)
  1023. return;
  1024. lineRow.removeChildren();
  1025. var line = this._textModel.line(lineNumber);
  1026. if (!line)
  1027. lineRow.appendChild(document.createElement("br"));
  1028. var plainTextStart = -1;
  1029. for (var j = 0; j < line.length;) {
  1030. if (j > 1000) {
  1031. // This line is too long - do not waste cycles on minified js highlighting.
  1032. if (plainTextStart === -1)
  1033. plainTextStart = j;
  1034. break;
  1035. }
  1036. var attribute = highlight[j];
  1037. if (!attribute || !attribute.tokenType) {
  1038. if (plainTextStart === -1)
  1039. plainTextStart = j;
  1040. j++;
  1041. } else {
  1042. if (plainTextStart !== -1) {
  1043. this._appendTextNode(lineRow, line.substring(plainTextStart, j));
  1044. plainTextStart = -1;
  1045. --this._paintLinesOperationsCredit;
  1046. }
  1047. this._appendSpan(lineRow, line.substring(j, j + attribute.length), attribute.tokenType);
  1048. j += attribute.length;
  1049. --this._paintLinesOperationsCredit;
  1050. }
  1051. }
  1052. if (plainTextStart !== -1) {
  1053. this._appendTextNode(lineRow, line.substring(plainTextStart, line.length));
  1054. --this._paintLinesOperationsCredit;
  1055. }
  1056. if (lineRow.decorationsElement)
  1057. lineRow.appendChild(lineRow.decorationsElement);
  1058. } finally {
  1059. if (this._rangeToMark && this._rangeToMark.startLine === lineNumber)
  1060. this._markedRangeElement = highlightSearchResult(lineRow, this._rangeToMark.startColumn, this._rangeToMark.endColumn - this._rangeToMark.startColumn);
  1061. this.endDomUpdates();
  1062. }
  1063. },
  1064. _releaseLinesHighlight: function(lineRow)
  1065. {
  1066. if (!lineRow)
  1067. return;
  1068. if ("spans" in lineRow) {
  1069. var spans = lineRow.spans;
  1070. for (var j = 0; j < spans.length; ++j)
  1071. this._cachedSpans.push(spans[j]);
  1072. delete lineRow.spans;
  1073. }
  1074. if ("textNodes" in lineRow) {
  1075. var textNodes = lineRow.textNodes;
  1076. for (var j = 0; j < textNodes.length; ++j)
  1077. this._cachedTextNodes.push(textNodes[j]);
  1078. delete lineRow.textNodes;
  1079. }
  1080. this._cachedRows.push(lineRow);
  1081. },
  1082. _getSelection: function()
  1083. {
  1084. var selection = window.getSelection();
  1085. if (!selection.rangeCount)
  1086. return null;
  1087. var selectionRange = selection.getRangeAt(0);
  1088. // Selection may be outside of the viewer.
  1089. if (!this._container.isAncestor(selectionRange.startContainer) || !this._container.isAncestor(selectionRange.endContainer))
  1090. return null;
  1091. var start = this._selectionToPosition(selectionRange.startContainer, selectionRange.startOffset);
  1092. var end = selectionRange.collapsed ? start : this._selectionToPosition(selectionRange.endContainer, selectionRange.endOffset);
  1093. if (selection.anchorNode === selectionRange.startContainer && selection.anchorOffset === selectionRange.startOffset)
  1094. return new WebInspector.TextRange(start.line, start.column, end.line, end.column);
  1095. else
  1096. return new WebInspector.TextRange(end.line, end.column, start.line, start.column);
  1097. },
  1098. _restoreSelection: function(range, scrollIntoView)
  1099. {
  1100. if (!range)
  1101. return;
  1102. var start = this._positionToSelection(range.startLine, range.startColumn);
  1103. var end = range.isEmpty() ? start : this._positionToSelection(range.endLine, range.endColumn);
  1104. window.getSelection().setBaseAndExtent(start.container, start.offset, end.container, end.offset);
  1105. if (scrollIntoView) {
  1106. for (var node = end.container; node; node = node.parentElement) {
  1107. if (node.scrollIntoViewIfNeeded) {
  1108. node.scrollIntoViewIfNeeded();
  1109. break;
  1110. }
  1111. }
  1112. }
  1113. },
  1114. _setCaretLocation: function(line, column, scrollIntoView)
  1115. {
  1116. var range = new WebInspector.TextRange(line, column, line, column);
  1117. this._restoreSelection(range, scrollIntoView);
  1118. },
  1119. _selectionToPosition: function(container, offset)
  1120. {
  1121. if (container === this._container && offset === 0)
  1122. return { line: 0, column: 0 };
  1123. if (container === this._container && offset === 1)
  1124. return { line: this._textModel.linesCount - 1, column: this._textModel.lineLength(this._textModel.linesCount - 1) };
  1125. var lineRow = this._enclosingLineRowOrSelf(container);
  1126. var lineNumber = lineRow.lineNumber;
  1127. if (container === lineRow && offset === 0)
  1128. return { line: lineNumber, column: 0 };
  1129. // This may be chunk and chunks may contain \n.
  1130. var column = 0;
  1131. var node = lineRow.nodeType === Node.TEXT_NODE ? lineRow : lineRow.traverseNextTextNode(lineRow);
  1132. while (node && node !== container) {
  1133. var text = node.textContent;
  1134. for (var i = 0; i < text.length; ++i) {
  1135. if (text.charAt(i) === "\n") {
  1136. lineNumber++;
  1137. column = 0;
  1138. } else
  1139. column++;
  1140. }
  1141. node = node.traverseNextTextNode(lineRow);
  1142. }
  1143. if (node === container && offset) {
  1144. var text = node.textContent;
  1145. for (var i = 0; i < offset; ++i) {
  1146. if (text.charAt(i) === "\n") {
  1147. lineNumber++;
  1148. column = 0;
  1149. } else
  1150. column++;
  1151. }
  1152. }
  1153. return { line: lineNumber, column: column };
  1154. },
  1155. _positionToSelection: function(line, column)
  1156. {
  1157. var chunk = this.chunkForLine(line);
  1158. // One-lined collapsed chunks may still stay highlighted.
  1159. var lineRow = chunk.linesCount === 1 ? chunk.element : chunk.getExpandedLineRow(line);
  1160. if (lineRow)
  1161. var rangeBoundary = lineRow.rangeBoundaryForOffset(column);
  1162. else {
  1163. var offset = column;
  1164. for (var i = chunk.startLine; i < line; ++i)
  1165. offset += this._textModel.lineLength(i) + 1; // \n
  1166. lineRow = chunk.element;
  1167. if (lineRow.firstChild)
  1168. var rangeBoundary = { container: lineRow.firstChild, offset: offset };
  1169. else
  1170. var rangeBoundary = { container: lineRow, offset: 0 };
  1171. }
  1172. return rangeBoundary;
  1173. },
  1174. _enclosingLineRowOrSelf: function(element)
  1175. {
  1176. var lineRow = element.enclosingNodeOrSelfWithClass("webkit-line-content");
  1177. if (lineRow)
  1178. return lineRow;
  1179. for (var lineRow = element; lineRow; lineRow = lineRow.parentElement) {
  1180. if (lineRow.parentElement === this._container)
  1181. return lineRow;
  1182. }
  1183. return null;
  1184. },
  1185. _appendSpan: function(element, content, className)
  1186. {
  1187. if (className === "html-resource-link" || className === "html-external-link") {
  1188. element.appendChild(this._createLink(content, className === "html-external-link"));
  1189. return;
  1190. }
  1191. var span = this._cachedSpans.pop() || document.createElement("span");
  1192. span.className = "webkit-" + className;
  1193. span.textContent = content;
  1194. element.appendChild(span);
  1195. if (!("spans" in element))
  1196. element.spans = [];
  1197. element.spans.push(span);
  1198. },
  1199. _appendTextNode: function(element, text)
  1200. {
  1201. var textNode = this._cachedTextNodes.pop();
  1202. if (textNode)
  1203. textNode.nodeValue = text;
  1204. else
  1205. textNode = document.createTextNode(text);
  1206. element.appendChild(textNode);
  1207. if (!("textNodes" in element))

Large files files are truncated, but you can click here to view the full file