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.className = "cursor";
|
||||
this.cursor.style.height = this.lineHeight + "px";
|
||||
this.cursor.style.height = this.lineHeight + "px";
|
||||
|
||||
this.markers = {};
|
||||
this._markerId = 1;
|
||||
}
|
||||
|
||||
DumbRenderer.prototype =
|
||||
|
|
@ -167,6 +170,38 @@ DumbRenderer.prototype =
|
|||
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)
|
||||
{
|
||||
setText(this.composition, "");
|
||||
|
|
|
|||
101
Editor.js
101
Editor.js
|
|
@ -39,12 +39,26 @@ function TextInput(parentNode, host) {
|
|||
host.onCompositionEnd();
|
||||
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, "textInput", onTextInput, false);
|
||||
addListener(text, "paste", onTextInput, false);
|
||||
addListener(text, "paste", onTextInput, false);
|
||||
addListener(text, "propertychange", onTextInput, false);
|
||||
|
||||
addListener(text, "copy", onCopy, false);
|
||||
addListener(text, "cut", onCut, false);
|
||||
|
||||
addListener(text, "compositionstart", onCompositionStart, false);
|
||||
addListener(text, "compositionupdate", onCompositionUpdate, false);
|
||||
addListener(text, "compositionend", onCompositionEnd, false);
|
||||
|
|
@ -84,6 +98,12 @@ function KeyBinding(element, host)
|
|||
addListener(element, "keydown", function(e)
|
||||
{
|
||||
var key = e.keyCode;
|
||||
|
||||
// TODO
|
||||
/*
|
||||
if (!e.shiftKey) {
|
||||
host.clearSelection();
|
||||
}*/
|
||||
|
||||
switch (key)
|
||||
{
|
||||
|
|
@ -96,11 +116,21 @@ function KeyBinding(element, host)
|
|||
return stopEvent(e);
|
||||
|
||||
case keys.LEFT:
|
||||
host.moveLeft();
|
||||
if (e.metaKey) {
|
||||
host.moveLineStart();
|
||||
} else {
|
||||
host.moveLeft();
|
||||
}
|
||||
return stopEvent(e);
|
||||
|
||||
case keys.RIGHT:
|
||||
host.moveRight();
|
||||
if (e.metaKey) {
|
||||
host.moveLineEnd();
|
||||
} else if (e.shiftKey) {
|
||||
host.selectRight();
|
||||
} else {
|
||||
host.moveRight();
|
||||
}
|
||||
return stopEvent(e);
|
||||
|
||||
case keys.POS1:
|
||||
|
|
@ -147,13 +177,17 @@ function Editor(doc, renderer)
|
|||
return preventDefault(e);
|
||||
});
|
||||
|
||||
this.cursor = {
|
||||
row: 0,
|
||||
column: 0
|
||||
}
|
||||
this.doc = doc;
|
||||
renderer.setDocument(doc);
|
||||
|
||||
this.cursor = {
|
||||
row: 0,
|
||||
column: 0
|
||||
};
|
||||
|
||||
this.selectionRange = null;
|
||||
this.selection = null;
|
||||
|
||||
this.draw();
|
||||
}
|
||||
|
||||
|
|
@ -179,6 +213,25 @@ Editor.prototype =
|
|||
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)
|
||||
{
|
||||
var pos = this.renderer.screenToTextCoordinates(pageX, pageY);
|
||||
|
|
@ -289,6 +342,40 @@ Editor.prototype =
|
|||
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) {
|
||||
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)
|
||||
{
|
||||
var firstRow = range.start.row;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,21 @@ function VirtualRenderer(containerId)
|
|||
row: 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);
|
||||
|
||||
|
|
@ -19,15 +34,27 @@ VirtualRenderer.prototype.draw = function()
|
|||
var minHeight = this.container.clientHeight + offset;
|
||||
|
||||
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);
|
||||
this.firstRow = firstRow = Math.round((this.scrollTop - offset) / this.lineHeight);
|
||||
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 = [];
|
||||
for (var i=firstRow; i<lastRow; i++)
|
||||
{
|
||||
|
|
@ -35,16 +62,14 @@ VirtualRenderer.prototype.draw = function()
|
|||
"<div class='line ",
|
||||
i % 2 == 0 ? "even" : "odd",
|
||||
"' style='height:" + this.lineHeight + "px;",
|
||||
"width:", longestLine, "px'>"
|
||||
"width:", width, "px'>"
|
||||
);
|
||||
this.renderLine(html, i),
|
||||
html.push("</div>");
|
||||
}
|
||||
|
||||
this.canvas.innerHTML = html.join("");
|
||||
|
||||
this.updateCursor(this.cursorPos);
|
||||
}
|
||||
element.innerHTML = html.join("");
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
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 {
|
||||
position: absolute;
|
||||
padding: 3px;
|
||||
/*padding: 3px;*/
|
||||
border: 1px solid black;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
|
|
@ -37,6 +37,7 @@
|
|||
}
|
||||
|
||||
.canvas {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
font-family: Monaco, "Courier New";
|
||||
|
|
@ -62,7 +63,7 @@
|
|||
}
|
||||
|
||||
.line.odd {
|
||||
background: #FAFAFA;
|
||||
/*background: #FAFAFA;*/
|
||||
}
|
||||
|
||||
.keyword {
|
||||
|
|
@ -77,6 +78,15 @@
|
|||
font-style: italic;
|
||||
color: rgb(0, 102, 255);
|
||||
}
|
||||
|
||||
.markers {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.selection {
|
||||
position: absolute;
|
||||
background: rgba(77, 151, 255, 0.33);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="lib.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue