home *** CD-ROM | disk | FTP | other *** search
/ Computer Active Guide 2009 July / CAG7.ISO / Internetas / SafariSetup.exe / AppleApplicationSupport.msi / WebKit.resources_inspector_TextViewer.js < prev    next >
Encoding:
JavaScript  |  2010-06-03  |  27.4 KB  |  785 lines

  1. /*
  2.  * Copyright (C) 2009 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.  
  32. WebInspector.TextViewer = function(textModel, platform, url)
  33. {
  34.     this._textModel = textModel;
  35.     this._textModel.changeListener = this._buildChunks.bind(this);
  36.     this._highlighter = new WebInspector.TextEditorHighlighter(this._textModel, this._highlightDataReady.bind(this));
  37.  
  38.     this.element = document.createElement("div");
  39.     this.element.className = "text-editor monospace";
  40.     this.element.tabIndex = 0;
  41.  
  42.     this.element.addEventListener("scroll", this._scroll.bind(this), false);
  43.     this.element.addEventListener("keydown", this._handleKeyDown.bind(this), false);
  44.     this.element.addEventListener("beforecopy", this._beforeCopy.bind(this), false);
  45.     this.element.addEventListener("copy", this._copy.bind(this), false);
  46.     this.element.addEventListener("dblclick", this._handleDoubleClick.bind(this), false);
  47.  
  48.     this._url = url;
  49.  
  50.     this._linesContainerElement = document.createElement("table");
  51.     this._linesContainerElement.className = "text-editor-lines";
  52.     this._linesContainerElement.setAttribute("cellspacing", 0);
  53.     this._linesContainerElement.setAttribute("cellpadding", 0);
  54.     this.element.appendChild(this._linesContainerElement);
  55.  
  56.     this._defaultChunkSize = 50;
  57.     this._paintCoalescingLevel = 0;
  58.  
  59.     this.freeCachedElements();
  60.     this._buildChunks();
  61. }
  62.  
  63. WebInspector.TextViewer.prototype = {
  64.     set mimeType(mimeType)
  65.     {
  66.         this._highlighter.mimeType = mimeType;
  67.     },
  68.  
  69.     get textModel()
  70.     {
  71.         return this._textModel;
  72.     },
  73.  
  74.     revealLine: function(lineNumber)
  75.     {
  76.         if (lineNumber >= this._textModel.linesCount)
  77.             return;
  78.  
  79.         var chunk = this._makeLineAChunk(lineNumber);
  80.         chunk.element.scrollIntoViewIfNeeded();
  81.     },
  82.  
  83.     set editCallback(editCallback)
  84.     {
  85.         this._editCallback = editCallback;
  86.     },
  87.  
  88.     addDecoration: function(lineNumber, decoration)
  89.     {
  90.         var chunk = this._makeLineAChunk(lineNumber);
  91.         chunk.addDecoration(decoration);
  92.     },
  93.  
  94.     removeDecoration: function(lineNumber, decoration)
  95.     {
  96.         var chunk = this._makeLineAChunk(lineNumber);
  97.         chunk.removeDecoration(decoration);
  98.     },
  99.  
  100.     markAndRevealRange: function(range)
  101.     {
  102.         if (this._rangeToMark) {
  103.             var markedLine = this._rangeToMark.startLine;
  104.             this._rangeToMark = null;
  105.             this._paintLines(markedLine, markedLine + 1);
  106.         }
  107.  
  108.         if (range) {
  109.             this._rangeToMark = range;
  110.             this.revealLine(range.startLine);
  111.             this._paintLines(range.startLine, range.startLine + 1);
  112.             if (this._markedRangeElement)
  113.                 this._markedRangeElement.scrollIntoViewIfNeeded();
  114.         }
  115.         delete this._markedRangeElement;
  116.     },
  117.  
  118.     highlightLine: function(lineNumber)
  119.     {
  120.         if (typeof this._highlightedLine === "number") {
  121.             var chunk = this._makeLineAChunk(this._highlightedLine);
  122.             chunk.removeDecoration("webkit-highlighted-line");
  123.         }
  124.         this._highlightedLine = lineNumber;
  125.         this.revealLine(lineNumber);
  126.         var chunk = this._makeLineAChunk(lineNumber);
  127.         chunk.addDecoration("webkit-highlighted-line");
  128.     },
  129.  
  130.     freeCachedElements: function()
  131.     {
  132.         this._cachedSpans = [];
  133.         this._cachedTextNodes = [];
  134.         this._cachedRows = [];
  135.     },
  136.  
  137.     _buildChunks: function()
  138.     {
  139.         this._linesContainerElement.removeChildren();
  140.  
  141.         this._textChunks = [];
  142.         for (var i = 0; i < this._textModel.linesCount; i += this._defaultChunkSize) {
  143.             var chunk = new WebInspector.TextChunk(this, i, i + this._defaultChunkSize);
  144.             this._textChunks.push(chunk);
  145.             this._linesContainerElement.appendChild(chunk.element);
  146.         }
  147.  
  148.         this._indexChunks();
  149.         this._highlighter.reset();
  150.         this._repaintAll();
  151.     },
  152.  
  153.     _makeLineAChunk: function(lineNumber)
  154.     {
  155.         if (!this._textChunks)
  156.             this._buildChunks();
  157.  
  158.         var chunkNumber = this._chunkNumberForLine(lineNumber);
  159.         var oldChunk = this._textChunks[chunkNumber];
  160.         if (oldChunk.linesCount === 1)
  161.             return oldChunk;
  162.  
  163.         var wasExpanded = oldChunk.expanded;
  164.         oldChunk.expanded = false;
  165.  
  166.         var insertIndex = oldChunk.chunkNumber + 1;
  167.  
  168.         // Prefix chunk.
  169.         if (lineNumber > oldChunk.startLine) {
  170.             var prefixChunk = new WebInspector.TextChunk(this, oldChunk.startLine, lineNumber);
  171.             this._textChunks.splice(insertIndex++, 0, prefixChunk);
  172.             this._linesContainerElement.insertBefore(prefixChunk.element, oldChunk.element);
  173.         }
  174.  
  175.         // Line chunk.
  176.         var lineChunk = new WebInspector.TextChunk(this, lineNumber, lineNumber + 1);
  177.         this._textChunks.splice(insertIndex++, 0, lineChunk);
  178.         this._linesContainerElement.insertBefore(lineChunk.element, oldChunk.element);
  179.  
  180.         // Suffix chunk.
  181.         if (oldChunk.startLine + oldChunk.linesCount > lineNumber + 1) {
  182.             var suffixChunk = new WebInspector.TextChunk(this, lineNumber + 1, oldChunk.startLine + oldChunk.linesCount);
  183.             this._textChunks.splice(insertIndex, 0, suffixChunk);
  184.             this._linesContainerElement.insertBefore(suffixChunk.element, oldChunk.element);
  185.         }
  186.  
  187.         // Remove enclosing chunk.
  188.         this._textChunks.splice(oldChunk.chunkNumber, 1);
  189.         this._linesContainerElement.removeChild(oldChunk.element);
  190.         this._indexChunks();
  191.  
  192.         if (wasExpanded) {
  193.             if (prefixChunk)
  194.                 prefixChunk.expanded = true;
  195.             lineChunk.expanded = true;
  196.             if (suffixChunk)
  197.                 suffixChunk.expanded = true;
  198.         }
  199.  
  200.         return lineChunk;
  201.     },
  202.  
  203.     _indexChunks: function()
  204.     {
  205.         for (var i = 0; i < this._textChunks.length; ++i)
  206.             this._textChunks[i].chunkNumber = i;
  207.     },
  208.  
  209.     _scroll: function()
  210.     {
  211.         var scrollTop = this.element.scrollTop;
  212.         setTimeout(function() {
  213.             if (scrollTop === this.element.scrollTop)
  214.                 this._repaintAll();
  215.         }.bind(this), 50);
  216.     },
  217.  
  218.     _handleKeyDown: function()
  219.     {
  220.         if (this._editingLine || event.metaKey || event.shiftKey || event.ctrlKey || event.altKey)
  221.             return;
  222.  
  223.         var scrollValue = 0;
  224.         if (event.keyCode === WebInspector.KeyboardShortcut.KeyCodes.Up)
  225.             scrollValue = -1;
  226.         else if (event.keyCode == WebInspector.KeyboardShortcut.KeyCodes.Down)
  227.             scrollValue = 1;
  228.         
  229.         if (scrollValue) {
  230.             event.preventDefault();
  231.             event.stopPropagation();
  232.             this.element.scrollByLines(scrollValue);
  233.             return;
  234.         }
  235.         
  236.         scrollValue = 0;
  237.         if (event.keyCode === WebInspector.KeyboardShortcut.KeyCodes.Left)
  238.             scrollValue = -40;
  239.         else if (event.keyCode == WebInspector.KeyboardShortcut.KeyCodes.Right)
  240.             scrollValue = 40;
  241.         
  242.         if (scrollValue) {
  243.             event.preventDefault();
  244.             event.stopPropagation();
  245.             this.element.scrollLeft += scrollValue;
  246.         }
  247.     },
  248.  
  249.     _handleDoubleClick: function(e)
  250.     {
  251.         if (!this._editCallback)
  252.             return;
  253.  
  254.         var cell = e.target.enclosingNodeOrSelfWithNodeName("TD");
  255.         if (!cell)
  256.             return;
  257.  
  258.         var lineRow = cell.parentElement;
  259.         if (lineRow.firstChild === cell)
  260.             return;  // Do not trigger editing from line numbers.
  261.  
  262.         var oldContent = lineRow.lastChild.innerHTML;
  263.         this._editingLine = WebInspector.startEditing(lineRow.lastChild, this._commitEditingLine.bind(this, lineRow.lineNumber, lineRow.lastChild), this._cancelEditingLine.bind(this, lineRow.lastChild, oldContent), null, true);
  264.     },
  265.  
  266.     _commitEditingLine: function(lineNumber, element)
  267.     {
  268.         this._editCallback(lineNumber, element.textContent)
  269.         delete this._editingLine;
  270.     },
  271.  
  272.     _cancelEditingLine: function(element, oldContent, e)
  273.     {
  274.         element.innerHTML = oldContent;
  275.         delete this._editingLine;
  276.     },
  277.  
  278.     _beforeCopy: function(e)
  279.     {
  280.         e.preventDefault();
  281.     },
  282.  
  283.     _copy: function(e)
  284.     {
  285.         var range = this._getSelection();
  286.         var text = this._textModel.copyRange(range);
  287.         InspectorFrontendHost.copyText(text);
  288.         e.preventDefault();
  289.     },
  290.  
  291.     beginUpdates: function(enabled)
  292.     {
  293.         this._paintCoalescingLevel++;
  294.     },
  295.  
  296.     endUpdates: function(enabled)
  297.     {
  298.         this._paintCoalescingLevel--;
  299.         if (!this._paintCoalescingLevel)
  300.             this._repaintAll();
  301.     },
  302.  
  303.     _chunkForOffset: function(offset)
  304.     {
  305.         var currentOffset = 0;
  306.         var row = this._linesContainerElement.firstChild;
  307.         while (row) {
  308.             var rowHeight = row.offsetHeight;
  309.             if (offset >= currentOffset && offset < currentOffset + rowHeight)
  310.                 return row.chunkNumber;
  311.             row = row.nextSibling;
  312.             currentOffset += rowHeight;
  313.         }
  314.         return this._textChunks.length - 1;
  315.     },
  316.  
  317.     _chunkNumberForLine: function(lineNumber)
  318.     {
  319.         for (var i = 0; i < this._textChunks.length; ++i) {
  320.             var line = this._textChunks[i].startLine;
  321.             if (lineNumber >= this._textChunks[i].startLine && lineNumber < this._textChunks[i].startLine + this._textChunks[i].linesCount)
  322.                 return i;
  323.         }
  324.         return this._textChunks.length - 1;
  325.     },
  326.  
  327.     _chunkForLine: function(lineNumber)
  328.     {
  329.         return this._textChunks[this._chunkNumberForLine(lineNumber)];
  330.     },
  331.  
  332.     _chunkStartLine: function(chunkNumber)
  333.     {
  334.         var lineNumber = 0;
  335.         for (var i = 0; i < chunkNumber && i < this._textChunks.length; ++i)
  336.             lineNumber += this._textChunks[i].linesCount;
  337.         return lineNumber;
  338.     },
  339.  
  340.     _repaintAll: function()
  341.     {
  342.         if (this._paintCoalescingLevel)
  343.             return;
  344.  
  345.         if (!this._textChunks)
  346.             this._buildChunks();
  347.  
  348.         var visibleFrom = this.element.scrollTop;
  349.         var visibleTo = this.element.scrollTop + this.element.clientHeight;
  350.  
  351.         var offset = 0;
  352.         var firstVisibleLine = -1;
  353.         var lastVisibleLine = 0;
  354.         var toExpand = [];
  355.         var toCollapse = [];
  356.         for (var i = 0; i < this._textChunks.length; ++i) {
  357.             var chunk = this._textChunks[i];
  358.             var chunkHeight = chunk.height;
  359.             if (offset + chunkHeight > visibleFrom && offset < visibleTo) {
  360.                 toExpand.push(chunk);
  361.                 if (firstVisibleLine === -1)
  362.                     firstVisibleLine = chunk.startLine;
  363.                 lastVisibleLine = chunk.startLine + chunk.linesCount;
  364.             } else {
  365.                 toCollapse.push(chunk);
  366.                 if (offset >= visibleTo)
  367.                     break;
  368.             }
  369.             offset += chunkHeight;
  370.         }
  371.  
  372.         for (var j = i; j < this._textChunks.length; ++j)
  373.             toCollapse.push(this._textChunks[i]);
  374.  
  375.         var selection = this._getSelection();
  376.  
  377.         this._muteHighlightListener = true;
  378.         this._highlighter.highlight(lastVisibleLine);
  379.         delete this._muteHighlightListener;
  380.  
  381.         for (var i = 0; i < toCollapse.length; ++i)
  382.             toCollapse[i].expanded = false;
  383.         for (var i = 0; i < toExpand.length; ++i)
  384.             toExpand[i].expanded = true;
  385.  
  386.         this._restoreSelection(selection);
  387.     },
  388.  
  389.     _highlightDataReady: function(fromLine, toLine)
  390.     {
  391.         if (this._muteHighlightListener)
  392.             return;
  393.  
  394.         var selection;
  395.         for (var i = fromLine; i < toLine; ++i) {
  396.             var lineRow = this._textModel.getAttribute(i, "line-row");
  397.             if (!lineRow || lineRow.highlighted)
  398.                 continue;
  399.             if (!selection)
  400.                 selection = this._getSelection();
  401.             this._paintLine(lineRow, i);
  402.         }
  403.         this._restoreSelection(selection);
  404.     },
  405.  
  406.     _paintLines: function(fromLine, toLine)
  407.     {
  408.         for (var i = fromLine; i < toLine; ++i) {
  409.             var lineRow = this._textModel.getAttribute(i, "line-row");
  410.             if (lineRow)
  411.                 this._paintLine(lineRow, i);
  412.         }
  413.     },
  414.  
  415.     _paintLine: function(lineRow, lineNumber)
  416.     {
  417.         var element = lineRow.lastChild;
  418.         var highlight = this._textModel.getAttribute(lineNumber, "highlight");
  419.         if (!highlight) {
  420.             if (this._rangeToMark && this._rangeToMark.startLine === lineNumber)
  421.                 this._markedRangeElement = highlightSearchResult(element, this._rangeToMark.startColumn, this._rangeToMark.endColumn - this._rangeToMark.startColumn);
  422.             return;
  423.         }
  424.  
  425.         element.removeChildren();
  426.         var line = this._textModel.line(lineNumber);
  427.  
  428.         var plainTextStart = -1;
  429.         for (var j = 0; j < line.length;) {
  430.             if (j > 1000) {
  431.                 // This line is too long - do not waste cycles on minified js highlighting.
  432.                 if (plainTextStart === -1)
  433.                     plainTextStart = j;
  434.                 break;
  435.             }
  436.             var attribute = highlight[j];
  437.             if (!attribute || !attribute.tokenType) {
  438.                 if (plainTextStart === -1)
  439.                     plainTextStart = j;
  440.                 j++;
  441.             } else {
  442.                 if (plainTextStart !== -1) {
  443.                     this._appendTextNode(element, line.substring(plainTextStart, j));
  444.                     plainTextStart = -1;
  445.                 }
  446.                 this._appendSpan(element, line.substring(j, j + attribute.length), attribute.tokenType);
  447.                 j += attribute.length;
  448.             }
  449.         }
  450.         if (plainTextStart !== -1)
  451.             this._appendTextNode(element, line.substring(plainTextStart, line.length));
  452.         if (this._rangeToMark && this._rangeToMark.startLine === lineNumber)
  453.             this._markedRangeElement = highlightSearchResult(element, this._rangeToMark.startColumn, this._rangeToMark.endColumn - this._rangeToMark.startColumn);
  454.         if (lineRow.decorationsElement)
  455.             element.appendChild(lineRow.decorationsElement);
  456.     },
  457.  
  458.     _releaseLinesHighlight: function(fromLine, toLine)
  459.     {
  460.         for (var i = fromLine; i < toLine; ++i) {
  461.             var lineRow = this._textModel.getAttribute(i, "line-row");
  462.             if (!lineRow)
  463.                 continue;
  464.             var element = lineRow.lastChild;
  465.             if ("spans" in element) {
  466.                 var spans = element.spans;
  467.                 for (var j = 0; j < spans.length; ++j)
  468.                     this._cachedSpans.push(spans[j]);
  469.                 delete element.spans;
  470.             }
  471.             if ("textNodes" in element) {
  472.                 var textNodes = element.textNodes;
  473.                 for (var j = 0; j < textNodes.length; ++j)
  474.                     this._cachedTextNodes.push(textNodes[j]);
  475.                 delete element.textNodes;
  476.             }
  477.         }
  478.     },
  479.  
  480.     _getSelection: function()
  481.     {
  482.         var selection = window.getSelection();
  483.         if (selection.isCollapsed)
  484.             return null;
  485.         var selectionRange = selection.getRangeAt(0);
  486.         var start = this._selectionToPosition(selectionRange.startContainer, selectionRange.startOffset);
  487.         var end = this._selectionToPosition(selectionRange.endContainer, selectionRange.endOffset);
  488.         return new WebInspector.TextRange(start.line, start.column, end.line, end.column);
  489.     },
  490.  
  491.     _restoreSelection: function(range)
  492.     {
  493.         if (!range)
  494.             return;
  495.         var startRow = this._textModel.getAttribute(range.startLine, "line-row");
  496.         if (startRow)
  497.             var start = startRow.lastChild.rangeBoundaryForOffset(range.startColumn);
  498.         else {
  499.             var offset = range.startColumn;
  500.             var chunkNumber = this._chunkNumberForLine(range.startLine);
  501.             for (var i = this._chunkStartLine(chunkNumber); i < range.startLine; ++i)
  502.                 offset += this._textModel.line(i).length + 1; // \n
  503.             var lineCell = this._textChunks[chunkNumber].element.lastChild;
  504.             if (lineCell.firstChild)
  505.                 var start = { container: lineCell.firstChild, offset: offset };
  506.             else
  507.                 var start = { container: lineCell, offset: 0 };
  508.         }
  509.  
  510.         var endRow = this._textModel.getAttribute(range.endLine, "line-row");
  511.         if (endRow)
  512.             var end = endRow.lastChild.rangeBoundaryForOffset(range.endColumn);
  513.         else {
  514.             var offset = range.endColumn;
  515.             var chunkNumber = this._chunkNumberForLine(range.endLine);
  516.             for (var i = this._chunkStartLine(chunkNumber); i < range.endLine; ++i)
  517.                 offset += this._textModel.line(i).length + 1; // \n
  518.             var lineCell = this._textChunks[chunkNumber].element.lastChild;
  519.             if (lineCell.firstChild)
  520.                 var end = { container: lineCell.firstChild, offset: offset };
  521.             else
  522.                 var end = { container: lineCell, offset: 0 };
  523.         }
  524.  
  525.         var selectionRange = document.createRange();
  526.         selectionRange.setStart(start.container, start.offset);
  527.         selectionRange.setEnd(end.container, end.offset);
  528.  
  529.         var selection = window.getSelection();
  530.         selection.removeAllRanges();
  531.         selection.addRange(selectionRange);
  532.     },
  533.  
  534.     _selectionToPosition: function(container, offset)
  535.     {
  536.         if (container === this.element && offset === 0)
  537.             return { line: 0, column: 0 };
  538.         if (container === this.element && offset === 1)
  539.             return { line: this._textModel.linesCount - 1, column: this._textModel.lineLength(this._textModel.linesCount - 1) };
  540.  
  541.         var lineRow = container.enclosingNodeOrSelfWithNodeName("tr");
  542.         var lineNumber = lineRow.lineNumber;
  543.         if (container.nodeName === "TD" && offset === 0)
  544.             return { line: lineNumber, column: 0 };
  545.         if (container.nodeName === "TD" && offset === 1)
  546.             return { line: lineNumber, column: this._textModel.lineLength(lineNumber) };
  547.  
  548.         var column = 0;
  549.         if (lineRow.chunk) {
  550.             // This is chunk.
  551.             var text = lineRow.lastChild.textContent;
  552.             for (var i = 0; i < offset; ++i) {
  553.                 if (text.charAt(i) === "\n") {
  554.                     lineNumber++;
  555.                     column = 0;
  556.                 } else
  557.                     column++; 
  558.             }
  559.             return { line: lineNumber, column: column };
  560.         }
  561.  
  562.         // This is individul line.
  563.         var column = 0;
  564.         var node = lineRow.lastChild.traverseNextTextNode(lineRow.lastChild);
  565.         while (node && node !== container) {
  566.             column += node.textContent.length;
  567.             node = node.traverseNextTextNode(lineRow.lastChild);
  568.         }
  569.         column += offset;
  570.         return { line: lineRow.lineNumber, column: column };
  571.     },
  572.  
  573.     _appendSpan: function(element, content, className)
  574.     {
  575.         if (className === "html-resource-link" || className === "html-external-link") {
  576.             element.appendChild(this._createLink(content, className === "html-external-link"));
  577.             return;
  578.         }
  579.  
  580.         var span = this._cachedSpans.pop() || document.createElement("span");
  581.         span.className = "webkit-" + className;
  582.         span.textContent = content;
  583.         element.appendChild(span);
  584.         if (!("spans" in element))
  585.             element.spans = [];
  586.         element.spans.push(span);
  587.     },
  588.  
  589.     _appendTextNode: function(element, text)
  590.     {
  591.         var textNode = this._cachedTextNodes.pop();
  592.         if (textNode) {
  593.             textNode.nodeValue = text;
  594.         } else
  595.             textNode = document.createTextNode(text);
  596.         element.appendChild(textNode);
  597.         if (!("textNodes" in element))
  598.             element.textNodes = [];
  599.         element.textNodes.push(textNode);
  600.     },
  601.  
  602.     _createLink: function(content, isExternal)
  603.     {
  604.         var quote = content.charAt(0);
  605.         if (content.length > 1 && (quote === "\"" ||   quote === "'"))
  606.             content = content.substring(1, content.length - 1);
  607.         else
  608.             quote = null;
  609.  
  610.         var a = WebInspector.linkifyURLAsNode(this._rewriteHref(content), content, null, isExternal);
  611.         var span = document.createElement("span");
  612.         span.className = "webkit-html-attribute-value";
  613.         if (quote)
  614.             span.appendChild(document.createTextNode(quote));
  615.         span.appendChild(a);
  616.         if (quote)
  617.             span.appendChild(document.createTextNode(quote));
  618.         return span;
  619.     },
  620.  
  621.     _rewriteHref: function(hrefValue, isExternal)
  622.     {
  623.         if (!this._url || !hrefValue || hrefValue.indexOf("://") > 0)
  624.             return hrefValue;
  625.         return WebInspector.completeURL(this._url, hrefValue);
  626.     },
  627.  
  628.     resize: function()
  629.     {
  630.         this._repaintAll();
  631.     }
  632. }
  633.  
  634. var cachedSpans = [];
  635.  
  636. WebInspector.TextChunk = function(textViewer, startLine, endLine)
  637. {
  638.     this._textViewer = textViewer;
  639.     this.element = document.createElement("tr");
  640.     this._textModel = textViewer._textModel;
  641.     this.element.chunk = this;
  642.     this.element.lineNumber = startLine;
  643.  
  644.     this.startLine = startLine;
  645.     endLine = Math.min(this._textModel.linesCount, endLine);
  646.     this.linesCount = endLine - startLine;
  647.  
  648.     this._lineNumberElement = document.createElement("td");
  649.     this._lineNumberElement.className = "webkit-line-number";
  650.     this.element.appendChild(this._lineNumberElement);
  651.  
  652.     this._lineContentElement = document.createElement("td");
  653.     this._lineContentElement.className = "webkit-line-content";
  654.     this.element.appendChild(this._lineContentElement);
  655.  
  656.     this._expanded = false;
  657.  
  658.     var lineNumbers = [];
  659.     var lines = [];
  660.     for (var i = startLine; i < endLine; ++i) {
  661.         lineNumbers.push(i + 1);
  662.         lines.push(this._textModel.line(i));
  663.     }
  664.     if (this.linesCount === 1) {
  665.         // Single line chunks are typically created for decorations. Host line number in
  666.         // the sub-element in order to allow flexible border / margin management.
  667.         var innerSpan = document.createElement("span");
  668.         innerSpan.className = "webkit-line-number-inner";
  669.         innerSpan.textContent = startLine + 1;
  670.         var outerSpan = document.createElement("div");
  671.         outerSpan.className = "webkit-line-number-outer";
  672.         outerSpan.appendChild(innerSpan);
  673.         this._lineNumberElement.appendChild(outerSpan);
  674.     } else
  675.         this._lineNumberElement.textContent = lineNumbers.join("\n");
  676.     this._lineContentElement.textContent = lines.join("\n");
  677. }
  678.  
  679. WebInspector.TextChunk.prototype = {
  680.     addDecoration: function(decoration)
  681.     {
  682.         if (typeof decoration === "string") {
  683.             this.element.addStyleClass(decoration);
  684.             return;
  685.         }
  686.         if (!this.element.decorationsElement) {
  687.             this.element.decorationsElement = document.createElement("div");
  688.             this._lineContentElement.appendChild(this.element.decorationsElement);
  689.         }
  690.         this.element.decorationsElement.appendChild(decoration);
  691.     },
  692.  
  693.     removeDecoration: function(decoration)
  694.     {
  695.         if (typeof decoration === "string") {
  696.             this.element.removeStyleClass(decoration);
  697.             return;
  698.         }
  699.         if (!this.element.decorationsElement)
  700.             return;
  701.         this.element.decorationsElement.removeChild(decoration);
  702.     },
  703.  
  704.     get expanded()
  705.     {
  706.         return this._expanded;
  707.     },
  708.  
  709.     set expanded(expanded)
  710.     {
  711.         if (this._expanded === expanded)
  712.             return;
  713.  
  714.         this._expanded = expanded;
  715.  
  716.         if (this.linesCount === 1) {
  717.             this._textModel.setAttribute(this.startLine, "line-row", this.element);
  718.             if (expanded)
  719.                 this._textViewer._paintLines(this.startLine, this.startLine + 1);
  720.             return;
  721.         }
  722.  
  723.         if (expanded) {
  724.             var parentElement = this.element.parentElement;
  725.             for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) {
  726.                 var lineRow = this._createRow(i);
  727.                 this._textModel.setAttribute(i, "line-row", lineRow);
  728.                 parentElement.insertBefore(lineRow, this.element);
  729.             }
  730.             parentElement.removeChild(this.element);
  731.  
  732.             this._textViewer._paintLines(this.startLine, this.startLine + this.linesCount);
  733.         } else {
  734.             var firstLine = this._textModel.getAttribute(this.startLine, "line-row");
  735.             var parentElement = firstLine.parentElement;
  736.             this._textViewer._releaseLinesHighlight(this.startLine, this.startLine + this.linesCount);
  737.  
  738.             parentElement.insertBefore(this.element, firstLine);
  739.             for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) {
  740.                 var lineRow = this._textModel.getAttribute(i, "line-row");
  741.                 this._textModel.removeAttribute(i, "line-row");
  742.                 this._textViewer._cachedRows.push(lineRow);
  743.                 parentElement.removeChild(lineRow);
  744.             }
  745.         }
  746.     },
  747.  
  748.     get height()
  749.     {
  750.         if (!this._expanded)
  751.             return this.element.offsetHeight;
  752.         var result = 0;
  753.         for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) {
  754.             var lineRow = this._textModel.getAttribute(i, "line-row");
  755.             result += lineRow.offsetHeight;
  756.         }
  757.         return result;
  758.     },
  759.  
  760.     _createRow: function(lineNumber)
  761.     {
  762.         var cachedRows = this._textViewer._cachedRows;
  763.         if (cachedRows.length) {
  764.             var lineRow = cachedRows[cachedRows.length - 1];
  765.             cachedRows.length--;
  766.             var lineNumberElement = lineRow.firstChild;
  767.             var lineContentElement = lineRow.lastChild;
  768.         } else {
  769.             var lineRow = document.createElement("tr");
  770.  
  771.             var lineNumberElement = document.createElement("td");
  772.             lineNumberElement.className = "webkit-line-number";
  773.             lineRow.appendChild(lineNumberElement);
  774.  
  775.             var lineContentElement = document.createElement("td");
  776.             lineContentElement.className = "webkit-line-content";
  777.             lineRow.appendChild(lineContentElement);        
  778.         }
  779.         lineRow.lineNumber = lineNumber;
  780.         lineNumberElement.textContent = lineNumber + 1;
  781.         lineContentElement.textContent = this._textModel.line(lineNumber);
  782.         return lineRow;
  783.     }
  784. }
  785.