Copy and paste and basic selection support
This commit is contained in:
parent
828b4a184a
commit
f85c1b9758
6 changed files with 358 additions and 22 deletions
|
|
@ -13,7 +13,10 @@ function DumbRenderer(containerId)
|
||||||
|
|
||||||
this.cursor = document.createElement("div");
|
this.cursor = document.createElement("div");
|
||||||
this.cursor.className = "cursor";
|
this.cursor.className = "cursor";
|
||||||
this.cursor.style.height = this.lineHeight + "px";
|
this.cursor.style.height = this.lineHeight + "px";
|
||||||
|
|
||||||
|
this.markers = {};
|
||||||
|
this._markerId = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
DumbRenderer.prototype =
|
DumbRenderer.prototype =
|
||||||
|
|
@ -167,6 +170,38 @@ DumbRenderer.prototype =
|
||||||
this.container.className = "";
|
this.container.className = "";
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addMarker : function(range, clazz)
|
||||||
|
{
|
||||||
|
var id = this._markerId++;
|
||||||
|
this.markers[id] = {
|
||||||
|
range: range,
|
||||||
|
type: "line",
|
||||||
|
clazz: clazz
|
||||||
|
};
|
||||||
|
|
||||||
|
this.draw();
|
||||||
|
|
||||||
|
return id;
|
||||||
|
},
|
||||||
|
|
||||||
|
removeMarker : function(markerId)
|
||||||
|
{
|
||||||
|
var marker = this.markers[markerId];
|
||||||
|
if (marker) {
|
||||||
|
delete(this.markers[markerId]);
|
||||||
|
this.draw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateMarker : function(markerId, range)
|
||||||
|
{
|
||||||
|
var marker = this.markers[markerId];
|
||||||
|
if (marker) {
|
||||||
|
marker.range = range;
|
||||||
|
this.draw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
showComposition : function(position)
|
showComposition : function(position)
|
||||||
{
|
{
|
||||||
setText(this.composition, "");
|
setText(this.composition, "");
|
||||||
|
|
|
||||||
101
Editor.js
101
Editor.js
|
|
@ -39,12 +39,26 @@ function TextInput(parentNode, host) {
|
||||||
host.onCompositionEnd();
|
host.onCompositionEnd();
|
||||||
onTextInput();
|
onTextInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var onCopy = function() {
|
||||||
|
text.value = host.getCopyText();
|
||||||
|
text.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
var onCut = function() {
|
||||||
|
text.value = host.getCopyText();
|
||||||
|
host.onCut();
|
||||||
|
text.select();
|
||||||
|
}
|
||||||
|
|
||||||
addListener(text, "keypress", onTextInput, false);
|
addListener(text, "keypress", onTextInput, false);
|
||||||
addListener(text, "textInput", onTextInput, false);
|
addListener(text, "textInput", onTextInput, false);
|
||||||
addListener(text, "paste", onTextInput, false);
|
addListener(text, "paste", onTextInput, false);
|
||||||
addListener(text, "propertychange", onTextInput, false);
|
addListener(text, "propertychange", onTextInput, false);
|
||||||
|
|
||||||
|
addListener(text, "copy", onCopy, false);
|
||||||
|
addListener(text, "cut", onCut, false);
|
||||||
|
|
||||||
addListener(text, "compositionstart", onCompositionStart, false);
|
addListener(text, "compositionstart", onCompositionStart, false);
|
||||||
addListener(text, "compositionupdate", onCompositionUpdate, false);
|
addListener(text, "compositionupdate", onCompositionUpdate, false);
|
||||||
addListener(text, "compositionend", onCompositionEnd, false);
|
addListener(text, "compositionend", onCompositionEnd, false);
|
||||||
|
|
@ -84,6 +98,12 @@ function KeyBinding(element, host)
|
||||||
addListener(element, "keydown", function(e)
|
addListener(element, "keydown", function(e)
|
||||||
{
|
{
|
||||||
var key = e.keyCode;
|
var key = e.keyCode;
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
/*
|
||||||
|
if (!e.shiftKey) {
|
||||||
|
host.clearSelection();
|
||||||
|
}*/
|
||||||
|
|
||||||
switch (key)
|
switch (key)
|
||||||
{
|
{
|
||||||
|
|
@ -96,11 +116,21 @@ function KeyBinding(element, host)
|
||||||
return stopEvent(e);
|
return stopEvent(e);
|
||||||
|
|
||||||
case keys.LEFT:
|
case keys.LEFT:
|
||||||
host.moveLeft();
|
if (e.metaKey) {
|
||||||
|
host.moveLineStart();
|
||||||
|
} else {
|
||||||
|
host.moveLeft();
|
||||||
|
}
|
||||||
return stopEvent(e);
|
return stopEvent(e);
|
||||||
|
|
||||||
case keys.RIGHT:
|
case keys.RIGHT:
|
||||||
host.moveRight();
|
if (e.metaKey) {
|
||||||
|
host.moveLineEnd();
|
||||||
|
} else if (e.shiftKey) {
|
||||||
|
host.selectRight();
|
||||||
|
} else {
|
||||||
|
host.moveRight();
|
||||||
|
}
|
||||||
return stopEvent(e);
|
return stopEvent(e);
|
||||||
|
|
||||||
case keys.POS1:
|
case keys.POS1:
|
||||||
|
|
@ -147,13 +177,17 @@ function Editor(doc, renderer)
|
||||||
return preventDefault(e);
|
return preventDefault(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.cursor = {
|
|
||||||
row: 0,
|
|
||||||
column: 0
|
|
||||||
}
|
|
||||||
this.doc = doc;
|
this.doc = doc;
|
||||||
renderer.setDocument(doc);
|
renderer.setDocument(doc);
|
||||||
|
|
||||||
|
this.cursor = {
|
||||||
|
row: 0,
|
||||||
|
column: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
this.selectionRange = null;
|
||||||
|
this.selection = null;
|
||||||
|
|
||||||
this.draw();
|
this.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,6 +213,25 @@ Editor.prototype =
|
||||||
this.renderer.visualizeBlur();
|
this.renderer.visualizeBlur();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getCopyText : function()
|
||||||
|
{
|
||||||
|
if (this.selectionRange) {
|
||||||
|
return this.doc.getTextRange(this.selectionRange);
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onCut : function()
|
||||||
|
{
|
||||||
|
if (this.selectionRange)
|
||||||
|
{
|
||||||
|
this.cursor = this.doc.remove(this.selectionRange);
|
||||||
|
this.clearSelection();
|
||||||
|
this.draw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
placeCursorToMouse : function(pageX, pageY)
|
placeCursorToMouse : function(pageX, pageY)
|
||||||
{
|
{
|
||||||
var pos = this.renderer.screenToTextCoordinates(pageX, pageY);
|
var pos = this.renderer.screenToTextCoordinates(pageX, pageY);
|
||||||
|
|
@ -289,6 +342,40 @@ Editor.prototype =
|
||||||
this.renderer.scrollCursorIntoView();
|
this.renderer.scrollCursorIntoView();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clearSelection : function()
|
||||||
|
{
|
||||||
|
this.selectionRange = null;
|
||||||
|
if (this.selection) {
|
||||||
|
this.renderer.removeMarker(this.selection);
|
||||||
|
this.selection = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
selectRight : function()
|
||||||
|
{
|
||||||
|
if (!this.selectionRange) {
|
||||||
|
this.selectionRange = {
|
||||||
|
start: {
|
||||||
|
row: this.cursor.row,
|
||||||
|
column: this.cursor.column
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.moveRight();
|
||||||
|
|
||||||
|
this.selectionRange.end = {
|
||||||
|
row: this.cursor.row,
|
||||||
|
column: this.cursor.column
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selection) {
|
||||||
|
this.renderer.updateMarker(this.selection, this.selectionRange);
|
||||||
|
} else {
|
||||||
|
this.selection = this.renderer.addMarker(this.selectionRange, "selection");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
moveBy : function(rows, chars) {
|
moveBy : function(rows, chars) {
|
||||||
this.moveTo(this.cursor.row+rows, this.cursor.column+chars);
|
this.moveTo(this.cursor.row+rows, this.cursor.column+chars);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,20 @@ TextDocument.prototype =
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getTextRange : function(range)
|
||||||
|
{
|
||||||
|
if (range.start.row == range.end.row) {
|
||||||
|
return this.lines[range.start.row].substring(range.start.column, range.end.column);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
this.lines[range.start.row].substring(range.start.column) +
|
||||||
|
this.lines.slice(range.start.row+1, range.end.row+1) +
|
||||||
|
this.lines[range.end.row].substring(0, range.end.column)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
remove : function(range)
|
remove : function(range)
|
||||||
{
|
{
|
||||||
var firstRow = range.start.row;
|
var firstRow = range.start.row;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,21 @@ function VirtualRenderer(containerId)
|
||||||
row: 0,
|
row: 0,
|
||||||
column: 0
|
column: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.layers = [];
|
||||||
|
this.layers.push({
|
||||||
|
element: this.canvas,
|
||||||
|
update: this.updateLines
|
||||||
|
});
|
||||||
|
|
||||||
|
this.markerEl = document.createElement("div");
|
||||||
|
this.markerEl.className = "markers";
|
||||||
|
this.container.appendChild(this.markerEl);
|
||||||
|
|
||||||
|
this.layers.push({
|
||||||
|
element: this.markerEl,
|
||||||
|
update: this.updateMarkers
|
||||||
|
});
|
||||||
}
|
}
|
||||||
inherits(VirtualRenderer, DumbRenderer);
|
inherits(VirtualRenderer, DumbRenderer);
|
||||||
|
|
||||||
|
|
@ -19,15 +34,27 @@ VirtualRenderer.prototype.draw = function()
|
||||||
var minHeight = this.container.clientHeight + offset;
|
var minHeight = this.container.clientHeight + offset;
|
||||||
|
|
||||||
var longestLine = this.getLongestLineWidth(lines);
|
var longestLine = this.getLongestLineWidth(lines);
|
||||||
|
|
||||||
this.canvas.style.marginTop = (-offset) + "px";
|
|
||||||
this.canvas.style.height = minHeight + "px";
|
|
||||||
this.canvas.style.width = longestLine + "px";
|
|
||||||
|
|
||||||
var lineCount = Math.ceil(minHeight / this.lineHeight);
|
var lineCount = Math.ceil(minHeight / this.lineHeight);
|
||||||
this.firstRow = firstRow = Math.round((this.scrollTop - offset) / this.lineHeight);
|
this.firstRow = firstRow = Math.round((this.scrollTop - offset) / this.lineHeight);
|
||||||
var lastRow = Math.min(lines.length, firstRow+lineCount);
|
var lastRow = Math.min(lines.length, firstRow+lineCount);
|
||||||
|
|
||||||
|
for (var i=0; i < this.layers.length; i++)
|
||||||
|
{
|
||||||
|
var layer = this.layers[i];
|
||||||
|
|
||||||
|
var style = layer.element.style;
|
||||||
|
style.marginTop = (-offset) + "px";
|
||||||
|
style.height = minHeight + "px";
|
||||||
|
style.width = longestLine + "px";
|
||||||
|
|
||||||
|
layer.update.call(this, layer.element, firstRow, lastRow, longestLine);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.updateCursor(this.cursorPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
VirtualRenderer.prototype.updateLines = function(element, firstRow, lastRow, width)
|
||||||
|
{
|
||||||
var html = [];
|
var html = [];
|
||||||
for (var i=firstRow; i<lastRow; i++)
|
for (var i=firstRow; i<lastRow; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -35,16 +62,14 @@ VirtualRenderer.prototype.draw = function()
|
||||||
"<div class='line ",
|
"<div class='line ",
|
||||||
i % 2 == 0 ? "even" : "odd",
|
i % 2 == 0 ? "even" : "odd",
|
||||||
"' style='height:" + this.lineHeight + "px;",
|
"' style='height:" + this.lineHeight + "px;",
|
||||||
"width:", longestLine, "px'>"
|
"width:", width, "px'>"
|
||||||
);
|
);
|
||||||
this.renderLine(html, i),
|
this.renderLine(html, i),
|
||||||
html.push("</div>");
|
html.push("</div>");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.canvas.innerHTML = html.join("");
|
element.innerHTML = html.join("");
|
||||||
|
};
|
||||||
this.updateCursor(this.cursorPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualRenderer.prototype.renderLine = function(stringBuilder, row)
|
VirtualRenderer.prototype.renderLine = function(stringBuilder, row)
|
||||||
{
|
{
|
||||||
|
|
@ -66,6 +91,67 @@ VirtualRenderer.prototype.renderLine = function(stringBuilder, row)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
VirtualRenderer.prototype.updateMarkers = function(element, firstRow, lastRow, width)
|
||||||
|
{
|
||||||
|
var html = [];
|
||||||
|
for (var key in this.markers)
|
||||||
|
{
|
||||||
|
var marker = this.markers[key];
|
||||||
|
var range = marker.range;
|
||||||
|
|
||||||
|
if (range.start.row !== range.end.row)
|
||||||
|
{
|
||||||
|
if (range.start.row >= firstRow && range.start.row <= lastRow)
|
||||||
|
{
|
||||||
|
html.push(
|
||||||
|
"<div class='", marker.clazz, "' style='",
|
||||||
|
"height:", this.lineHeight, "px;",
|
||||||
|
"width:", width - (range.start.column * this.characterWidth), "px;",
|
||||||
|
"top:", (range.start.row-firstRow) * this.lineHeight, "px;",
|
||||||
|
"left:", range.start.column * this.characterWidth, "px;'></div>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (range.end.row >= firstRow && range.end.row <= lastRow)
|
||||||
|
{
|
||||||
|
html.push(
|
||||||
|
"<div class='", marker.clazz, "' style='",
|
||||||
|
"height:", this.lineHeight, "px;",
|
||||||
|
"top:", (range.end.row-firstRow) * this.lineHeight, "px;",
|
||||||
|
"width:", range.end.column * this.characterWidth, "px;'></div>"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var row=range.start.row+1; row < range.end.row; row++)
|
||||||
|
{
|
||||||
|
if (row >= firstRow && row <= lastRow)
|
||||||
|
{
|
||||||
|
html.push(
|
||||||
|
"<div class='", marker.clazz, "' style='",
|
||||||
|
"height:", this.lineHeight, "px;",
|
||||||
|
"width:", width, "px;",
|
||||||
|
"top:", (row-firstRow) * this.lineHeight, "px;'></div>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (range.start.row >= firstRow && range.start.row <= lastRow)
|
||||||
|
{
|
||||||
|
html.push(
|
||||||
|
"<div class='", marker.clazz, "' style='",
|
||||||
|
"height:", this.lineHeight, "px;",
|
||||||
|
"width:", (range.end.column - range.start.column) * this.characterWidth, "px;",
|
||||||
|
"top:", (range.start.row-firstRow) * this.lineHeight, "px;",
|
||||||
|
"left:", range.start.column * this.characterWidth, "px;'></div>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
element.innerHTML = html.join("");
|
||||||
|
};
|
||||||
|
|
||||||
VirtualRenderer.prototype.updateCursor = function(position)
|
VirtualRenderer.prototype.updateCursor = function(position)
|
||||||
{
|
{
|
||||||
this.cursorPos = {
|
this.cursorPos = {
|
||||||
|
|
|
||||||
105
cut_copy.html
Normal file
105
cut_copy.html
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
|
||||||
|
"http://www.w3.org/TR/html4/strict.dtd">
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<title>Text Events</title>
|
||||||
|
<meta name="author" content="Fabian Jakobs">
|
||||||
|
|
||||||
|
<style type="text/css" media="screen">
|
||||||
|
|
||||||
|
#container {
|
||||||
|
border: 1px solid black;
|
||||||
|
width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas {
|
||||||
|
border: 1px solid black;
|
||||||
|
margin: 4px;
|
||||||
|
width: 590px;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="container">
|
||||||
|
<textarea id="text"></textarea>
|
||||||
|
<div id="canvas"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="button" value="Clear" id="some_name" onclick="document.getElementById('logger').innerHTML = ''">
|
||||||
|
<div id="logger">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript" charset="utf-8">
|
||||||
|
|
||||||
|
if (!window.console) window.console = {};
|
||||||
|
if (!console.log) {
|
||||||
|
var logger = document.getElementById("logger");
|
||||||
|
console.log = function() {
|
||||||
|
logger.innerHTML += Array.prototype.join.call(arguments, ", ") + "<br>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addListener(elem, type, callback) {
|
||||||
|
if (elem.addEventListener) {
|
||||||
|
return elem.addEventListener(type, callback, false);
|
||||||
|
}
|
||||||
|
if (elem.attachEvent) {
|
||||||
|
elem.attachEvent("on" + type, function() {
|
||||||
|
callback(window.event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var container = document.getElementById("container");
|
||||||
|
var canvas = document.getElementById("canvas");
|
||||||
|
var text = document.getElementById("text");
|
||||||
|
|
||||||
|
function log(e) {
|
||||||
|
console.log(e.type, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function logKey(e) {
|
||||||
|
console.log(e.type, e.charCode, e.keyCode, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
addListener(text, "keydown", logKey, false);
|
||||||
|
addListener(text, "keyup", logKey, false);
|
||||||
|
addListener(text, "keypress", logKey, false);
|
||||||
|
|
||||||
|
addListener(text, "textInput", function(e) {
|
||||||
|
console.log(e.type, e.data, e);
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
function fillSelection() {
|
||||||
|
text.value = "Juhu Kinners";
|
||||||
|
text.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
addListener(text, "copy", fillSelection, false);
|
||||||
|
addListener(text, "paste", log, false);
|
||||||
|
addListener(text, "cut", fillSelection, false);
|
||||||
|
|
||||||
|
addListener(text, "beforecopy", log, false);
|
||||||
|
addListener(text, "beforepaste", log, false);
|
||||||
|
addListener(text, "beforecut", log, false);
|
||||||
|
|
||||||
|
addListener(text, "compositionstart", log, false);
|
||||||
|
addListener(text, "compositionupdate", log, false);
|
||||||
|
addListener(text, "compositionend", log, false);
|
||||||
|
|
||||||
|
addListener(text, "propertychange", function(e) {
|
||||||
|
console.log(e.type, e.propertyName, e);
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
14
editor.html
14
editor.html
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
#virtual_container {
|
#virtual_container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
padding: 3px;
|
/*padding: 3px;*/
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.canvas {
|
.canvas {
|
||||||
|
z-index: 2;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-family: Monaco, "Courier New";
|
font-family: Monaco, "Courier New";
|
||||||
|
|
@ -62,7 +63,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.line.odd {
|
.line.odd {
|
||||||
background: #FAFAFA;
|
/*background: #FAFAFA;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.keyword {
|
.keyword {
|
||||||
|
|
@ -77,6 +78,15 @@
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
color: rgb(0, 102, 255);
|
color: rgb(0, 102, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markers {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection {
|
||||||
|
position: absolute;
|
||||||
|
background: rgba(77, 151, 255, 0.33);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script src="lib.js" type="text/javascript" charset="utf-8"></script>
|
<script src="lib.js" type="text/javascript" charset="utf-8"></script>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue