577 lines
No EOL
13 KiB
JavaScript
577 lines
No EOL
13 KiB
JavaScript
var keys = {
|
|
UP: 38,
|
|
RIGHT: 39,
|
|
DOWN: 40,
|
|
LEFT: 37,
|
|
POS1: 36,
|
|
END: 35,
|
|
DELETE: 46,
|
|
BACKSPACE: 8,
|
|
TAB: 9,
|
|
A: 65
|
|
}
|
|
|
|
function KeyBinding(element, host)
|
|
{
|
|
lib.addListener(element, "keydown", function(e)
|
|
{
|
|
var key = e.keyCode;
|
|
|
|
switch (key)
|
|
{
|
|
case keys.A:
|
|
if (e.metaKey)
|
|
{
|
|
host.selectAll();
|
|
return lib.stopEvent(e);
|
|
}
|
|
break;
|
|
|
|
case keys.UP:
|
|
if (e.shiftKey) {
|
|
host.selectUp();
|
|
} else {
|
|
host.navigateUp();
|
|
}
|
|
return lib.stopEvent(e);
|
|
|
|
case keys.DOWN:
|
|
if (e.shiftKey) {
|
|
host.selectDown();
|
|
} else {
|
|
host.navigateDown();
|
|
}
|
|
return lib.stopEvent(e);
|
|
|
|
case keys.LEFT:
|
|
if (e.metaKey && e.shiftKey) {
|
|
host.selectLineStart();
|
|
} else if (e.metaKey) {
|
|
host.navigateLineStart();
|
|
} else if (e.shiftKey) {
|
|
host.selectLeft();
|
|
} else {
|
|
host.navigateLeft();
|
|
}
|
|
return lib.stopEvent(e);
|
|
|
|
case keys.RIGHT:
|
|
if (e.metaKey && e.shiftKey) {
|
|
host.selectLineEnd();
|
|
} else if (e.metaKey) {
|
|
host.navigateLineEnd();
|
|
} else if (e.shiftKey) {
|
|
host.selectRight();
|
|
} else {
|
|
host.navigateRight();
|
|
}
|
|
return lib.stopEvent(e);
|
|
|
|
case keys.POS1:
|
|
if (e.shiftKey) {
|
|
host.selectLineStart();
|
|
} else {
|
|
host.navigateLineStart();
|
|
}
|
|
return lib.stopEvent(e);
|
|
|
|
case keys.END:
|
|
if (e.shiftKey) {
|
|
host.selectLineEnd();
|
|
} else {
|
|
host.navigateLineEnd();
|
|
}
|
|
return lib.stopEvent(e);
|
|
|
|
case keys.DELETE:
|
|
host.removeRight();
|
|
return lib.stopEvent(e);
|
|
|
|
case keys.BACKSPACE:
|
|
host.removeLeft();
|
|
return lib.stopEvent(e);
|
|
|
|
case keys.TAB:
|
|
host.onTextInput(" ");
|
|
return lib.stopEvent(e);
|
|
}
|
|
});
|
|
};
|
|
|
|
function Editor(doc, renderer)
|
|
{
|
|
var container = renderer.getContainerElement();
|
|
this.renderer = renderer;
|
|
|
|
this.textInput = new TextInput(container, this);
|
|
new KeyBinding(container, this);
|
|
|
|
lib.addListener(container, "mousedown", lib.bind(this.onMouseDown, this));
|
|
lib.addListener(container, "dblclick", lib.bind(this.onMouseDoubleClick, this));
|
|
lib.addMouseWheelListener(container, lib.bind(this.onMouseWheel, this));
|
|
lib.addTripleClickListener(container, lib.bind(this.selectCurrentLine, this));
|
|
|
|
this.doc = doc;
|
|
doc.addChangeListener(lib.bind(this.onDocumentChange, this));
|
|
renderer.setDocument(doc);
|
|
|
|
this.tokenizer = new BackgroundTokenizer(lib.bind(this.onTokenizerUpdate, this));
|
|
this.tokenizer.setLines(doc.lines);
|
|
renderer.setTokenizer(this.tokenizer);
|
|
|
|
this.cursor = {
|
|
row: 0,
|
|
column: 0
|
|
};
|
|
|
|
this.selectionAnchor = null;
|
|
this.selectionLead = null;
|
|
this.selection = null;
|
|
|
|
this.renderer.draw();
|
|
}
|
|
|
|
Editor.prototype =
|
|
{
|
|
resize : function() {
|
|
this.renderer.draw();
|
|
},
|
|
|
|
updateCursor : function() {
|
|
this.renderer.updateCursor(this.cursor);
|
|
},
|
|
|
|
onFocus : function() {
|
|
this.renderer.showCursor();
|
|
this.renderer.visualizeFocus();
|
|
},
|
|
|
|
onBlur : function() {
|
|
this.renderer.hideCursor();
|
|
this.renderer.visualizeBlur();
|
|
},
|
|
|
|
onDocumentChange : function(startRow, endRow)
|
|
{
|
|
this.tokenizer.start(startRow);
|
|
this.renderer.updateLines(startRow, endRow);
|
|
},
|
|
|
|
onTokenizerUpdate : function(startRow, endRow) {
|
|
console.log("token update", startRow, endRow);
|
|
this.renderer.updateLines(startRow, endRow);
|
|
},
|
|
|
|
onMouseDown : function(e)
|
|
{
|
|
this.textInput.focus();
|
|
|
|
var pos = this.renderer.screenToTextCoordinates(e.pageX, e.pageY);
|
|
this.moveCursorTo(pos.row, pos.column);
|
|
this.setSelectionAnchor(pos.row, pos.column);
|
|
this.renderer.scrollCursorIntoView();
|
|
|
|
var _self = this;
|
|
var mousePageX, mousePageY;
|
|
|
|
var onMouseSelection = function(e) {
|
|
mousePageX = e.pageX;
|
|
mousePageY = e.pageY;
|
|
};
|
|
|
|
var onMouseSelectionEnd = function() {
|
|
clearInterval(timerId);
|
|
};
|
|
|
|
var onSelectionInterval = function()
|
|
{
|
|
if (mousePageX === undefined || mousePageY === undefined) return;
|
|
|
|
selectionLead = _self.renderer.screenToTextCoordinates(mousePageX, mousePageY);
|
|
|
|
_self._moveSelection(function() {
|
|
_self.moveCursorTo(selectionLead.row, selectionLead.column);
|
|
});
|
|
_self.renderer.scrollCursorIntoView();
|
|
};
|
|
|
|
lib.capture(this.container, onMouseSelection, onMouseSelectionEnd);
|
|
var timerId = setInterval(onSelectionInterval, 20 );
|
|
|
|
return lib.preventDefault(e);
|
|
},
|
|
|
|
onMouseDoubleClick : function(e)
|
|
{
|
|
var line = this.doc.getLine(this.cursor.row);
|
|
var column = this.cursor.column;
|
|
|
|
var tokenRe = /[a-zA-Z0-9_]+/g;
|
|
var nonTokenRe = /[^a-zA-Z0-9_]+/g;
|
|
|
|
var inToken = false;
|
|
if (column > 0) {
|
|
inToken = !!line.charAt(column-1).match(tokenRe);
|
|
}
|
|
|
|
if (!inToken) {
|
|
inToken = !!line.charAt(column).match(tokenRe);
|
|
}
|
|
|
|
var re = inToken ? tokenRe : nonTokenRe;
|
|
|
|
var start = column;
|
|
if (start > 0)
|
|
{
|
|
do {
|
|
start--;
|
|
} while (start >= 0 && line.charAt(start).match(re))
|
|
start++;
|
|
}
|
|
|
|
var end = column;
|
|
while (end < line.length && line.charAt(end).match(re)) {
|
|
end++;
|
|
}
|
|
|
|
this.setSelectionAnchor(this.cursor.row, start);
|
|
this._moveSelection(function() {
|
|
this.moveCursorTo(this.cursor.row, end);
|
|
});
|
|
},
|
|
|
|
onMouseWheel : function(e)
|
|
{
|
|
var delta = e.wheel;
|
|
this.renderer.scrollToY(this.renderer.getScrollTop() - (delta * 15));
|
|
return lib.preventDefault(e);
|
|
},
|
|
|
|
getCopyText : function()
|
|
{
|
|
if (this.hasSelection()) {
|
|
return this.doc.getTextRange(this.getSelectionRange());
|
|
} else {
|
|
return "";
|
|
}
|
|
},
|
|
|
|
onCut : function()
|
|
{
|
|
if (this.hasSelection())
|
|
{
|
|
this.cursor = this.doc.remove(this.getSelectionRange());
|
|
this.clearSelection();
|
|
this.renderer.updateCursor(this.cursor);
|
|
}
|
|
},
|
|
|
|
onTextInput: function(text)
|
|
{
|
|
if (this.hasSelection())
|
|
{
|
|
this.cursor = this.doc.replace(this.getSelectionRange(), text);
|
|
this.clearSelection();
|
|
} else {
|
|
this.cursor = this.doc.insert(this.cursor, text);
|
|
}
|
|
this.renderer.updateCursor(this.cursor);
|
|
this.renderer.scrollCursorIntoView();
|
|
},
|
|
|
|
removeRight : function()
|
|
{
|
|
if (this.hasSelection())
|
|
{
|
|
this.cursor = this.doc.remove(this.getSelectionRange());
|
|
this.renderer.updateCursor(this.cursor);
|
|
this.clearSelection();
|
|
}
|
|
else
|
|
{
|
|
var rangeEnd = {
|
|
row: this.cursor.row,
|
|
column: this.cursor.column + 1
|
|
}
|
|
if (rangeEnd.column > this.doc.getLine(this.cursor.row).length) {
|
|
rangeEnd.row += 1;
|
|
rangeEnd.column = 0;
|
|
}
|
|
this.doc.remove({start: this.cursor, end: renageEnd});
|
|
}
|
|
|
|
this.renderer.scrollCursorIntoView();
|
|
},
|
|
|
|
removeLeft : function()
|
|
{
|
|
if (this.hasSelection())
|
|
{
|
|
this.cursor = this.doc.remove(this.getSelectionRange());
|
|
this.clearSelection();
|
|
}
|
|
else
|
|
{
|
|
if (this.cursor.row == 0 && this.cursor.column == 0) {
|
|
return;
|
|
}
|
|
|
|
var rangeStart = {
|
|
row: this.cursor.row,
|
|
column: this.cursor.column + -1
|
|
}
|
|
if (rangeStart.column < 0)
|
|
{
|
|
rangeStart.row -= 1;
|
|
rangeStart.column = this.doc.getLine(this.cursor.row-1).length;
|
|
}
|
|
this.cursor = this.doc.remove({start: rangeStart, end: this.cursor});
|
|
}
|
|
|
|
this.renderer.updateCursor(this.cursor);
|
|
this.renderer.scrollCursorIntoView();
|
|
},
|
|
|
|
onCompositionStart : function()
|
|
{
|
|
this.renderer.showComposition(this.cursor);
|
|
this.onTextInput(" ");
|
|
},
|
|
|
|
onCompositionUpdate : function(text) {
|
|
this.renderer.setCompositionText(text);
|
|
},
|
|
|
|
onCompositionEnd : function() {
|
|
this.renderer.hideComposition();
|
|
this.removeLeft();
|
|
},
|
|
|
|
navigateUp : function()
|
|
{
|
|
this.clearSelection();
|
|
this.moveCursorUp();
|
|
this.renderer.scrollCursorIntoView();
|
|
},
|
|
|
|
navigateDown : function() {
|
|
this.clearSelection();
|
|
this.moveCursorDown();
|
|
this.renderer.scrollCursorIntoView();
|
|
},
|
|
|
|
navigateLeft : function()
|
|
{
|
|
if (this.hasSelection()) {
|
|
var selectionStart = this.getSelectionRange().start;
|
|
this.moveCursorTo(selectionStart.row, selectionStart.column);
|
|
} else {
|
|
this.moveCursorLeft();
|
|
}
|
|
this.clearSelection();
|
|
|
|
this.renderer.scrollCursorIntoView();
|
|
},
|
|
|
|
navigateRight : function()
|
|
{
|
|
if (this.hasSelection()) {
|
|
var selectionEnd = this.getSelectionRange().end;
|
|
this.moveCursorTo(selectionEnd.row, selectionEnd.column);
|
|
} else {
|
|
this.moveCursorRight();
|
|
}
|
|
this.clearSelection();
|
|
|
|
this.renderer.scrollCursorIntoView();
|
|
},
|
|
|
|
navigateLineStart : function()
|
|
{
|
|
this.clearSelection();
|
|
this.moveCursorLineStart();
|
|
this.renderer.scrollCursorIntoView();
|
|
},
|
|
|
|
navigateLineEnd : function()
|
|
{
|
|
this.clearSelection();
|
|
this.moveCursorLineEnd();
|
|
this.renderer.scrollCursorIntoView();
|
|
},
|
|
|
|
moveCursorUp : function() {
|
|
this.moveCursorBy(-1, 0);
|
|
},
|
|
|
|
moveCursorDown : function() {
|
|
this.moveCursorBy(1, 0);
|
|
},
|
|
|
|
moveCursorLeft : function()
|
|
{
|
|
if (this.cursor.column == 0) {
|
|
if (this.cursor.row > 0) {
|
|
this.moveCursorTo(this.cursor.row-1, this.doc.getLine(this.cursor.row-1).length);
|
|
}
|
|
} else {
|
|
this.moveCursorBy(0, -1);
|
|
}
|
|
},
|
|
|
|
moveCursorRight : function()
|
|
{
|
|
if (this.cursor.column == this.doc.getLine(this.cursor.row).length) {
|
|
if (this.cursor.row < this.doc.getLength()-1) {
|
|
this.moveCursorTo(this.cursor.row+1, 0);
|
|
}
|
|
} else {
|
|
this.moveCursorBy(0, 1);
|
|
}
|
|
this.renderer.scrollCursorIntoView();
|
|
},
|
|
|
|
moveCursorLineStart : function()
|
|
{
|
|
this.moveCursorTo(this.cursor.row, 0);
|
|
this.renderer.scrollCursorIntoView();
|
|
},
|
|
|
|
moveCursorLineEnd : function() {
|
|
this.moveCursorTo(this.cursor.row, this.doc.getLine(this.cursor.row).length);
|
|
this.renderer.scrollCursorIntoView();
|
|
},
|
|
|
|
moveCursorBy : function(rows, chars) {
|
|
this.moveCursorTo(this.cursor.row+rows, this.cursor.column+chars);
|
|
},
|
|
|
|
moveCursorTo : function(row, column)
|
|
{
|
|
if (row >= this.doc.getLength()) {
|
|
this.cursor.row = this.doc.getLength()-1;
|
|
this.cursor.column = this.doc.getLine(this.cursor.row).length;
|
|
} else if (row < 0) {
|
|
this.cursor.row = 0;
|
|
this.cursor.column = 0;
|
|
} else {
|
|
this.cursor.row = row;
|
|
this.cursor.column = Math.min(this.doc.getLine(this.cursor.row).length, Math.max(0, column));
|
|
}
|
|
this.updateCursor();
|
|
},
|
|
|
|
hasSelection : function() {
|
|
return !!this.selectionLead;
|
|
},
|
|
|
|
setSelectionAnchor : function(row, column)
|
|
{
|
|
this.clearSelection();
|
|
|
|
this.selectionAnchor = {
|
|
row: Math.min(this.doc.getLength()-1, Math.max(0, row)),
|
|
column: Math.min(this.doc.getLine(this.cursor.row).length, Math.max(0, column))
|
|
};
|
|
|
|
this.selectionLead = null;
|
|
},
|
|
|
|
getSelectionRange : function()
|
|
{
|
|
var anchor = this.selectionAnchor;
|
|
var lead = this.selectionLead;
|
|
|
|
if (!anchor) {
|
|
return null;
|
|
} else {
|
|
if (anchor.row > lead.row || (anchor.row == lead.row && anchor.column > lead.column)) {
|
|
return {
|
|
start: lead,
|
|
end: anchor
|
|
}
|
|
} else {
|
|
return {
|
|
start: anchor,
|
|
end: lead
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
clearSelection : function()
|
|
{
|
|
this.selectionLead = null;
|
|
this.selectionAnchor = null;
|
|
|
|
if (this.selection) {
|
|
this.renderer.removeMarker(this.selection);
|
|
this.selection = null;
|
|
}
|
|
},
|
|
|
|
selectAll : function()
|
|
{
|
|
var lastRow = this.doc.getLength()-1;
|
|
this.setSelectionAnchor(lastRow, this.doc.getLine(lastRow).length);
|
|
|
|
this._moveSelection(function() {
|
|
this.moveCursorTo(0, 0);
|
|
});
|
|
},
|
|
|
|
_moveSelection : function(mover)
|
|
{
|
|
if (!this.selectionAnchor) {
|
|
this.selectionAnchor = {
|
|
row: this.cursor.row,
|
|
column: this.cursor.column
|
|
}
|
|
}
|
|
|
|
mover.call(this);
|
|
|
|
this.selectionLead = {
|
|
row: this.cursor.row,
|
|
column: this.cursor.column
|
|
}
|
|
|
|
if (this.selection) {
|
|
this.renderer.removeMarker(this.selection);
|
|
}
|
|
this.selection = this.renderer.addMarker(this.getSelectionRange(), "selection");
|
|
this.renderer.scrollCursorIntoView();
|
|
},
|
|
|
|
selectUp : function() {
|
|
this._moveSelection(this.moveCursorUp);
|
|
},
|
|
|
|
selectDown : function() {
|
|
this._moveSelection(this.moveCursorDown);
|
|
},
|
|
|
|
selectRight : function() {
|
|
this._moveSelection(this.moveCursorRight);
|
|
},
|
|
|
|
selectLeft : function() {
|
|
this._moveSelection(this.moveCursorLeft);
|
|
},
|
|
|
|
selectLineStart : function() {
|
|
this._moveSelection(this.moveCursorLineStart);
|
|
},
|
|
|
|
selectLineEnd : function() {
|
|
this._moveSelection(this.moveCursorLineEnd);
|
|
},
|
|
|
|
selectCurrentLine : function()
|
|
{
|
|
this.setSelectionAnchor(this.cursor.row, 0);
|
|
this._moveSelection(function() {
|
|
this.moveCursorTo(this.cursor.row+1, 0);
|
|
});
|
|
}
|
|
} |