WebInspector.TextEditor = function(textModel, platform)
{
this._textModel = textModel;
this._textModel.changeListener = this._textChanged.bind(this);
this._highlighter = new WebInspector.TextEditorHighlighter(this._textModel, this._highlightChanged.bind(this));
this.element = document.createElement("div");
this.element.className = "text-editor";
this.element.tabIndex = 0;
this._canvas = document.createElement("canvas");
this._canvas.className = "text-editor-canvas";
this.element.appendChild(this._canvas);
this._container = document.createElement("div");
this._container.className = "text-editor-container";
this.element.appendChild(this._container);
this._sheet = document.createElement("div");
this._sheet.className = "text-editor-sheet";
this._container.appendChild(this._sheet);
var cursorElement = document.createElement("div");
cursorElement.className = "text-editor-cursor";
this._container.appendChild(cursorElement);
this._cursor = new WebInspector.TextCursor(cursorElement);
this._container.addEventListener("scroll", this._scroll.bind(this), false);
this._sheet.addEventListener("mouseup", this._mouseUp.bind(this), false);
this._sheet.addEventListener("mousedown", this._mouseDown.bind(this), false);
this._sheet.addEventListener("mousemove", this._mouseMove.bind(this), false);
this._sheet.addEventListener("mouseout", this._mouseOut.bind(this), false);
this._sheet.addEventListener("dblclick", this._dblClick.bind(this), false);
this._sheet.addEventListener("contextmenu", this._contextMenu.bind(this), false);
this.element.addEventListener("keydown", this._keyDown.bind(this), false);
this.element.addEventListener("textInput", this._textInput.bind(this), false);
this.element.addEventListener("beforecopy", this._beforeCopy.bind(this), false);
this.element.addEventListener("copy", this._copy.bind(this), false);
this.element.addEventListener("beforecut", this._beforeCut.bind(this), false);
this.element.addEventListener("cut", this._cut.bind(this), false);
this.element.addEventListener("beforepaste", this._beforePaste.bind(this), false);
this.element.addEventListener("paste", this._paste.bind(this), false);
this._desiredCaretColumn = 0;
this._scrollLeft = 0;
this._scrollTop = 0;
this._ctx = this._canvas.getContext("2d");
this._selection = new WebInspector.TextSelectionModel(this._selectionChanged.bind(this));
this._isMac = platform && (platform.indexOf("mac") === 0);
this._initFont();
this._paintCoalescingLevel = 0;
this._registerShortcuts();
this._highlightingEnabled = true;
this._debugMode = false;
this._textWidth = 0;
this._longestLineNumber = 0;
this._lineOffsetsCache = [0];
this._readOnly = false;
}
WebInspector.TextEditor.prototype = {
set text(text)
{
var lastLine = this._textModel.linesCount - 1;
this._textModel.setText(null, text);
this._textModel.resetUndoStack();
this._setCaretLocation(0, 0);
},
set mimeType(mimeType)
{
this._highlighter.mimeType = mimeType;
},
get textModel()
{
return this._textModel;
},
set readOnly(readOnly)
{
this._readOnly = readOnly;
if (readOnly)
this.element.addStyleClass("text-editor-readonly")
else
this.element.removeStyleClass("text-editor-readonly")
},
set lineNumberDecorator(lineNumberDecorator)
{
this._lineNumberDecorator = lineNumberDecorator;
},
set lineDecorator(lineDecorator)
{
this._lineDecorator = lineDecorator;
},
get selection()
{
return this._selection.range();
},
setSelection: function(startLine, startColumn, endLine, endColumn)
{
var start = this._fit(startLine, startColumn);
this._selection.setStart(start.line, start.column);
this._setSelectionEnd(endLine, endColumn);
},
setDivDecoration: function(lineNumber, element)
{
var divDecoration = this._textModel.getAttribute(lineNumber, "div-decoration");
if (divDecoration && divDecoration.element && divDecoration.element.parentNode)
divDecoration.element.parentNode.removeChild(divDecoration.element);
this._textModel.removeAttribute(lineNumber, "div-decoration");
if (element) {
divDecoration = { element: element };
this.element.appendChild(element);
this._textModel.setAttribute(lineNumber, "div-decoration", divDecoration);
}
this.packAndRepaintAll();
},
_offsetToLine: function(offset)
{
if (offset > this._lineOffsetsCache[this._lineOffsetsCache.length - 1]) {
var lineNumber = this._lineOffsetsCache.length;
while (this._lineToOffset(lineNumber) < offset)
lineNumber++;
return lineNumber;
}
var from = 0;
var to = this._lineOffsetsCache.length;
while (to > from + 1) {
var mid = Math.floor((from + to) / 2);
if (this._lineOffsetsCache[mid] > offset)
to = mid;
else
from = mid;
}
return to;
},
_lineToOffset: function(lineNumber)
{
var offset = this._lineOffsetsCache[lineNumber];
if (offset)
return offset;
for (var line = lineNumber; line > 0; --line) {
if (this._lineOffsetsCache[line])
break;
}
offset = this._lineOffsetsCache[line];
for (var i = line + 1; i <= lineNumber; ++i) {
offset += this._lineHeight(i - 1);
this._lineOffsetsCache[i] = offset;
}
return offset;
},
_lineHeight: function(lineNumber)
{
var divDecoration = this._textModel.getAttribute(lineNumber, "div-decoration");
if (divDecoration)
return 2 * this._textLineHeight + divDecoration.element.clientHeight;
return this._textLineHeight;
},
reveal: function(line, column) {
var maxScrollTop = this._lineToOffset(line);
var minScrollTop = maxScrollTop + this._lineHeight(line) - this._canvas.height;
if (this._scrollTop > maxScrollTop)
this._container.scrollTop = maxScrollTop - this._textLineHeight * 2;
else if (this._scrollTop < minScrollTop)
this._container.scrollTop = minScrollTop + this._textLineHeight * 2;
var firstColumn = this._columnForOffset(line, this._scrollLeft);
var maxScrollLeft = this._columnToOffset(line, column);
var minScrollLeft = maxScrollLeft - this._container.clientWidth + this._lineNumberWidth;
if (this._scrollLeft < minScrollLeft)
this._container.scrollLeft = minScrollLeft + 100;
if (this._scrollLeft > maxScrollLeft)
this._container.scrollLeft = maxScrollLeft;
},
_textChanged: function(oldRange, newRange, oldText, newText)
{
if (newRange.linesCount == oldRange.linesCount)
this._invalidateLines(newRange.startLine, newRange.endLine + 1);
else
this._invalidateLines(newRange.startLine, this._textModel.linesCount + Math.max(0, oldRange.endLine - newRange.endLine));
if (this._highlightingEnabled) {
var lastVisibleLine = Math.min(this._textModel.linesCount, this._offsetToLine(this._scrollTop + this._canvas.height) + 1);
this._highlighter.updateHighlight(newRange.startLine, lastVisibleLine);
}
this._updateSize(newRange.startLine, Math.max(newRange.endLine, oldRange.endLine));
if (oldRange.linesCount !== newRange.linesCount) {
this._lineOffsetsCache.length = oldRange.startLine + 1;
this._lineToOffset(oldRange.startLine);
this.paintLineNumbers();
}
this._paint();
},
_selectionChanged: function(oldRange, newRange)
{
if (oldRange.isEmpty() && newRange.isEmpty() && oldRange.startLine === newRange.startLine) {
return;
}
this._invalidateLines(oldRange.startLine, oldRange.endLine + 1);
this._invalidateLines(newRange.startLine, newRange.endLine + 1);
this._paint();
},
_highlightChanged: function(fromLine, toLine)
{
if (this._muteHighlightListener)
return;
this._invalidateLines(fromLine, toLine);
this._paint();
},
packAndRepaintAll: function()
{
this.setCoalescingUpdate(true);
this._lineOffsetsCache = [0];
this._updateSize(0, this._textModel.linesCount);
this.repaintAll();
this.setCoalescingUpdate(false);
},
_updateSize: function(startLine, endLine)
{
this.setCoalescingUpdate(true);
var guardedEndLine = Math.min(this._textModel.linesCount, endLine + 1);
var newMaximum = false;
for (var i = startLine; i < guardedEndLine; ++i) {
var lineWidth = this._ctx.measureText(this._textModel.line(i)).width;
if (lineWidth > this._textWidth) {
this._textWidth = lineWidth;
this._longestLineNumber = i;
newMaximum = true;
}
}
if (!newMaximum && startLine <= this._longestLineNumber && this._longestLineNumber <= endLine) {
this._textWidth = 0;
this._longestLineNumber = 0;
for (var i = 0; i < this._textModel.linesCount; ++i) {
var lineWidth = this._ctx.measureText(this._textModel.line(i)).width;
if (lineWidth > this._textWidth) {
this._textWidth = lineWidth;
this._longestLineNumber = i;
}
}
}
var newLineNumberDigits = this._decimalDigits(this._textModel.linesCount);
this._lineNumberWidth = (newLineNumberDigits + 2) * this._digitWidth;
var newWidth = this._textWidth + this._lineNumberWidth + "px";
var newHeight = this._lineToOffset(this._textModel.linesCount) + "px";
this._sheet.style.width = newWidth;
this._sheet.style.height = newHeight;
if (newLineNumberDigits !== this._lineNumberDigits) {
this._lineNumberDigits = newLineNumberDigits;
this.repaintAll();
}
this.updateCanvasSize();
this.setCoalescingUpdate(false);
},
updateCanvasSize: function()
{
if (this._canvas.width !== this._container.clientWidth || this._canvas.height !== this._container.clientHeight) {
this._canvas.width = this._container.clientWidth;
this._canvas.height = this._container.clientHeight;
this.repaintAll();
}
},
repaintAll: function()
{
this._invalidateLines(0, this._textModel.linesCount);
this._paint();
},
_invalidateLines: function(startLine, endLine)
{
if (!this._damage)
this._damage = [ { startLine: startLine, endLine: endLine } ];
else {
for (var i = 0; i < this._damage.length; ++i) {
var chunk = this._damage[i];
if (chunk.startLine <= endLine && chunk.endLine >= startLine) {
chunk.startLine = Math.min(chunk.startLine, startLine);
chunk.endLine = Math.max(chunk.endLine, endLine);
return;
}
}
this._damage.push({ startLine: startLine, endLine: endLine });
}
},
_paint: function()
{
if (this._paintCoalescingLevel)
return;
this.paintLineNumbers();
for (var i = 0; this._damage && i < this._damage.length; ++i)
this._paintLines(this._damage[i].startLine, this._damage[i].endLine);
delete this._damage;
this._updateDivDecorations();
this._updateCursor(this._selection.endLine, this._selection.endColumn);
},
_paintLines: function(firstLine, lastLine)
{
this._ctx.font = this._font;
this._ctx.textBaseline = "bottom";
firstLine = Math.max(firstLine, this._offsetToLine(this._scrollTop) - 1);
lastLine = Math.min(lastLine, this._offsetToLine(this._scrollTop + this._canvas.height) + 1);
if (firstLine > lastLine)
return;
if (this._debugMode) {
WebInspector.log("Repaint %d:%d", firstLine, lastLine);
this._ctx.fillStyle = "rgb(255,255,0)";
var fromOffset = this._lineToOffset(firstLine);
var toOffset = this._lineToOffset(lastLine);
this._ctx.fillRect(this._lineNumberWidth - 1, fromOffset - this._scrollTop, this._canvas.width - this._lineNumberWidth + 1, toOffset - fromOffset);
setTimeout(this._paintLinesContinuation.bind(this, firstLine, lastLine), 100);
} else
this._paintLinesContinuation(firstLine, lastLine);
},
_paintLinesContinuation: function(firstLine, lastLine) {
this._ctx.save();
this._ctx.beginPath();
this._ctx.rect(this._lineNumberWidth - 1, 0, this._canvas.width - this._lineNumberWidth + 1, this._canvas.height);
this._ctx.clip();
var fromOffset = this._lineToOffset(firstLine);
var toOffset = lastLine < this._textModel.linesCount ? this._lineToOffset(lastLine) : this._canvas.height + this._scrollTop;
if (this._readOnly || firstLine + 1 != lastLine || this._selection.endLine != firstLine) {
this._ctx.fillStyle = "rgb(255,255,255)";
this._ctx.fillRect(0, fromOffset - this._scrollTop, this._canvas.width, toOffset - fromOffset);
}
lastLine = Math.min(lastLine, this._textModel.linesCount);
if (!this._readOnly && this._selection.startLine === this._selection.endLine && firstLine <= this._selection.startLine && this._selection.startLine < lastLine)
this._paintCurrentLine(this._selection.startLine);
this._paintSelection(firstLine, lastLine);
if (this._highlightingEnabled) {
this._muteHighlightListener = true;
this._highlighter.highlight(lastLine);
delete this._muteHighlightListener;
}
for (var i = firstLine; i < lastLine; ++i) {
var line = this._textModel.line(i);
var lineOffset = this._lineToOffset(i) - this._scrollTop;
if (this._lineDecorator)
this._lineDecorator.decorate(i, this._ctx, this._lineNumberWidth - 1, lineOffset, this._canvas.width - this._lineNumberWidth + 1, this._lineHeight(i), this._textLineHeight);
var divDecoration = this._textModel.getAttribute(i, "div-decoration");
if (divDecoration)
this._positionDivDecoration(i, divDecoration, true);
if (!this._highlightingEnabled) {
this._ctx.fillStyle = "rgb(0,0,0)";
this._ctx.fillText(line, this._lineNumberWidth - this._scrollLeft, lineOffset + this._textLineHeight);
continue;
}
if (line.length > 1000) {
var firstColumn = this._columnForOffset(i, this._scrollLeft);
var lastColumn = this._columnForOffset(i, this._scrollLeft + this._canvas.width);
}
var highlighterState = this._textModel.getAttribute(i, "highlighter-state");
var plainTextStart = -1;
for (var j = 0; j < line.length;) {
var attribute = highlighterState && highlighterState.attributes[j];
if (attribute && firstColumn && j + attribute.length < firstColumn) {
j += attribute.length;
continue;
}
if (attribute && lastColumn && j > lastColumn)
break;
if (!attribute || !attribute.style) {
if (plainTextStart === -1)
plainTextStart = j;
j++;
} else {
if (plainTextStart !== -1) {
this._ctx.fillStyle = "rgb(0,0,0)";
this._ctx.fillText(line.substring(plainTextStart, j), this._lineNumberWidth - this._scrollLeft + this._columnToOffset(i, plainTextStart), lineOffset + this._textLineHeight);
plainTextStart = -1;
}
this._ctx.fillStyle = attribute.style;
this._ctx.fillText(line.substring(j, j + attribute.length), this._lineNumberWidth - this._scrollLeft + this._columnToOffset(i, j), lineOffset + this._textLineHeight);
j += attribute.length;
}
}
if (plainTextStart !== -1) {
this._ctx.fillStyle = "rgb(0,0,0)";
this._ctx.fillText(line.substring(plainTextStart, j), this._lineNumberWidth - this._scrollLeft + this._columnToOffset(i, plainTextStart), lineOffset + this._textLineHeight);
}
}
this._ctx.restore();
},
paintLineNumbers: function()
{
this._ctx.font = this._font;
this._ctx.textBaseline = "bottom";
this._ctx.fillStyle = "rgb(255,255,255)";
this._ctx.fillRect(0, 0, this._lineNumberWidth - 2, this._canvas.height);
this._ctx.fillStyle = "rgb(235,235,235)";
this._ctx.fillRect(this._lineNumberWidth - 2, 0, 1, this._canvas.height);
var firstLine = Math.max(0, this._offsetToLine(this._scrollTop) - 1);
var lastLine = Math.min(this._textModel.linesCount, this._offsetToLine(this._scrollTop + this._canvas.height) + 1);
for (var i = firstLine; i < lastLine; ++i) {
var lineOffset = this._lineToOffset(i) - this._scrollTop;
this._ctx.fillStyle = "rgb(155,155,155)";
if (this._lineNumberDecorator && this._lineNumberDecorator.decorate(i, this._ctx, 0, lineOffset, this._lineNumberWidth, this._lineHeight(i), this._textLineHeight))
continue;
this._ctx.fillText(i + 1, (this._lineNumberDigits - this._decimalDigits(i + 1) + 1) * this._digitWidth, lineOffset + this._textLineHeight);
}
},
_paintCurrentLine: function(line)
{
this._ctx.fillStyle = "rgb(232, 242, 254)";
this._ctx.fillRect(0, this._lineToOffset(line) - this._scrollTop, this._canvas.width, this._lineHeight(line));
},
_scroll: function(e)
{
this._cursor._cursorElement.style.display = "none";
setTimeout(this._repaintOnScroll.bind(this), 10);
},
_repaintOnScroll: function()
{
if (this._scrollTop !== this._container.scrollTop || this._scrollLeft !== this._container.scrollLeft) {
this._scrollTop = this._container.scrollTop;
this._scrollLeft = this._container.scrollLeft;
this.repaintAll();
}
},
_mouseUp: function(e)
{
this._isDragging = false;
},
_mouseDown: function(e)
{
if (e.button === 2 || (this._isMac && e.ctrlKey))
return;
var location = this._caretForMouseEvent(e);
if (e.offsetX < this._lineNumberWidth && this._lineNumberDecorator) {
if (this._lineNumberDecorator.mouseDown(location.line, e))
return;
}
if (e.shiftKey)
this._setSelectionEnd(location.line, location.column);
else
this._setCaretLocation(location.line, location.column);
this._isDragging = true;
this._textModel.markUndoableState();
},
_mouseMove: function(e)
{
if (!this._isDragging)
return;
var location = this._caretForMouseEvent(e);
this._setSelectionEnd(location.line, location.column)
},
_mouseOut: function(e)
{
},
_dblClick: function(e)
{
var location = this._caretForMouseEvent(e);
var range = this._textModel.wordRange(location.line, location.column);
this.setSelection(range.startLine, range.startColumn, range.endLine, range.endColumn);
},
_contextMenu: function(e)
{
if (e.offsetX < this._lineNumberWidth && this._lineNumberDecorator) {
var location = this._caretForMouseEvent(e);
var line = location.line;
if (this._lineNumberDecorator.contextMenu(location.line, e))
return;
} else {
var range = this._selection.range();
if (!range.isEmpty()) {
var text = this._textModel.copyRange(range);
var contextMenu = new WebInspector.ContextMenu();
contextMenu.appendItem(WebInspector.UIString("Copy"), this._copy.bind(this));
contextMenu.show(event);
}
}
},
_caretForMouseEvent: function(e)
{
var lineNumber = Math.max(0, this._offsetToLine(e.offsetY) - 1);
var line = this._textModel.line(lineNumber);
var offset = e.offsetX + this._scrollLeft - this._lineNumberWidth;
return { line: lineNumber, column: this._columnForOffset(lineNumber, offset) };
},
_columnForOffset: function(lineNumber, offset)
{
var length = 0;
var line = this._textModel.line(lineNumber);
var charWidth = this._ctx.measureText("a").width;
var index = Math.floor(offset / charWidth);
var indexOffset = this._ctx.measureText(line.substring(0, index)).width;
if (offset >= indexOffset && index < line.length && offset < indexOffset + this._ctx.measureText(line.charAt(index)).width)
return index;
var delta = indexOffset < offset ? 1 : -1;
while (index >=0 && index < line.length) {
index += delta;
indexOffset += delta * this._ctx.measureText(line.charAt(index)).width;
if (offset >= indexOffset && offset < indexOffset + charWidth)
return index;
}
return line.length;
},
_columnToOffset: function(lineNumber, column)
{
var line = this._textModel.line(lineNumber);
return this._ctx.measureText(line.substring(0, column)).width;
},
_keyDown: function(e)
{
var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEvent(e);
var handler = this._shortcuts[shortcutKey];
if (handler) {
handler.call(this);
e.preventDefault();
e.stopPropagation();
return;
}
if (this._handleNavigationKey(e)) {
e.preventDefault();
e.stopPropagation();
return;
}
if (this._readOnly)
return;
var keyCodes = WebInspector.KeyboardShortcut.KeyCodes;
switch (e.keyCode) {
case keyCodes.Backspace:
this._handleBackspaceKey();
break;
case keyCodes.Delete:
this._handleDeleteKey();
break;
case keyCodes.Tab:
this._replaceSelectionWith("\t");
break;
case keyCodes.Enter:
this._replaceSelectionWith("\n");
break;
default:
return;
}
e.preventDefault();
e.stopPropagation();
},
_handleNavigationKey: function(e)
{
var caretLine = this._selection.endLine;
var caretColumn = this._selection.endColumn;
var arrowAction = e.shiftKey ? this._setSelectionEnd : this._setCaretLocation;
var keyCodes = WebInspector.KeyboardShortcut.KeyCodes;
switch (e.keyCode) {
case keyCodes.Up:
case keyCodes.PageUp:
if (e.metaKey)
arrowAction.call(this, 0, 0, true);
else if (e.ctrlKey)
this._container.scrollTop -= this._lineHeight(caretLine);
else {
if (e.keyCode === keyCodes.Up)
arrowAction.call(this, caretLine - 1, this._desiredCaretColumn, true);
else {
var offset = Math.max(0, this._lineToOffset(caretLine) - this._canvas.height);
arrowAction.call(this, this._offsetToLine(offset), this._desiredCaretColumn, true);
}
}
break;
case keyCodes.Down:
case keyCodes.PageDown:
if (e.metaKey)
arrowAction.call(this, this._textModel.linesCount - 1, this._textModel.lineLength(this._textModel.linesCount - 1), true);
else if (e.ctrlKey)
this._container.scrollTop += this._lineHeight(caretLine);
else {
if (e.keyCode === keyCodes.Down)
arrowAction.call(this, caretLine + 1, this._desiredCaretColumn, true);
else {
var offset = this._lineToOffset(caretLine) + this._canvas.height;
arrowAction.call(this, this._offsetToLine(offset), this._desiredCaretColumn, true);
}
}
break;
case keyCodes.Home:
if (this._isMetaCtrl(e))
arrowAction.call(this, 0, 0, true);
else
arrowAction.call(this, this._selection.endLine, 0);
break;
case keyCodes.End:
if (this._isMetaCtrl(e))
arrowAction.call(this, this._textModel.linesCount - 1, this._textModel.lineLength(this._textModel.linesCount - 1), true);
else
arrowAction.call(this, this._selection.endLine, this._textModel.lineLength(this._selection.endLine));
break;
case keyCodes.Left:
if (!e.shiftKey && !e.metaKey && !this._isAltCtrl(e) && !this._selection.isEmpty()) {
var range = this._selection.range();
this._setCaretLocation(range.startLine, range.startColumn);
} else if (e.metaKey)
arrowAction.call(this, this._selection.endLine, 0);
else if (caretColumn === 0 && caretLine > 0)
arrowAction.call(this, caretLine - 1, this._textModel.lineLength(caretLine - 1));
else if (this._isAltCtrl(e)) {
caretColumn = this._textModel.wordStart(this._selection.endLine, this._selection.endColumn);
if (caretColumn === this._selection.endColumn)
caretColumn = 0;
arrowAction.call(this, caretLine, caretColumn);
} else
arrowAction.call(this, caretLine, caretColumn - 1);
break;
case keyCodes.Right:
var line = this._textModel.line(caretLine);
if (!e.shiftKey && !e.metaKey && !this._isAltCtrl(e) && !this._selection.isEmpty()) {
var range = this._selection.range();
this._setCaretLocation(range.endLine, range.endColumn);
} else if (e.metaKey)
arrowAction.call(this, this._selection.endLine, this._textModel.lineLength(this._selection.endLine));
else if (caretColumn === line.length && caretLine < this._textModel.linesCount - 1)
arrowAction.call(this, caretLine + 1, 0);
else if (this._isAltCtrl(e)) {
caretColumn = this._textModel.wordEnd(this._selection.endLine, this._selection.endColumn);
if (caretColumn === this._selection.endColumn)
caretColumn = line.length;
arrowAction.call(this, caretLine, caretColumn);
} else
arrowAction.call(this, caretLine, caretColumn + 1);
break;
default:
return false;
}
this._textModel.markUndoableState();
return true;
},
_textInput: function(e)
{
if (this._readOnly)
return;
if (e.data && !e.altKey && !e.ctrlKey && !e.metaKey) {
this._replaceSelectionWith(e.data);
e.preventDefault();
e.stopPropagation();
}
},
_setCaretLocation: function(line, column, updown)
{
this.setSelection(line, column, line, column, updown);
},
_setSelectionEnd: function(line, column, updown)
{
if (!updown)
this._desiredCaretColumn = column;
var end = this._fit(line, column);
this._selection.setEnd(end.line, end.column);
this.reveal(this._selection.endLine, this._selection.endColumn);
this._updateCursor(end.line, end.column);
},
_updateDivDecorations: function()
{
var firstLine = this._offsetToLine(this._scrollTop) - 1;
var lastLine = this._offsetToLine(this._scrollTop + this._canvas.height) + 1;
var linesCount = this._textModel.linesCount;
for (var i = 0; i < linesCount; ++i) {
var divDecoration = this._textModel.getAttribute(i, "div-decoration");
if (divDecoration)
this._positionDivDecoration(i, divDecoration, i > firstLine && i < lastLine);
}
},
_positionDivDecoration: function(lineNumber, divDecoration, visible)
{
divDecoration.element.style.position = "absolute";
divDecoration.element.style.top = this._lineToOffset(lineNumber) - this._scrollTop + this._textLineHeight + "px";
divDecoration.element.style.left = this._lineNumberWidth + "px";
divDecoration.element.style.setProperty("max-width", this._canvas.width + "px");
},
_updateCursor: function(line, column)
{
if (line >= this._textModel.linesCount)
return;
var offset = this._columnToOffset(line, column);
if (offset >= this._container.scrollLeft && !this._readOnly)
this._cursor.setLocation(this._lineNumberWidth + offset - 1, this._lineToOffset(line));
else
this._cursor.hide();
},
_fit: function(line, column)
{
line = Math.max(0, Math.min(line, this._textModel.linesCount - 1));
var lineLength = this._textModel.lineLength(line);
column = Math.max(0, Math.min(column, lineLength));
return { line: line, column: column };
},
_paintSelection: function(firstLine, lastLine)
{
if (this._selection.isEmpty())
return;
var range = this._selection.range();
this._ctx.fillStyle = "rgb(181, 213, 255)";
firstLine = Math.max(firstLine, range.startLine);
endLine = Math.min(lastLine, range.endLine + 1);
for (var i = firstLine; i < endLine; ++i) {
var line = this._textModel.line(i);
var from, to;
if (i === range.startLine) {
var offset = this._columnToOffset(range.startLine, range.startColumn);
from = offset - this._scrollLeft + this._lineNumberWidth - 1;
} else
from = 0;
if (i === range.endLine) {
var offset = this._columnToOffset(range.endLine, range.endColumn);
to = offset - this._scrollLeft + this._lineNumberWidth - 1;
} else
to = this._canvas.width;
this._ctx.fillRect(from, this._lineToOffset(i) - this._scrollTop, to - from, this._lineHeight(i));
}
this._ctx.fillStyle = "rgb(0, 0, 0)";
},
_beforeCopy: function(e)
{
if (!this._selection.isEmpty())
e.preventDefault();
},
_copy: function(e)
{
var range = this._selection.range();
var text = this._textModel.copyRange(range);
function delayCopy()
{
InspectorFrontendHost.copyText(text);
}
setTimeout(delayCopy);
if (e)
e.preventDefault();
},
_beforeCut: function(e)
{
if (!this._selection.isEmpty())
e.preventDefault();
},
_cut: function(e)
{
if (this._readOnly) {
e.preventDefault();
return;
}
this._textModel.markUndoableState();
this._copy(e);
this._replaceSelectionWith("");
},
_beforePaste: function(e)
{
e.preventDefault();
},
_paste: function(e)
{
if (this._readOnly) {
e.preventDefault();
return;
}
var text = e.clipboardData.getData("Text");
if (!text)
return;
this._textModel.markUndoableState();
this._replaceSelectionWith(text);
e.preventDefault();
},
_replaceSelectionWith: function(newText, overrideRange)
{
var range = overrideRange || this._selection.range();
this.setCoalescingUpdate(true);
var newRange = this._textModel.setText(range, newText);
this._setCaretLocation(newRange.endLine, newRange.endColumn);
this.setCoalescingUpdate(false);
},
setCoalescingUpdate: function(enabled)
{
if (enabled)
this._paintCoalescingLevel++;
else
this._paintCoalescingLevel--;
if (!this._paintCoalescingLevel)
this._paint();
},
_selectAll: function()
{
this._selection.setStart(0, 0);
var lastLineNum = this._textModel.linesCount - 1;
this._selection.setEnd(lastLineNum, this._textModel.lineLength(lastLineNum));
this._updateCursor(this._selection.endLine, this._selection.endColumn);
},
_initFont: function(sansSerif)
{
if (!WebInspector.TextEditor.PlatformFonts) {
WebInspector.TextEditor.PlatformFonts = {};
WebInspector.TextEditor.PlatformFonts[WebInspector.OS.Windows] = {size: 12, face: "Lucida Console"};
WebInspector.TextEditor.PlatformFonts[WebInspector.OS.WindowsVistaOrLater] = {size: 12, face: "Courier"};
WebInspector.TextEditor.PlatformFonts[WebInspector.OS.MacSnowLeopard] = {size: 11, face: "Menlo"};
WebInspector.TextEditor.PlatformFonts[WebInspector.OS.MacLeopard] = {size: 10, face: "Monaco"};
WebInspector.TextEditor.PlatformFonts[WebInspector.OS.MacTiger] = {size: 10, face: "Monaco"};
}
if (sansSerif) {
this._isMonospace = false;
this._fontSize = 11;
this._font = this._fontSize + "px sans-serif";
} else {
this._isMonospace = true;
const platform = WebInspector.platform;
const fontInfo = WebInspector.TextEditor.PlatformFonts[platform] || {size: 10, face: "monospace"};
this._fontSize = fontInfo.size;
this._font = this._fontSize + "px " + fontInfo.face;
}
this._ctx.font = this._font;
this._digitWidth = this._ctx.measureText("0").width;
this._textLineHeight = Math.floor(this._fontSize * 1.4);
this._cursor.setTextLineHeight(this._textLineHeight);
this._lineOffsetsCache = [0];
},
_registerShortcuts: function()
{
var modifiers = WebInspector.KeyboardShortcut.Modifiers;
this._shortcuts = {};
this._shortcuts[WebInspector.KeyboardShortcut.makeKey("z", this._isMac ? modifiers.Meta : modifiers.Ctrl)] = this._handleUndo.bind(this);
this._shortcuts[WebInspector.KeyboardShortcut.makeKey("z", modifiers.Shift | (this._isMac ? modifiers.Meta : modifiers.Ctrl))] = this._handleRedo.bind(this);
this._shortcuts[WebInspector.KeyboardShortcut.makeKey("a", this._isMac ? modifiers.Meta : modifiers.Ctrl)] = this._selectAll.bind(this);
this._shortcuts[WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Plus, this._isMac ? modifiers.Meta : modifiers.Ctrl)] = this._handleZoomIn.bind(this);
this._shortcuts[WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Minus, this._isMac ? modifiers.Meta : modifiers.Ctrl)] = this._handleZoomOut.bind(this);
this._shortcuts[WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Zero, this._isMac ? modifiers.Meta : modifiers.Ctrl)] = this._handleZoomReset.bind(this);
if (this._isMac)
this._shortcuts[WebInspector.KeyboardShortcut.makeKey("d", modifiers.Ctrl)] = this._handleDeleteKey.bind(this);
this._shortcuts[WebInspector.KeyboardShortcut.makeKey("d", modifiers.Ctrl | modifiers.Alt)] = this._handleToggleDebugMode.bind(this);
this._shortcuts[WebInspector.KeyboardShortcut.makeKey("h", modifiers.Ctrl | modifiers.Alt)] = this._handleToggleHighlightMode.bind(this);
this._shortcuts[WebInspector.KeyboardShortcut.makeKey("m", modifiers.Ctrl | modifiers.Alt)] = this._handleToggleMonospaceMode.bind(this);
},
_handleUndo: function()
{
this.setCoalescingUpdate(true);
var range = this._textModel.undo();
if (range)
this._setCaretLocation(range.endLine, range.endColumn);
this.setCoalescingUpdate(false);
},
_handleRedo: function()
{
this.setCoalescingUpdate(true);
var range = this._textModel.redo();
if (range)
this._setCaretLocation(range.endLine, range.endColumn);
this.setCoalescingUpdate(false);
},
_handleDeleteKey: function()
{
var range = this._selection.range();
if (range.isEmpty()) {
if (range.endColumn < this._textModel.lineLength(range.startLine))
range.endColumn++;
else if (range.endLine < this._textModel.linesCount) {
range.endLine++;
range.endColumn = 0;
} else
return;
} else
this._textModel.markUndoableState();
this._replaceSelectionWith("", range);
},
_handleBackspaceKey: function()
{
var range = this._selection.range();
if (range.isEmpty()) {
if (range.startColumn > 0)
range.startColumn--;
else if (range.startLine > 0) {
range.startLine--;
range.startColumn = this._textModel.lineLength(range.startLine);
} else
return;
} else
this._textModel.markUndoableState();
this._replaceSelectionWith("", range);
},
_handleToggleDebugMode: function()
{
this._debugMode = !this._debugMode;
},
_handleZoomIn: function()
{
if (this._fontSize < 25)
this._changeFont(!this._isMonospace, this._fontSize + 1);
},
_handleZoomOut: function()
{
if (this._fontSize > 1)
this._changeFont(!this._isMonospace, this._fontSize - 1);
},
_handleZoomReset: function()
{
this._changeFont(!this._isMonospace);
},
_handleToggleMonospaceMode: function()
{
this._changeFont(this._isMonospace, this._fontSize);
},
_changeFont: function(sansSerif, fontSize) {
this._initFont(sansSerif, fontSize);
this._updateSize(0, this._textModel.linesCount);
this.repaintAll();
},
_handleToggleHighlightMode: function()
{
this._highlightingEnabled = !this._highlightingEnabled;
},
_isMetaCtrl: function(e)
{
return this._isMac ? e.metaKey : e.ctrlKey;
},
_isAltCtrl: function(e)
{
return this._isMac ? e.altKey : e.ctrlKey;
},
_decimalDigits: function(number)
{
return Math.ceil(Math.log(number + 1) / Math.log(10));
}
}
WebInspector.TextSelectionModel = function(changeListener)
{
this.startLine = 0;
this.startColumn = 0;
this.endLine = 0;
this.endColumn = 0;
this._changeListener = changeListener;
}
WebInspector.TextSelectionModel.prototype = {
setStart: function(line, column)
{
var oldRange = this.range();
this.startLine = line;
this.startColumn = column;
this.endLine = line;
this.endColumn = column;
this._changeListener(oldRange, this.range());
},
setEnd: function(line, column)
{
var oldRange = this.range();
this.endLine = line;
this.endColumn = column;
this._changeListener(oldRange, this.range(), this.endLine, this.endColumn);
},
range: function()
{
if (this.startLine < this.endLine || (this.startLine === this.endLine && this.startColumn <= this.endColumn))
return new WebInspector.TextRange(this.startLine, this.startColumn, this.endLine, this.endColumn);
else
return new WebInspector.TextRange(this.endLine, this.endColumn, this.startLine, this.startColumn);
},
isEmpty: function()
{
return this.startLine === this.endLine && this.startColumn === this.endColumn;
}
}
WebInspector.TextCursor = function(cursorElement)
{
this._visible = false;
this._cursorElement = cursorElement;
}
WebInspector.TextCursor.prototype = {
setLocation: function(x, y)
{
this._x = x;
this._y = y;
if (this._paintInterval) {
window.clearInterval(this._paintInterval);
delete this._paintInterval;
}
this._paintInterval = window.setInterval(this._paint.bind(this, false), 500);
this._paint(true);
},
hide: function()
{
if (this._paintInterval) {
window.clearInterval(this._paintInterval);
delete this._paintInterval;
}
this._cursorElement.style.display = "none";
},
setTextLineHeight: function(textLineHeight)
{
this._cursorElement.style.height = textLineHeight + "px";
},
_paint: function(force)
{
if (force)
this._visible = true;
else
this._visible = !this._visible;
this._cursorElement.style.left = this._x + "px";
this._cursorElement.style.top = this._y + "px";
this._cursorElement.style.display = this._visible ? "block" : "none";
}
}