summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/inspector/front-end/TextViewer.js
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/inspector/front-end/TextViewer.js')
-rw-r--r--Source/WebCore/inspector/front-end/TextViewer.js964
1 files changed, 622 insertions, 342 deletions
diff --git a/Source/WebCore/inspector/front-end/TextViewer.js b/Source/WebCore/inspector/front-end/TextViewer.js
index ea36513..ce6502d 100644
--- a/Source/WebCore/inspector/front-end/TextViewer.js
+++ b/Source/WebCore/inspector/front-end/TextViewer.js
@@ -32,37 +32,23 @@
WebInspector.TextViewer = function(textModel, platform, url)
{
this._textModel = textModel;
- this._textModel.changeListener = this._buildChunks.bind(this);
- this._highlighter = new WebInspector.TextEditorHighlighter(this._textModel, this._highlightDataReady.bind(this));
+ this._textModel.changeListener = this._textChanged.bind(this);
this.element = document.createElement("div");
this.element.className = "text-editor monospace";
- this.element.tabIndex = 0;
-
- this.element.addEventListener("scroll", this._scroll.bind(this), false);
- this.element.addEventListener("keydown", this._handleKeyDown.bind(this), false);
- this.element.addEventListener("beforecopy", this._beforeCopy.bind(this), false);
- this.element.addEventListener("copy", this._copy.bind(this), false);
-
- this._url = url;
-
- this._linesContainerElement = document.createElement("table");
- this._linesContainerElement.className = "text-editor-lines";
- this._linesContainerElement.setAttribute("cellspacing", 0);
- this._linesContainerElement.setAttribute("cellpadding", 0);
- this.element.appendChild(this._linesContainerElement);
-
- this._defaultChunkSize = 50;
- this._paintCoalescingLevel = 0;
- this.freeCachedElements();
- this._buildChunks();
+ var syncScrollListener = this._syncScroll.bind(this);
+ var syncDecorationsForLineListener = this._syncDecorationsForLine.bind(this);
+ this._mainPanel = new WebInspector.TextEditorMainPanel(this._textModel, url, syncScrollListener, syncDecorationsForLineListener);
+ this._gutterPanel = new WebInspector.TextEditorGutterPanel(this._textModel, syncDecorationsForLineListener);
+ this.element.appendChild(this._mainPanel.element);
+ this.element.appendChild(this._gutterPanel.element);
}
WebInspector.TextViewer.prototype = {
set mimeType(mimeType)
{
- this._highlighter.mimeType = mimeType;
+ this._mainPanel.mimeType = mimeType;
},
get textModel()
@@ -72,85 +58,190 @@ WebInspector.TextViewer.prototype = {
revealLine: function(lineNumber)
{
- if (lineNumber >= this._textModel.linesCount)
- return;
-
- var chunk = this._makeLineAChunk(lineNumber);
- chunk.element.scrollIntoViewIfNeeded();
+ this._mainPanel.revealLine(lineNumber);
},
addDecoration: function(lineNumber, decoration)
{
- var chunk = this._makeLineAChunk(lineNumber);
- chunk.addDecoration(decoration);
+ this._mainPanel.addDecoration(lineNumber, decoration);
+ this._gutterPanel.addDecoration(lineNumber, decoration);
},
removeDecoration: function(lineNumber, decoration)
{
- var chunk = this._makeLineAChunk(lineNumber);
- chunk.removeDecoration(decoration);
+ this._mainPanel.removeDecoration(lineNumber, decoration);
+ this._gutterPanel.removeDecoration(lineNumber, decoration);
},
markAndRevealRange: function(range)
{
- if (this._rangeToMark) {
- var markedLine = this._rangeToMark.startLine;
- this._rangeToMark = null;
- this._paintLines(markedLine, markedLine + 1);
- }
-
- if (range) {
- this._rangeToMark = range;
- this.revealLine(range.startLine);
- this._paintLines(range.startLine, range.startLine + 1);
- if (this._markedRangeElement)
- this._markedRangeElement.scrollIntoViewIfNeeded();
- }
- delete this._markedRangeElement;
+ this._mainPanel.markAndRevealRange(range);
},
highlightLine: function(lineNumber)
{
- this.clearLineHighlight();
- this._highlightedLine = lineNumber;
- this.revealLine(lineNumber);
- var chunk = this._makeLineAChunk(lineNumber);
- chunk.addDecoration("webkit-highlighted-line");
+ this._mainPanel.highlightLine(lineNumber);
},
clearLineHighlight: function()
{
- if (typeof this._highlightedLine === "number") {
- var chunk = this._makeLineAChunk(this._highlightedLine);
- chunk.removeDecoration("webkit-highlighted-line");
- delete this._highlightedLine;
- }
+ this._mainPanel.clearLineHighlight();
},
freeCachedElements: function()
{
- this._cachedSpans = [];
- this._cachedTextNodes = [];
- this._cachedRows = [];
+ this._mainPanel.freeCachedElements();
+ this._gutterPanel.freeCachedElements();
+ },
+
+ editLine: function(lineRow, callback)
+ {
+ this._mainPanel.editLine(lineRow, callback);
+ },
+
+ get scrollTop()
+ {
+ return this._mainPanel.element.scrollTop;
+ },
+
+ set scrollTop(scrollTop)
+ {
+ this._mainPanel.element.scrollTop = scrollTop;
+ },
+
+ get scrollLeft()
+ {
+ return this._mainPanel.element.scrollLeft;
+ },
+
+ set scrollLeft(scrollLeft)
+ {
+ this._mainPanel.element.scrollLeft = scrollLeft;
+ },
+
+ beginUpdates: function()
+ {
+ this._mainPanel.beginUpdates();
+ this._gutterPanel.beginUpdates();
+ },
+
+ endUpdates: function()
+ {
+ this._mainPanel.endUpdates();
+ this._gutterPanel.endUpdates();
+ },
+
+ resize: function()
+ {
+ this._mainPanel.resize();
+ this._gutterPanel.resize();
+ this._updatePanelOffsets();
+ },
+
+ // WebInspector.TextModel listener
+ _textChanged: function(oldRange, newRange, oldText, newText)
+ {
+ this._mainPanel.textChanged();
+ this._gutterPanel.textChanged();
+ this._updatePanelOffsets();
+ },
+
+ _updatePanelOffsets: function()
+ {
+ var lineNumbersWidth = this._gutterPanel.element.offsetWidth;
+ if (lineNumbersWidth)
+ this._mainPanel.element.style.setProperty("left", lineNumbersWidth + "px");
+ else
+ this._mainPanel.element.style.removeProperty("left"); // Use default value set in CSS.
+ },
+
+ _syncScroll: function()
+ {
+ // Async call due to performance reasons.
+ setTimeout(function() {
+ var mainElement = this._mainPanel.element;
+ var gutterElement = this._gutterPanel.element;
+
+ // Handle horizontal scroll bar at the bottom of the main panel.
+ if (gutterElement.offsetHeight > mainElement.clientHeight)
+ gutterElement.style.setProperty("padding-bottom", (gutterElement.offsetHeight - mainElement.clientHeight) + "px");
+ else
+ gutterElement.style.removeProperty("padding-bottom");
+
+ gutterElement.scrollTop = mainElement.scrollTop;
+ }.bind(this), 0);
+ },
+
+ _syncDecorationsForLine: function(lineNumber)
+ {
+ if (lineNumber >= this._textModel.linesCount)
+ return;
+
+ var mainChunk = this._mainPanel.makeLineAChunk(lineNumber);
+ var gutterChunk = this._gutterPanel.makeLineAChunk(lineNumber);
+ var height = mainChunk.height;
+ if (height)
+ gutterChunk.element.style.setProperty("height", height + "px");
+ else
+ gutterChunk.element.style.removeProperty("height");
+ }
+}
+
+WebInspector.TextEditorChunkedPanel = function(textModel)
+{
+ this._textModel = textModel;
+
+ this._defaultChunkSize = 50;
+ this._paintCoalescingLevel = 0;
+}
+
+WebInspector.TextEditorChunkedPanel.prototype = {
+ get textModel()
+ {
+ return this._textModel;
+ },
+
+ revealLine: function(lineNumber)
+ {
+ if (lineNumber >= this._textModel.linesCount)
+ return;
+
+ var chunk = this.makeLineAChunk(lineNumber);
+ chunk.element.scrollIntoViewIfNeeded();
+ },
+
+ addDecoration: function(lineNumber, decoration)
+ {
+ var chunk = this.makeLineAChunk(lineNumber);
+ chunk.addDecoration(decoration);
+ },
+
+ removeDecoration: function(lineNumber, decoration)
+ {
+ var chunk = this.makeLineAChunk(lineNumber);
+ chunk.removeDecoration(decoration);
+ },
+
+ textChanged: function(oldRange, newRange, oldText, newText)
+ {
+ this._buildChunks();
},
_buildChunks: function()
{
- this._linesContainerElement.removeChildren();
+ this.element.removeChildren();
this._textChunks = [];
for (var i = 0; i < this._textModel.linesCount; i += this._defaultChunkSize) {
- var chunk = new WebInspector.TextChunk(this, i, i + this._defaultChunkSize);
+ var chunk = this._createNewChunk(i, i + this._defaultChunkSize);
this._textChunks.push(chunk);
- this._linesContainerElement.appendChild(chunk.element);
+ this.element.appendChild(chunk.element);
}
- this._indexChunks();
- this._highlighter.reset();
this._repaintAll();
},
- _makeLineAChunk: function(lineNumber)
+ makeLineAChunk: function(lineNumber)
{
if (!this._textChunks)
this._buildChunks();
@@ -163,31 +254,30 @@ WebInspector.TextViewer.prototype = {
var wasExpanded = oldChunk.expanded;
oldChunk.expanded = false;
- var insertIndex = oldChunk.chunkNumber + 1;
+ var insertIndex = chunkNumber + 1;
// Prefix chunk.
if (lineNumber > oldChunk.startLine) {
- var prefixChunk = new WebInspector.TextChunk(this, oldChunk.startLine, lineNumber);
+ var prefixChunk = this._createNewChunk(oldChunk.startLine, lineNumber);
this._textChunks.splice(insertIndex++, 0, prefixChunk);
- this._linesContainerElement.insertBefore(prefixChunk.element, oldChunk.element);
+ this.element.insertBefore(prefixChunk.element, oldChunk.element);
}
// Line chunk.
- var lineChunk = new WebInspector.TextChunk(this, lineNumber, lineNumber + 1);
+ var lineChunk = this._createNewChunk(lineNumber, lineNumber + 1);
this._textChunks.splice(insertIndex++, 0, lineChunk);
- this._linesContainerElement.insertBefore(lineChunk.element, oldChunk.element);
+ this.element.insertBefore(lineChunk.element, oldChunk.element);
// Suffix chunk.
if (oldChunk.startLine + oldChunk.linesCount > lineNumber + 1) {
- var suffixChunk = new WebInspector.TextChunk(this, lineNumber + 1, oldChunk.startLine + oldChunk.linesCount);
+ var suffixChunk = this._createNewChunk(lineNumber + 1, oldChunk.startLine + oldChunk.linesCount);
this._textChunks.splice(insertIndex, 0, suffixChunk);
- this._linesContainerElement.insertBefore(suffixChunk.element, oldChunk.element);
+ this.element.insertBefore(suffixChunk.element, oldChunk.element);
}
// Remove enclosing chunk.
- this._textChunks.splice(oldChunk.chunkNumber, 1);
- this._linesContainerElement.removeChild(oldChunk.element);
- this._indexChunks();
+ this._textChunks.splice(chunkNumber, 1);
+ this.element.removeChild(oldChunk.element);
if (wasExpanded) {
if (prefixChunk)
@@ -200,19 +290,315 @@ WebInspector.TextViewer.prototype = {
return lineChunk;
},
- _indexChunks: function()
+ _scroll: function()
{
- for (var i = 0; i < this._textChunks.length; ++i)
- this._textChunks[i].chunkNumber = i;
+ this._scheduleRepaintAll();
+ if (this._syncScrollListener)
+ this._syncScrollListener();
},
- _scroll: function()
+ _scheduleRepaintAll: function()
{
- var scrollTop = this.element.scrollTop;
- setTimeout(function() {
- if (scrollTop === this.element.scrollTop)
- this._repaintAll();
- }.bind(this), 50);
+ if (this._repaintAllTimer)
+ clearTimeout(this._repaintAllTimer);
+ this._repaintAllTimer = setTimeout(this._repaintAll.bind(this), 50);
+ },
+
+ beginUpdates: function()
+ {
+ this._paintCoalescingLevel++;
+ },
+
+ endUpdates: function()
+ {
+ this._paintCoalescingLevel--;
+ if (!this._paintCoalescingLevel)
+ this._repaintAll();
+ },
+
+ _chunkNumberForLine: function(lineNumber)
+ {
+ for (var i = 0; i < this._textChunks.length; ++i) {
+ var line = this._textChunks[i].startLine;
+ if (lineNumber >= line && lineNumber < line + this._textChunks[i].linesCount)
+ return i;
+ }
+ return this._textChunks.length - 1;
+ },
+
+ _chunkForLine: function(lineNumber)
+ {
+ return this._textChunks[this._chunkNumberForLine(lineNumber)];
+ },
+
+ _repaintAll: function()
+ {
+ delete this._repaintAllTimer;
+
+ if (this._paintCoalescingLevel)
+ return;
+
+ if (!this._textChunks)
+ this._buildChunks();
+
+ var visibleFrom = this.element.scrollTop;
+ var visibleTo = this.element.scrollTop + this.element.clientHeight;
+
+ var offset = 0;
+ var fromIndex = -1;
+ var toIndex = 0;
+ for (var i = 0; i < this._textChunks.length; ++i) {
+ var chunk = this._textChunks[i];
+ var chunkHeight = chunk.height;
+ if (offset + chunkHeight > visibleFrom && offset < visibleTo) {
+ if (fromIndex === -1)
+ fromIndex = i;
+ toIndex = i + 1;
+ } else {
+ if (offset >= visibleTo)
+ break;
+ }
+ offset += chunkHeight;
+ }
+
+ if (toIndex)
+ this._expandChunks(fromIndex, toIndex);
+ },
+
+ _totalHeight: function(firstElement, lastElement)
+ {
+ lastElement = (lastElement || firstElement).nextElementSibling;
+ if (lastElement)
+ return lastElement.offsetTop - firstElement.offsetTop;
+ else if (firstElement.offsetParent)
+ return firstElement.offsetParent.scrollHeight - firstElement.offsetTop;
+ return firstElement.offsetHeight;
+ },
+
+ resize: function()
+ {
+ this._repaintAll();
+ }
+}
+
+WebInspector.TextEditorGutterPanel = function(textModel, syncDecorationsForLineListener)
+{
+ WebInspector.TextEditorChunkedPanel.call(this, textModel);
+
+ this._syncDecorationsForLineListener = syncDecorationsForLineListener;
+
+ this.element = document.createElement("div");
+ this.element.className = "text-editor-lines";
+
+ this.element.addEventListener("scroll", this._scroll.bind(this), false);
+
+ this.freeCachedElements();
+ this._buildChunks();
+}
+
+WebInspector.TextEditorGutterPanel.prototype = {
+ freeCachedElements: function()
+ {
+ this._cachedRows = [];
+ },
+
+ _createNewChunk: function(startLine, endLine)
+ {
+ return new WebInspector.TextEditorGutterChunk(this, startLine, endLine);
+ },
+
+ _expandChunks: function(fromIndex, toIndex)
+ {
+ for (var i = 0; i < this._textChunks.length; ++i) {
+ this._textChunks[i].expanded = (fromIndex <= i && i < toIndex);
+ }
+ }
+}
+
+WebInspector.TextEditorGutterPanel.prototype.__proto__ = WebInspector.TextEditorChunkedPanel.prototype;
+
+WebInspector.TextEditorGutterChunk = function(textViewer, startLine, endLine)
+{
+ this._textViewer = textViewer;
+ this._textModel = textViewer._textModel;
+
+ this.startLine = startLine;
+ endLine = Math.min(this._textModel.linesCount, endLine);
+ this.linesCount = endLine - startLine;
+
+ this._expanded = false;
+
+ this.element = document.createElement("div");
+ this.element.lineNumber = startLine;
+ this.element.className = "webkit-line-number";
+
+ if (this.linesCount === 1) {
+ // Single line chunks are typically created for decorations. Host line number in
+ // the sub-element in order to allow flexible border / margin management.
+ var innerSpan = document.createElement("span");
+ innerSpan.className = "webkit-line-number-inner";
+ innerSpan.textContent = startLine + 1;
+ var outerSpan = document.createElement("div");
+ outerSpan.className = "webkit-line-number-outer";
+ outerSpan.appendChild(innerSpan);
+ this.element.appendChild(outerSpan);
+ } else {
+ var lineNumbers = [];
+ for (var i = startLine; i < endLine; ++i) {
+ lineNumbers.push(i + 1);
+ }
+ this.element.textContent = lineNumbers.join("\n");
+ }
+}
+
+WebInspector.TextEditorGutterChunk.prototype = {
+ addDecoration: function(decoration)
+ {
+ if (typeof decoration === "string") {
+ this.element.addStyleClass(decoration);
+ }
+ },
+
+ removeDecoration: function(decoration)
+ {
+ if (typeof decoration === "string") {
+ this.element.removeStyleClass(decoration);
+ }
+ },
+
+ get expanded()
+ {
+ return this._expanded;
+ },
+
+ set expanded(expanded)
+ {
+ if (this.linesCount === 1)
+ this._textViewer._syncDecorationsForLineListener(this.startLine);
+
+ if (this._expanded === expanded)
+ return;
+
+ this._expanded = expanded;
+
+ if (this.linesCount === 1)
+ return;
+
+ if (expanded) {
+ this._expandedLineRows = [];
+ var parentElement = this.element.parentElement;
+ for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) {
+ var lineRow = this._createRow(i);
+ parentElement.insertBefore(lineRow, this.element);
+ this._expandedLineRows.push(lineRow);
+ }
+ parentElement.removeChild(this.element);
+ } else {
+ var elementInserted = false;
+ for (var i = 0; i < this._expandedLineRows.length; ++i) {
+ var lineRow = this._expandedLineRows[i];
+ var parentElement = lineRow.parentElement;
+ if (parentElement) {
+ if (!elementInserted) {
+ elementInserted = true;
+ parentElement.insertBefore(this.element, lineRow);
+ }
+ this._textViewer._cachedRows.push(lineRow);
+ parentElement.removeChild(lineRow);
+ }
+ }
+ delete this._expandedLineRows;
+ }
+ },
+
+ get height()
+ {
+ if (!this._expandedLineRows)
+ return this._textViewer._totalHeight(this.element);
+ return this._textViewer._totalHeight(this._expandedLineRows[0], this._expandedLineRows[this._expandedLineRows.length - 1]);
+ },
+
+ _createRow: function(lineNumber)
+ {
+ var lineRow = this._textViewer._cachedRows.pop() || document.createElement("div");
+ lineRow.lineNumber = lineNumber;
+ lineRow.className = "webkit-line-number";
+ lineRow.textContent = lineNumber + 1;
+ return lineRow;
+ }
+}
+
+WebInspector.TextEditorMainPanel = function(textModel, url, syncScrollListener, syncDecorationsForLineListener)
+{
+ WebInspector.TextEditorChunkedPanel.call(this, textModel);
+
+ this._syncScrollListener = syncScrollListener;
+ this._syncDecorationsForLineListener = syncDecorationsForLineListener;
+
+ this._url = url;
+ this._highlighter = new WebInspector.TextEditorHighlighter(textModel, this._highlightDataReady.bind(this));
+
+ this.element = document.createElement("div");
+ this.element.className = "text-editor-contents";
+ this.element.tabIndex = 0;
+
+ this.element.addEventListener("scroll", this._scroll.bind(this), false);
+ this.element.addEventListener("keydown", this._handleKeyDown.bind(this), false);
+
+ var handleDOMUpdates = this._handleDOMUpdates.bind(this);
+ this.element.addEventListener("DOMCharacterDataModified", handleDOMUpdates, false);
+ this.element.addEventListener("DOMNodeInserted", handleDOMUpdates, false);
+ this.element.addEventListener("DOMNodeRemoved", handleDOMUpdates, false);
+
+ this.freeCachedElements();
+ this._buildChunks();
+}
+
+WebInspector.TextEditorMainPanel.prototype = {
+ set mimeType(mimeType)
+ {
+ this._highlighter.mimeType = mimeType;
+ },
+
+ markAndRevealRange: function(range)
+ {
+ if (this._rangeToMark) {
+ var markedLine = this._rangeToMark.startLine;
+ this._rangeToMark = null;
+ this._paintLines(markedLine, markedLine + 1);
+ }
+
+ if (range) {
+ this._rangeToMark = range;
+ this.revealLine(range.startLine);
+ this._paintLines(range.startLine, range.startLine + 1);
+ if (this._markedRangeElement)
+ this._markedRangeElement.scrollIntoViewIfNeeded();
+ }
+ delete this._markedRangeElement;
+ },
+
+ highlightLine: function(lineNumber)
+ {
+ this.clearLineHighlight();
+ this._highlightedLine = lineNumber;
+ this.revealLine(lineNumber);
+ this.addDecoration(lineNumber, "webkit-highlighted-line");
+ },
+
+ clearLineHighlight: function()
+ {
+ if (typeof this._highlightedLine === "number") {
+ this.removeDecoration(this._highlightedLine, "webkit-highlighted-line");
+ delete this._highlightedLine;
+ }
+ },
+
+ freeCachedElements: function()
+ {
+ this._cachedSpans = [];
+ this._cachedTextNodes = [];
+ this._cachedRows = [];
},
_handleKeyDown: function()
@@ -248,16 +634,15 @@ WebInspector.TextViewer.prototype = {
editLine: function(lineRow, callback)
{
- var element = lineRow.lastChild;
- var oldContent = element.innerHTML;
+ var oldContent = lineRow.innerHTML;
function finishEditing(committed, e, newContent)
{
if (committed)
callback(newContent);
- element.innerHTML = oldContent;
+ lineRow.innerHTML = oldContent;
delete this._editingLine;
}
- this._editingLine = WebInspector.startEditing(element, {
+ this._editingLine = WebInspector.startEditing(lineRow, {
context: null,
commitHandler: finishEditing.bind(this, true),
cancelHandler: finishEditing.bind(this, false),
@@ -265,102 +650,21 @@ WebInspector.TextViewer.prototype = {
});
},
- _beforeCopy: function(e)
- {
- e.preventDefault();
- },
-
- _copy: function(e)
- {
- var range = this._getSelection();
- var text = this._textModel.copyRange(range);
- InspectorFrontendHost.copyText(text);
- e.preventDefault();
- },
-
- beginUpdates: function(enabled)
- {
- this._paintCoalescingLevel++;
- },
-
- endUpdates: function(enabled)
- {
- this._paintCoalescingLevel--;
- if (!this._paintCoalescingLevel)
- this._repaintAll();
- },
-
- _chunkForOffset: function(offset)
- {
- var currentOffset = 0;
- var row = this._linesContainerElement.firstChild;
- while (row) {
- var rowHeight = row.offsetHeight;
- if (offset >= currentOffset && offset < currentOffset + rowHeight)
- return row.chunkNumber;
- row = row.nextSibling;
- currentOffset += rowHeight;
- }
- return this._textChunks.length - 1;
- },
-
- _chunkNumberForLine: function(lineNumber)
- {
- for (var i = 0; i < this._textChunks.length; ++i) {
- var line = this._textChunks[i].startLine;
- if (lineNumber >= this._textChunks[i].startLine && lineNumber < this._textChunks[i].startLine + this._textChunks[i].linesCount)
- return i;
- }
- return this._textChunks.length - 1;
- },
-
- _chunkForLine: function(lineNumber)
+ _buildChunks: function()
{
- return this._textChunks[this._chunkNumberForLine(lineNumber)];
+ this._highlighter.reset();
+ WebInspector.TextEditorChunkedPanel.prototype._buildChunks.call(this);
},
- _chunkStartLine: function(chunkNumber)
+ _createNewChunk: function(startLine, endLine)
{
- var lineNumber = 0;
- for (var i = 0; i < chunkNumber && i < this._textChunks.length; ++i)
- lineNumber += this._textChunks[i].linesCount;
- return lineNumber;
+ return new WebInspector.TextEditorMainChunk(this, startLine, endLine);
},
- _repaintAll: function()
+ _expandChunks: function(fromIndex, toIndex)
{
- if (this._paintCoalescingLevel)
- return;
-
- if (!this._textChunks)
- this._buildChunks();
-
- var visibleFrom = this.element.scrollTop;
- var visibleTo = this.element.scrollTop + this.element.clientHeight;
-
- var offset = 0;
- var firstVisibleLine = -1;
- var lastVisibleLine = 0;
- var toExpand = [];
- var toCollapse = [];
- for (var i = 0; i < this._textChunks.length; ++i) {
- var chunk = this._textChunks[i];
- var chunkHeight = chunk.height;
- if (offset + chunkHeight > visibleFrom && offset < visibleTo) {
- toExpand.push(chunk);
- if (firstVisibleLine === -1)
- firstVisibleLine = chunk.startLine;
- lastVisibleLine = chunk.startLine + chunk.linesCount;
- } else {
- toCollapse.push(chunk);
- if (offset >= visibleTo)
- break;
- }
- offset += chunkHeight;
- }
-
- for (var j = i; j < this._textChunks.length; ++j)
- toCollapse.push(this._textChunks[i]);
+ var lastChunk = this._textChunks[toIndex - 1];
+ var lastVisibleLine = lastChunk.startLine + lastChunk.linesCount;
var selection = this._getSelection();
@@ -368,10 +672,9 @@ WebInspector.TextViewer.prototype = {
this._highlighter.highlight(lastVisibleLine);
delete this._muteHighlightListener;
- for (var i = 0; i < toCollapse.length; ++i)
- toCollapse[i].expanded = false;
- for (var i = 0; i < toExpand.length; ++i)
- toExpand[i].expanded = true;
+ for (var i = 0; i < this._textChunks.length; ++i) {
+ this._textChunks[i].expanded = (fromIndex <= i && i < toIndex);
+ }
this._restoreSelection(selection);
},
@@ -380,40 +683,40 @@ WebInspector.TextViewer.prototype = {
{
if (this._muteHighlightListener)
return;
+ this._paintLines(fromLine, toLine, true /*restoreSelection*/);
+ },
+ _paintLines: function(fromLine, toLine, restoreSelection)
+ {
var selection;
+ var chunk = this._chunkForLine(fromLine);
for (var i = fromLine; i < toLine; ++i) {
- var lineRow = this._textModel.getAttribute(i, "line-row");
- if (!lineRow || lineRow.highlighted)
+ if (i >= chunk.startLine + chunk.linesCount)
+ chunk = this._chunkForLine(i);
+ var lineRow = chunk.getExpandedLineRow(i);
+ if (!lineRow)
continue;
- if (!selection)
+ if (restoreSelection && !selection)
selection = this._getSelection();
this._paintLine(lineRow, i);
}
- this._restoreSelection(selection);
- },
-
- _paintLines: function(fromLine, toLine)
- {
- for (var i = fromLine; i < toLine; ++i) {
- var lineRow = this._textModel.getAttribute(i, "line-row");
- if (lineRow)
- this._paintLine(lineRow, i);
- }
+ if (restoreSelection)
+ this._restoreSelection(selection);
},
_paintLine: function(lineRow, lineNumber)
{
- var element = lineRow.lastChild;
var highlight = this._textModel.getAttribute(lineNumber, "highlight");
if (!highlight) {
if (this._rangeToMark && this._rangeToMark.startLine === lineNumber)
- this._markedRangeElement = highlightSearchResult(element, this._rangeToMark.startColumn, this._rangeToMark.endColumn - this._rangeToMark.startColumn);
+ this._markedRangeElement = highlightSearchResult(lineRow, this._rangeToMark.startColumn, this._rangeToMark.endColumn - this._rangeToMark.startColumn);
return;
}
- element.removeChildren();
+ lineRow.removeChildren();
var line = this._textModel.line(lineNumber);
+ if (!line)
+ lineRow.appendChild(document.createElement("br"));
var plainTextStart = -1;
for (var j = 0; j < line.length;) {
@@ -430,98 +733,64 @@ WebInspector.TextViewer.prototype = {
j++;
} else {
if (plainTextStart !== -1) {
- this._appendTextNode(element, line.substring(plainTextStart, j));
+ this._appendTextNode(lineRow, line.substring(plainTextStart, j));
plainTextStart = -1;
}
- this._appendSpan(element, line.substring(j, j + attribute.length), attribute.tokenType);
+ this._appendSpan(lineRow, line.substring(j, j + attribute.length), attribute.tokenType);
j += attribute.length;
}
}
if (plainTextStart !== -1)
- this._appendTextNode(element, line.substring(plainTextStart, line.length));
+ this._appendTextNode(lineRow, line.substring(plainTextStart, line.length));
if (this._rangeToMark && this._rangeToMark.startLine === lineNumber)
- this._markedRangeElement = highlightSearchResult(element, this._rangeToMark.startColumn, this._rangeToMark.endColumn - this._rangeToMark.startColumn);
+ this._markedRangeElement = highlightSearchResult(lineRow, this._rangeToMark.startColumn, this._rangeToMark.endColumn - this._rangeToMark.startColumn);
if (lineRow.decorationsElement)
- element.appendChild(lineRow.decorationsElement);
+ lineRow.appendChild(lineRow.decorationsElement);
},
- _releaseLinesHighlight: function(fromLine, toLine)
+ _releaseLinesHighlight: function(lineRow)
{
- for (var i = fromLine; i < toLine; ++i) {
- var lineRow = this._textModel.getAttribute(i, "line-row");
- if (!lineRow)
- continue;
- var element = lineRow.lastChild;
- if ("spans" in element) {
- var spans = element.spans;
- for (var j = 0; j < spans.length; ++j)
- this._cachedSpans.push(spans[j]);
- delete element.spans;
- }
- if ("textNodes" in element) {
- var textNodes = element.textNodes;
- for (var j = 0; j < textNodes.length; ++j)
- this._cachedTextNodes.push(textNodes[j]);
- delete element.textNodes;
- }
+ if (!lineRow)
+ return;
+ if ("spans" in lineRow) {
+ var spans = lineRow.spans;
+ for (var j = 0; j < spans.length; ++j)
+ this._cachedSpans.push(spans[j]);
+ delete lineRow.spans;
}
+ if ("textNodes" in lineRow) {
+ var textNodes = lineRow.textNodes;
+ for (var j = 0; j < textNodes.length; ++j)
+ this._cachedTextNodes.push(textNodes[j]);
+ delete lineRow.textNodes;
+ }
+ this._cachedRows.push(lineRow);
},
_getSelection: function()
{
var selection = window.getSelection();
- if (selection.isCollapsed)
+ if (!selection.rangeCount)
return null;
var selectionRange = selection.getRangeAt(0);
// Selection may be outside of the viewer.
if (!this.element.isAncestor(selectionRange.startContainer) || !this.element.isAncestor(selectionRange.endContainer))
return null;
var start = this._selectionToPosition(selectionRange.startContainer, selectionRange.startOffset);
- var end = this._selectionToPosition(selectionRange.endContainer, selectionRange.endOffset);
- return new WebInspector.TextRange(start.line, start.column, end.line, end.column);
+ var end = selectionRange.collapsed ? start : this._selectionToPosition(selectionRange.endContainer, selectionRange.endOffset);
+ if (selection.anchorNode === selectionRange.startContainer && selection.anchorOffset === selectionRange.startOffset)
+ return new WebInspector.TextRange(start.line, start.column, end.line, end.column);
+ else
+ return new WebInspector.TextRange(end.line, end.column, start.line, start.column);
},
_restoreSelection: function(range)
{
if (!range)
return;
- var startRow = this._textModel.getAttribute(range.startLine, "line-row");
- if (startRow)
- var start = startRow.lastChild.rangeBoundaryForOffset(range.startColumn);
- else {
- var offset = range.startColumn;
- var chunkNumber = this._chunkNumberForLine(range.startLine);
- for (var i = this._chunkStartLine(chunkNumber); i < range.startLine; ++i)
- offset += this._textModel.line(i).length + 1; // \n
- var lineCell = this._textChunks[chunkNumber].element.lastChild;
- if (lineCell.firstChild)
- var start = { container: lineCell.firstChild, offset: offset };
- else
- var start = { container: lineCell, offset: 0 };
- }
-
- var endRow = this._textModel.getAttribute(range.endLine, "line-row");
- if (endRow)
- var end = endRow.lastChild.rangeBoundaryForOffset(range.endColumn);
- else {
- var offset = range.endColumn;
- var chunkNumber = this._chunkNumberForLine(range.endLine);
- for (var i = this._chunkStartLine(chunkNumber); i < range.endLine; ++i)
- offset += this._textModel.line(i).length + 1; // \n
- var lineCell = this._textChunks[chunkNumber].element.lastChild;
- if (lineCell.firstChild)
- var end = { container: lineCell.firstChild, offset: offset };
- else
- var end = { container: lineCell, offset: 0 };
- }
-
- var selectionRange = document.createRange();
- selectionRange.setStart(start.container, start.offset);
- selectionRange.setEnd(end.container, end.offset);
-
- var selection = window.getSelection();
- selection.removeAllRanges();
- selection.addRange(selectionRange);
+ var start = this._positionToSelection(range.startLine, range.startColumn);
+ var end = range.isEmpty() ? start : this._positionToSelection(range.endLine, range.endColumn);
+ window.getSelection().setBaseAndExtent(start.container, start.offset, end.container, end.offset);
},
_selectionToPosition: function(container, offset)
@@ -531,21 +800,26 @@ WebInspector.TextViewer.prototype = {
if (container === this.element && offset === 1)
return { line: this._textModel.linesCount - 1, column: this._textModel.lineLength(this._textModel.linesCount - 1) };
- var lineRow = container.enclosingNodeOrSelfWithNodeName("tr");
+ var lineRow = container.enclosingNodeOrSelfWithNodeName("DIV");
var lineNumber = lineRow.lineNumber;
- if (container.nodeName === "TD" && offset === 0)
+ if (container === lineRow && offset === 0)
return { line: lineNumber, column: 0 };
- if (container.nodeName === "TD" && offset === 1)
- return { line: lineNumber, column: this._textModel.lineLength(lineNumber) };
+ // This may be chunk and chunks may contain \n.
var column = 0;
- var node = lineRow.lastChild.traverseNextTextNode(lineRow.lastChild);
+ var node = lineRow.traverseNextTextNode(lineRow);
while (node && node !== container) {
- column += node.textContent.length;
- node = node.traverseNextTextNode(lineRow.lastChild);
+ var text = node.textContent;
+ for (var i = 0; i < text.length; ++i) {
+ if (text.charAt(i) === "\n") {
+ lineNumber++;
+ column = 0;
+ } else
+ column++;
+ }
+ node = node.traverseNextTextNode(lineRow);
}
- // This may be chunk and chunks may contain \n.
if (node === container && offset) {
var text = node.textContent;
for (var i = 0; i < offset; ++i) {
@@ -559,6 +833,25 @@ WebInspector.TextViewer.prototype = {
return { line: lineNumber, column: column };
},
+ _positionToSelection: function(line, column)
+ {
+ var chunk = this._chunkForLine(line);
+ var lineRow = chunk.getExpandedLineRow(line);
+ if (lineRow)
+ var rangeBoundary = lineRow.rangeBoundaryForOffset(column);
+ else {
+ var offset = column;
+ for (var i = chunk.startLine; i < line; ++i)
+ offset += this._textModel.lineLength(i) + 1; // \n
+ lineRow = chunk.element;
+ if (lineRow.firstChild)
+ var rangeBoundary = { container: lineRow.firstChild, offset: offset };
+ else
+ var rangeBoundary = { container: lineRow, offset: 0 };
+ }
+ return rangeBoundary;
+ },
+
_appendSpan: function(element, content, className)
{
if (className === "html-resource-link" || className === "html-external-link") {
@@ -578,9 +871,9 @@ WebInspector.TextViewer.prototype = {
_appendTextNode: function(element, text)
{
var textNode = this._cachedTextNodes.pop();
- if (textNode) {
+ if (textNode)
textNode.nodeValue = text;
- } else
+ else
textNode = document.createTextNode(text);
element.appendChild(textNode);
if (!("textNodes" in element))
@@ -614,58 +907,52 @@ WebInspector.TextViewer.prototype = {
return WebInspector.completeURL(this._url, hrefValue);
},
- resize: function()
+ _handleDOMUpdates: function(e)
{
- this._repaintAll();
+ var target = e.target;
+ var lineRow = target.enclosingNodeOrSelfWithClass("webkit-line-content");
+ if (lineRow === target || !lineRow || !lineRow.decorationsElement || !lineRow.decorationsElement.isAncestor(target))
+ return;
+ if (this._syncDecorationsForLineListener) {
+ // Wait until this event is processed and only then sync the sizes. This is necessary in
+ // case of the DOMNodeRemoved event, because it is dispatched before the removal takes place.
+ setTimeout(function() {
+ this._syncDecorationsForLineListener(lineRow.lineNumber);
+ }.bind(this), 0);
+ }
}
}
-var cachedSpans = [];
+WebInspector.TextEditorMainPanel.prototype.__proto__ = WebInspector.TextEditorChunkedPanel.prototype;
-WebInspector.TextChunk = function(textViewer, startLine, endLine)
+WebInspector.TextEditorMainChunk = function(textViewer, startLine, endLine)
{
this._textViewer = textViewer;
- this.element = document.createElement("tr");
this._textModel = textViewer._textModel;
- this.element.chunk = this;
+
+ this.element = document.createElement("div");
this.element.lineNumber = startLine;
+ this.element.className = "webkit-line-content";
this.startLine = startLine;
endLine = Math.min(this._textModel.linesCount, endLine);
this.linesCount = endLine - startLine;
- this._lineNumberElement = document.createElement("td");
- this._lineNumberElement.className = "webkit-line-number";
- this.element.appendChild(this._lineNumberElement);
-
- this._lineContentElement = document.createElement("td");
- this._lineContentElement.className = "webkit-line-content";
- this.element.appendChild(this._lineContentElement);
-
this._expanded = false;
- var lineNumbers = [];
var lines = [];
for (var i = startLine; i < endLine; ++i) {
- lineNumbers.push(i + 1);
lines.push(this._textModel.line(i));
}
- if (this.linesCount === 1) {
- // Single line chunks are typically created for decorations. Host line number in
- // the sub-element in order to allow flexible border / margin management.
- var innerSpan = document.createElement("span");
- innerSpan.className = "webkit-line-number-inner";
- innerSpan.textContent = startLine + 1;
- var outerSpan = document.createElement("div");
- outerSpan.className = "webkit-line-number-outer";
- outerSpan.appendChild(innerSpan);
- this._lineNumberElement.appendChild(outerSpan);
- } else
- this._lineNumberElement.textContent = lineNumbers.join("\n");
- this._lineContentElement.textContent = lines.join("\n");
+
+ this.element.textContent = lines.join("\n");
+
+ // The last empty line will get swallowed otherwise.
+ if (!lines[lines.length - 1])
+ this.element.appendChild(document.createElement("br"));
}
-WebInspector.TextChunk.prototype = {
+WebInspector.TextEditorMainChunk.prototype = {
addDecoration: function(decoration)
{
if (typeof decoration === "string") {
@@ -674,7 +961,8 @@ WebInspector.TextChunk.prototype = {
}
if (!this.element.decorationsElement) {
this.element.decorationsElement = document.createElement("div");
- this._lineContentElement.appendChild(this.element.decorationsElement);
+ this.element.decorationsElement.className = "webkit-line-decorations";
+ this.element.appendChild(this.element.decorationsElement);
}
this.element.decorationsElement.appendChild(decoration);
},
@@ -703,71 +991,63 @@ WebInspector.TextChunk.prototype = {
this._expanded = expanded;
if (this.linesCount === 1) {
- this._textModel.setAttribute(this.startLine, "line-row", this.element);
if (expanded)
- this._textViewer._paintLines(this.startLine, this.startLine + 1);
+ this._textViewer._paintLine(this.element, this.startLine);
return;
}
if (expanded) {
+ this._expandedLineRows = [];
var parentElement = this.element.parentElement;
for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) {
var lineRow = this._createRow(i);
- this._textModel.setAttribute(i, "line-row", lineRow);
parentElement.insertBefore(lineRow, this.element);
+ this._expandedLineRows.push(lineRow);
+ this._textViewer._paintLine(lineRow, i);
}
parentElement.removeChild(this.element);
-
- this._textViewer._paintLines(this.startLine, this.startLine + this.linesCount);
} else {
- var firstLine = this._textModel.getAttribute(this.startLine, "line-row");
- var parentElement = firstLine.parentElement;
- this._textViewer._releaseLinesHighlight(this.startLine, this.startLine + this.linesCount);
-
- parentElement.insertBefore(this.element, firstLine);
- for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) {
- var lineRow = this._textModel.getAttribute(i, "line-row");
- this._textModel.removeAttribute(i, "line-row");
- this._textViewer._cachedRows.push(lineRow);
- parentElement.removeChild(lineRow);
+ var elementInserted = false;
+ for (var i = 0; i < this._expandedLineRows.length; ++i) {
+ var lineRow = this._expandedLineRows[i];
+ var parentElement = lineRow.parentElement;
+ if (parentElement) {
+ if (!elementInserted) {
+ elementInserted = true;
+ parentElement.insertBefore(this.element, lineRow);
+ }
+ this._textViewer._releaseLinesHighlight(lineRow);
+ parentElement.removeChild(lineRow);
+ }
}
+ delete this._expandedLineRows;
}
},
get height()
{
- if (!this._expanded)
- return this.element.offsetHeight;
- var result = 0;
- for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) {
- var lineRow = this._textModel.getAttribute(i, "line-row");
- result += lineRow.offsetHeight;
- }
- return result;
+ if (!this._expandedLineRows)
+ return this._textViewer._totalHeight(this.element);
+ return this._textViewer._totalHeight(this._expandedLineRows[0], this._expandedLineRows[this._expandedLineRows.length - 1]);
},
_createRow: function(lineNumber)
{
- var cachedRows = this._textViewer._cachedRows;
- if (cachedRows.length) {
- var lineRow = cachedRows[cachedRows.length - 1];
- cachedRows.length--;
- var lineNumberElement = lineRow.firstChild;
- var lineContentElement = lineRow.lastChild;
- } else {
- var lineRow = document.createElement("tr");
-
- var lineNumberElement = document.createElement("td");
- lineNumberElement.className = "webkit-line-number";
- lineRow.appendChild(lineNumberElement);
-
- var lineContentElement = document.createElement("td");
- lineContentElement.className = "webkit-line-content";
- lineRow.appendChild(lineContentElement);
- }
+ var lineRow = this._textViewer._cachedRows.pop() || document.createElement("div");
lineRow.lineNumber = lineNumber;
- lineNumberElement.textContent = lineNumber + 1;
- lineContentElement.textContent = this._textModel.line(lineNumber);
+ lineRow.className = "webkit-line-content";
+ lineRow.textContent = this._textModel.line(lineNumber);
+ if (!lineRow.textContent)
+ lineRow.appendChild(document.createElement("br"));
return lineRow;
+ },
+
+ getExpandedLineRow: function(lineNumber)
+ {
+ if (!this._expanded || lineNumber < this.startLine || lineNumber >= this.startLine + this.linesCount)
+ return null;
+ if (!this._expandedLineRows)
+ return this.element;
+ return this._expandedLineRows[lineNumber - this.startLine];
}
}