PageRenderTime 56ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/src/3rdparty/webkit/Source/WebCore/inspector/front-end/TextViewer.js

https://bitbucket.org/ultra_iter/qt-vtl
JavaScript | 1940 lines | 1557 code | 298 blank | 85 comment | 312 complexity | 6a4762738e53c171f1570a02918a3f80 MD5 | raw file
Possible License(s): LGPL-2.0, LGPL-3.0, BSD-3-Clause, CC0-1.0, CC-BY-SA-4.0, LGPL-2.1, GPL-3.0, Apache-2.0

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

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