ace/editor.html
2010-04-02 15:39:45 +02:00

521 lines
No EOL
11 KiB
HTML

<!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>Editor</title>
<meta name="author" content="Fabian Jakobs">
<style type="text/css" media="screen">
#container {
position: absolute;
width: 600px;
}
#canvas {
position: absolute;
border: 1px solid black;
margin: 4px;
width: 590px;
height: 400px;
overflow-x: auto;
overflow-y: auto;
font-family: Courier New;
white-space: nowrap;
}
#canvas.focus {
border: 1px solid #327fbd;;
}
.composition {
position: absolute;
text-decoration: underline;
}
.cursor {
position: absolute;
width: 1px;
background: black;
}
.line {
white-space: nowrap;
}
.line.odd {
background: #FAFAFA;
}
</style>
</head>
<body>
<div id="container">
<div id="canvas"></div>
</div>
<script type="text/javascript" charset="utf-8">
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);
});
}
}
function setText(elem, text) {
if (elem.innerText !== undefined) {
elem.innerText = text;
}
if (elem.textContent !== undefined) {
elem.textContent = text;
}
}
function stopEvent(e) {
stopPropagation(e);
preventDefault(e);
return false;
}
function stopPropagation(e) {
if (e.stopPropagation)
e.stopPropagation();
else
e.cancelBubble = true;
}
function preventDefault (e)
{
if (e.preventDefault)
e.preventDefault();
else
e.returnValue = false;
}
function TextInput(parentNode, host) {
var text = document.createElement("textarea");
var style = text.style;
style.position = "absolute";
style.left = "-10000px";
style.top = "-10000px";
parentNode.appendChild(text);
var inCompostion = false;
var onTextInput = function(e) {
setTimeout(function() {
if (!inCompostion) {
if (text.value) host.onTextInput(text.value);
text.value = "";
}
}, 0)
}
var onCompositionStart = function(e)
{
inCompostion = true;
if (text.value) host.onTextInput(text.value);
text.value = "";
host.onCompositionStart();
setTimeout(onCompositionUpdate, 0);
}
var onCompositionUpdate = function() {
host.onCompositionUpdate(text.value);
}
var onCompositionEnd = function()
{
inCompostion = false;
host.onCompositionEnd();
onTextInput();
}
addListener(text, "keypress", onTextInput, false);
addListener(text, "textInput", onTextInput, false);
addListener(text, "paste", onTextInput, false);
addListener(text, "propertychange", onTextInput, false);
addListener(text, "compositionstart", onCompositionStart, false);
addListener(text, "compositionupdate", onCompositionUpdate, false);
addListener(text, "compositionend", onCompositionEnd, false);
addListener(text, "blur", function() {
host.onBlur();
}, false);
addListener(text, "focus", function() {
host.onFocus();
}, false);
this.focus = function() {
text.focus();
}
this.blur = function() {
this.blur();
}
};
var keys = {
UP: 38,
RIGHT: 39,
DOWN: 40,
LEFT: 37,
POS1: 36,
END: 35,
DELETE: 46,
BACKSPACE: 8,
TAB: 9
}
function KeyBinding(element, host)
{
addListener(element, "keydown", function(e)
{
var key = e.keyCode;
switch (key)
{
case keys.UP:
host.moveUp();
return stopEvent(e);
case keys.DOWN:
host.moveDown();
return stopEvent(e);
case keys.LEFT:
host.moveLeft();
return stopEvent(e);
case keys.RIGHT:
host.moveRight();
return stopEvent(e);
case keys.POS1:
host.moveLineStart();
return stopEvent(e);
case keys.END:
host.moveLineEnd();
return stopEvent(e);
case keys.DELETE:
host.removeRight();
return stopEvent(e);
case keys.BACKSPACE:
host.removeLeft();
return stopEvent(e);
case keys.TAB:
host.onTextInput(" ");
return stopEvent(e);
}
});
};
function Editor(containerId, canvasId)
{
this.container = document.getElementById(containerId);
this.canvas = document.getElementById(canvasId);
var textInput = new TextInput(container, this);
new KeyBinding(this.container, this);
var self = this;
addListener(container, "mousedown", function(e) {
textInput.focus();
self.placeCursorToMouse(e.pageX, e.pageY);
return preventDefault(e);
});
this.measureSizes();
this.composition = document.createElement("div");
this.composition.className = "composition";
this.composition.style.height = this.lineHeight + "px";
this.cursor = document.createElement("div");
this.cursor.className = "cursor";
this.cursor.style.height = this.lineHeight + "px";
this.row = 0;
this.col = 0;
this.lines = [""];
this.draw();
}
Editor.prototype = {
measureSizes : function()
{
var measureNode = document.createElement("div");
var style = measureNode.style;
style.width = style.height = "auto";
style.left = style.top = "-1000px";
style.visibility = "hidden";
style.position = "absolute";
style.overflow = "visible";
measureNode.innerHTML = "X<br>X";
this.canvas.appendChild(measureNode);
this.lineHeight = Math.round(measureNode.offsetHeight / 2);
this.characterWidth = measureNode.offsetWidth;
this.canvas.removeChild(measureNode);
},
draw : function()
{
if (this.cursor.parentNode) {
this.canvas.removeChild(this.cursor);
}
// TODO HACK
var longestLine = this.canvas.clientWidth;
for (var i=0; i < this.lines.length; i++) {
longestLine = Math.max(longestLine, (this.lines[i].length * this.characterWidth));
}
var html = [];
for (var i=0; i < this.lines.length; i++)
{
html.push(
"<div class='line ",
i % 2 == 0 ? "even" : "odd",
"' style='height:" + this.lineHeight + "px;",
"width:", longestLine, "px'>",
this.lines[i].
replace(/&/g, "&amp;").
replace(/</g, "&lt;").
replace(/\s/g, "&nbsp;"),
"</div>"
);
};
this.canvas.innerHTML = html.join("");
this.updateCursor();
},
updateCursor : function()
{
var left = this.col * this.characterWidth;
var top = this.row * this.lineHeight;
this.cursor.style.left = left + "px";
this.cursor.style.top = top + "px";
this.canvas.appendChild(this.cursor);
if (this.canvas.scrollLeft > left) {
this.canvas.scrollLeft = left;
}
if (this.canvas.scrollLeft + this.canvas.clientWidth < left + this.characterWidth) {
this.canvas.scrollLeft = left + this.characterWidth - this.canvas.clientWidth;
}
if (this.canvas.scrollTop > top) {
this.canvas.scrollTop = top;
}
if (this.canvas.scrollTop + this.canvas.clientHeight < top + this.lineHeight) {
this.canvas.scrollTop = top + this.lineHeight - this.canvas.clientHeight;
}
},
onFocus : function() {
this.canvas.className = "focus";
},
onBlur : function() {
this.canvas.className = "";
},
placeCursorToMouse : function(pageX, pageY)
{
var canvas = this.canvas;
var canvasPos = canvas.getBoundingClientRect();
var row = Math.floor((pageY + canvas.scrollTop - canvasPos.top) / this.lineHeight);
var col = Math.floor((pageX + canvas.scrollLeft - canvasPos.left) / this.characterWidth);
this.moveTo(row, col)
},
onTextInput: function(text)
{
var newLines = text.split(/[\n\r]/);
if (text == "\n")
{
var line = this.lines[this.row] || "";
this.lines[this.row] = line.substring(0, this.col);
this.lines.splice(this.row+1, 0, line.substring(this.col));
this.row += 1;
this.col = 0;
}
else if (newLines.length == 1)
{
var line = this.lines[this.row] || "";
this.lines[this.row] = line.substring(0, this.col) + text + line.substring(this.col);
this.col += text.length;
}
else
{
var line = this.lines[this.row] || "";
this.lines[this.row] = line.substring(0, this.col) + newLines[0];
this.lines[this.row+1] = newLines[newLines.length-1] + line.substring(this.col);
if (newLines.length > 2)
{
var args = [this.row + 1, 0]
args.push.apply(args, newLines.slice(1, -1));
this.lines.splice.apply(this.lines, args);
}
this.row = this.row + newLines.length - 1;
this.col = newLines[newLines.length-1].length;
}
this.draw();
},
removeRight : function()
{
var currentLine = this.lines[this.row];
if (this.col == currentLine.length)
{
this.lines[this.row] = currentLine + (this.lines[this.row+1] || "");
this.lines.splice(this.row+1, 1);
}
else
{
this.lines[this.row] = currentLine.substring(0, this.col) + currentLine.substring(this.col + 1);
}
this.draw();
},
removeLeft : function()
{
var currentLine = this.lines[this.row] || "";
if (this.col == 0)
{
if (this.row !== 0)
{
var prevLine = this.lines[this.row-1] || ""
this.lines[this.row-1] = currentLine + prevLine;
this.lines.splice(this.row, 1);
this.row -! 1;
this.col = prevLine.length;
}
}
else
{
this.lines[this.row] = currentLine.substring(0, this.col-1) + currentLine.substring(this.col);
this.col -= 1;
}
this.draw();
},
onCompositionStart : function()
{
setText(this.composition, "");
this.composition.style.left = (this.col * this.characterWidth) + "px";
this.composition.style.top = (this.row * this.lineHeight) + "px";
this.onTextInput(" ");
this.canvas.appendChild(this.composition);
},
onCompositionUpdate : function(text) {
setText(this.composition, text);
},
onCompositionEnd : function() {
if (this.composition.parentNode) {
this.canvas.removeChild(this.composition);
}
this.removeLeft();
},
moveUp : function() {
this.moveBy(-1, 0);
},
moveDown : function() {
this.moveBy(1, 0);
},
moveLeft : function() {
if (this.col == 0) {
if (this.row > 0) {
this.moveTo(this.row-1, this.lines[this.row-1].length);
}
}
this.moveBy(0, -1);
},
moveRight : function()
{
if (this.col == this.lines[this.row].length) {
if (this.row < this.lines.length-1) {
this.moveTo(this.row+1, 0);
}
} else {
this.moveBy(0, 1);
}
},
moveLineStart : function() {
this.moveTo(this.row, 0);
},
moveLineEnd : function() {
this.moveTo(this.row, this.lines[this.row].length);
},
moveBy : function(rows, chars) {
this.moveTo(this.row+rows, this.col+chars);
},
moveTo : function(row, column)
{
this.row = Math.min(this.lines.length-1, Math.max(0, row));
this.col = Math.min(this.lines[this.row].length, Math.max(0, column));
this.updateCursor();
}
}
new Editor("container", "canvas", "text");
</script>
</body>
</html>