console
const _module = (function () {
let factories = {};
let modules = {};
function search(folder, path) {
if (!path.includes(".")) {
return require(path);
}
let stack = folder.split("/");
let folders = path.split("/");
folders.forEach(folder => {
if (folder == "..") {
stack.pop();
} else if (folder && folder != ".") {
stack.push(folder);
}
});
return require(stack.join("/"));
}
function require(name, dep, callback) {
let $module;
let deps = [];
let $require = function (path) {
return search(name, path);
}
let _module = {
get exports() {
return modules[name];
},
set exports(value) {
modules[name] = value;
}
}
if (!modules[name]) {
if (factories[name] instanceof Function) {
modules[name] = {};
factories[name](
$require,
modules[name],
_module
);
}
}
$module = modules[name];
if (Array.isArray(dep)) {
dep.forEach(folder => {
switch (folder) {
case "require":
deps.push($require);
break;
case "exports":
deps.push($module);
break;
case "module":
deps.push(_module);
break;
default:
deps.push($require(folder));
break;
}
});
}
if (typeof callback != "function") {
callback = arguments[arguments.length - 1];
}
if (typeof callback == "function") {
callback.apply(null, [$module].concat(deps));
}
return $module;
}
function define(name, factory) {
factories[name] = factory;
}
return { define, require, factories, modules };
})();
const { define, require } = _module;
define("editor/native_extend", function (require, exports, module) {
(function () {
this.on = function (type, listener, option) {
this.addEventListener(type, listener, option);
}
this.off = function (type, listener) {
this.removeEventListener(type, listener);
}
this.once = function (type, listener, option) {
if (!option) {
option = {};
}
option.once = true;
this.addEventListener(type, listener, option);
}
}).call(EventTarget.prototype)
; (function () {
this.extractChildElement = function () {
let children = Array.from(this.children);
this.innerHTML = "";
return children;
};
this.setStyles = function (option) {
let $self = this;
Object.keys(option).forEach(function (key) {
$self.style[key] = option[key];
});
}
this.addClass = function () {
let classes = Array.from(arguments);
this.classList.add.apply(this.classList, classes);
}
this.removeClass = function () {
let classes = Array.from(arguments);
this.classList.remove.apply(this.classList, classes);
}
}).call(HTMLElement.prototype);
(function () {
this.isValidCSSValue = function (key, value) {
let el = this.createElement("div");
el.style[key] = value;
return el.style[key] != "";
}
this.buildElement = function (option) {
let el = option.tag instanceof HTMLElement ? option.tag : this.createElement(option.tag);
if (option.content != undefined) {
el.innerHTML = option.content;
}
if (option.clazz instanceof Array) {
el.classList.add(...option.clazz);
} else if (typeof option.clazz === 'string') {
el.classList.add(option.clazz);
}
if (option.style instanceof Object) {
Object.keys(option.style).forEach(key => {
el.style[key] = option.style[key];
});
}
if (option.parent && option.parent.appendChild) {
option.parent.appendChild(el);
} else if (typeof option.parent == "string") {
this.querySelector(option.parent).appendChild(el);
}
if (option.attr instanceof Object) {
Object.keys(option.attr).forEach(key => {
el.setAttribute(key, option.attr[key]);
});
}
if (option.prop instanceof Object) {
Object.keys(option.prop).forEach(key => {
el[key] = option.prop[key];
});
}
return el;
}
}).call(Document.prototype);
(function () {
this.toMap = function (callback) {
let map = {};
for (let i = 0; i < this.length; ++i) {
if (typeof callback === "function") {
let result = callback(this[i], i, this);
if (result) {
map[result.key] = result.value;
}
} else {
map[this[i]] = this[i];
}
}
return map;
}
this.empty = function () {
this.length = 0;
}
}).call(Array.prototype)
});
define("editor/utils", function (require, exports, module) {
exports.isFunction = function (fnc) {
return typeof fnc == "function";
}
exports.addEventListener = function (target, type, listener, option) {
if (target instanceof EventTarget) {
target.addEventListener(type, listener, option);
} else if (exports.isFunction(target.on)) {
target.on(type, listener);
}
}
exports.removeEventListener = function (target, type, listener) {
if (target instanceof EventTarget) {
target.removeEventListener(type, listener);
} else if (exports.isFunction(target.off)) {
target.off(type, listener);
}
}
exports.addOnceEventListener = function (target, type, listener) {
if (target instanceof EventTarget) {
target.addEventListener(type, listener, { once: true });
} else if (exports.isFunction(target.once)) {
target.once(type, listener);
} else if (exports.isFunction(target.on) && exports.isFunction(target.off)) {
function callback() {
listener.apply(this, Array.from(arguments));
target.off(type, callback);
}
target.on(type, callback);
}
}
let testCSSValidElem = document.createElement("div");
exports.isValidCSSValue = function (key, value) {
let valid;
testCSSValidElem.style[key] = value;
valid = testCSSValidElem.style[key] != "";
testCSSValidElem.style[key] = "";
return valid;
}
exports.buildElement = function (option) {
let el = option.tag instanceof HTMLElement ? option.tag : document.createElement(option.tag);
if (option.content != undefined) {
el.innerHTML = option.content;
}
if (option.clazz instanceof Array) {
el.classList.add(...option.clazz);
} else if (typeof option.clazz === 'string') {
el.classList.add(option.clazz);
}
if (option.style instanceof Object) {
Object.keys(option.style).forEach(key => {
el.style[key] = option.style[key];
});
}
if (option.parent && option.parent.appendChild) {
option.parent.appendChild(el);
} else if (typeof option.parent == "string") {
document.querySelector(option.parent).appendChild(el);
}
if (option.attr instanceof Object) {
Object.keys(option.attr).forEach(key => {
el.setAttribute(key, option.attr[key]);
});
}
if (option.prop instanceof Object) {
Object.keys(option.prop).forEach(key => {
el[key] = option.prop[key];
});
}
return el;
}
exports.setElementStyles = function (elem, styles) {
Object.keys(styles).forEach(key => {
elem.style[key] = styles[key];
});
}
exports.extractChildren = function (target) {
let children = Array.from(target.children);
target.replaceChildren();
return children;
}
exports.addClass = function (target, classes) {
if (!Array.isArray(classes)) {
classes = Array.from(arguments).slice(1);
}
target.classList.add.apply(target.classList, classes);
}
exports.removeClass = function (target, classes) {
if (!Array.isArray(classes)) {
classes = Array.from(arguments).slice(1);
}
target.classList.remove.apply(target.classList, classes);
}
exports.ArrayToMap = function (array, callback) {
let map = {};
for (let i = 0; i < array.length; ++i) {
if (typeof callback === "function") {
let result = callback(array[i], i, array);
if (result) {
map[result.key] = result.value;
}
} else {
map[array[i]] = array[i];
}
}
return map;
}
exports.emptyArray = function (array) {
array.length = 0;
}
exports.deepCopy = function (target, source) {
for (let key in source) {
let value = source[key];
if (value instanceof Array) {
target[key] = [];
deepCopy(target[key], value);
} else if (value instanceof Object) {
if (typeof value == "function") {
target[key] = value;
} else {
target[key] = {};
deepCopy(target[key], value);
}
} else {
target[key] = value;
}
}
}
exports.fncToDebounced = function (fnc, time, thisArg) {
function callback() {
clearTimeout(callback.timer);
callback.timer = setTimeout(() => {
fnc.apply(thisArg, Array.from(arguments));
}, time);
}
callback.cancel = function () {
clearTimeout(callback.timer);
}
callback.immediate = function () {
fnc.apply(thisArg, Array.from(arguments));
}
return callback;
}
exports.fncToThrottled = function (fnc, time, leading, thisArg) {
let args = [];
function callback() {
args = Array.from(arguments);
if (callback.waiting) {
return;
}
callback.waiting = true;
if (leading) {
fnc.apply(thisArg, args);
}
clearTimeout(callback.timer);
callback.timer = setTimeout(() => {
callback.waiting = false;
fnc.apply(thisArg, args);
}, time);
}
callback.cancel = function () {
clearTimeout(callback.timer);
}
callback.immediate = function () {
fnc.apply(thisArg, Array.from(arguments));
}
return callback;
}
exports.fncToLater = function (fnc, time, thisArg) {
function callback() {
callback.args = Array.from(arguments);
if (callback.pendding) {
return;
}
callback.pendding = true;
callback.timer = setTimeout(() => {
fnc.apply(thisArg, callback.args);
callback.pendding = false;
}, time);
}
callback.immediate = function () {
callback.args = Array.from(arguments);
fnc.apply(thisArg, callback.args);
}
callback.cancel = function () {
clearTimeout(callback.timer);
}
return callback;
}
exports.comparePoints = function (p1, p2) {
let value = p1.row - p2.row || p1.column - p2.column;
if (value < -1) {
value = -1;
}
if (value > 1) {
value = 1;
}
return value;
}
exports.comparePoint = function (point, range) {
let isBackwards = exports.isRangeBackwards(range);
let startPoint = range.start, endPoint = range.end;
if (isBackwards) {
startPoint = range.end;
endPoint = range.start;
}
if (exports.comparePoints(point, startPoint) >= 0 && exports.comparePoints(point, endPoint) <= 0) {
return 0;
}
if (exports.comparePoints(point, startPoint) < 0) {
return -1;
}
if (exports.comparePoints(point, endPoint) > 0) {
return 1;
}
}
exports.compareRange = function (range, other) {
let cmp = exports.comparePoint(other.start, range) + exports.comparePoint(other.end, range);
if (cmp > 1) {
return "before";
}
if (Math.abs(cmp) == 1) {
return "intersecting";
}
if (cmp == 0) {
return "inside";
}
if (cmp < 0) {
return "after";
}
}
exports.isRangeBackwards = function (range) {
if (exports.isFunction(range.isBackwards)) {
return range.isBackwards();
}
return exports.comparePoints(range.end, range.start) < 0;
}
exports.setRangeForward = function (range) {
if (exports.isRangeBackwards(range)) {
let oldStartRow = range.start.row;
let oldStartColumn = range.start.column;
let oldEndRow = range.end.row;
let oldEndColumn = range.end.column;
range.setStart(oldEndRow, oldEndColumn);
range.setEnd(oldStartRow, oldStartColumn);
}
}
exports.setRangeBackward = function (range) {
if (!exports.isRangeBackwards(range)) {
let oldStartRow = range.start.row;
let oldStartColumn = range.start.column;
let oldEndRow = range.end.row;
let oldEndColumn = range.end.column;
range.setEnd(oldStartRow, oldStartColumn);
range.setStart(oldEndRow, oldEndColumn);
}
}
exports.captureSelectMouse = function(mouseTarget, onMouseDown, onMouseMove, onMouseUp, scrollTarget, onScroll) {
let mouseEvent = null;
function _onMouseMove(e) {
mouseEvent = e;
if (typeof onMouseMove == "function") {
onMouseMove.call(this, e);
}
}
function _onMouseUp(e) {
document.off("mousemove", _onMouseMove);
if (typeof onMouseUp == "function") {
onMouseUp.call(this, e);
}
if (scrollTarget) {
scrollTarget.off("scroll", _onScroll);
}
mouseEvent = null;
}
function _onScroll(e) {
if (typeof onScroll == "function") {
onScroll.call(this, mouseEvent, e);
}
}
if (scrollTarget) {
scrollTarget.on("scroll", _onScroll);
}
if (mouseTarget) {
mouseTarget.once("mousedown", function (e) {
mouseEvent = e;
if (typeof onMouseDown == "function") {
onMouseDown.call(this, e);
}
});
}
document.on("mousemove", _onMouseMove);
document.once("mouseup", _onMouseUp);
}
});
define("editor/font_metrics", function (require, exports, module) {
let utils = require('../utils');
module.exports = class FontMetrics {
constructor(parent) {
this.measureNode = utils.buildElement({
tag: "pre",
style: {
position: "fixed",
left: 0,
top: 0,
opacity: 0,
pointerEvents: "none",
margin: 0,
padding: 0,
zIndex: -1
},
parent
});
this.sizes = {};
}
getCharaterWidth(char) {
if (!this.sizes[char]) {
this.measureNode.innerText = char;
this.sizes[char] = this.measureNode.getBoundingClientRect().width;
}
return this.sizes[char];
}
getTextWdith(text) {
let width = 0;
for (let char of text) {
width += this.getCharaterWidth(char);
}
return width;
}
setMeasureNodeStyle(style) {
let oldWidth = this.getCharaterWidth("x");
utils.setElementStyles(this.measureNode, style);
delete this.sizes['x'];
if (this.getCharaterWidth("x") != oldWidth) {
this.sizes = {};
}
}
}
});
define("editor/emitter", function (require, exports, module) {
module.exports = class Emitter {
constructor() {
this._eventRegistry = {};
}
on(type, listener) {
let listenerList = this._eventRegistry[type] || (this._eventRegistry[type] = []);
listenerList.push(listener);
}
off(type, listener) {
let listenerList = this._eventRegistry[type];
if (Array.isArray(listenerList)) {
let index = listenerList.indexOf(listener);
if (index != -1) {
listenerList.splice(index, 1);
}
}
}
once(type, listener) {
let $self = this;
function callback() {
listener.apply($self, Array.from(arguments));
$self.off(type, callback);
}
this.on(type, callback);
}
emit(type) {
let listenerList = this._eventRegistry[type];
if (Array.isArray(listenerList)) {
let args = Array.prototype.slice.call(arguments, 1);
listenerList.forEach(callback => {
callback.apply(this, args);
});
}
}
removeAllListeners(type) {
if (type && typeof type == "string" &&
type in this._eventRegistry) {
this._eventRegistry[type] = [];
} else {
this._eventRegistry = {};
}
}
}
});
define("editor/config", function (require, exports, module) {
module.exports = class Config {
constructor(renderer) {
this.renderer = renderer;
this.opts = {};
this.$toExec = {};
this.$pendding = false;
}
defineOption(key, desc) {
this.opts[key] = { ...desc };
if (desc.immediate && desc.handler) {
desc.handler.call(this, { key, oldValue: undefined, newValue: desc.value });
}
}
setOption(key, value) {
let desc = this.opts[key];
if (!desc) {
return;
}
let mutation = { key, newValue: value, oldValue: desc.value };
desc.value = value;
if (desc.handler && !desc.async) {
desc.handler.call(this, mutation);
} else if (desc.handler && desc.async) {
this.$toExec[key] = {
handler: desc.handler,
mutation
};
}
this._schedule();
}
getOption(key) {
let desc = this.opts[key];
if (!desc) {
return;
}
return desc.value;
}
_schedule() {
if (this.$pendding) {
return;
}
this.$pendding = true;
setTimeout(() => {
this._flush();
});
}
_flush() {
for (let key in this.$toExec) {
let handler = this.$toExec[key].handler;
let mutaion = this.$toExec[key].mutaion
handler.call(this, mutaion);
}
this.$pendding = false;
this.$toExec = {};
}
}
});
define("editor/config/default_options", function (require, exports, module) {
module.exports = function defaultOptions(config) {
config.defineOption("fontSize", {
value: 14,
async: true,
immediate: true,
handler(mutaion) {
let renderer = this.renderer;
let metrics = renderer.metrics;
let container = renderer.container;
metrics.setMeasureNodeStyle({
fontSize: mutaion.newValue + 'px'
});
container.style.fontSize = mutaion.newValue + 'px';
},
});
config.defineOption("fontFamily", {
value: "Consolas",
async: true,
immediate: true,
handler(mutaion) {
let renderer = this.renderer;
let metrics = renderer.metrics;
let container = renderer.container;
metrics.setMeasureNodeStyle({
fontFamily: mutaion.newValue
});
container.style.fontFamily = mutaion.newValue;
}
});
config.defineOption("startLine", {
value: 1,
async: true,
handler(mutaion) {
this.renderer.$gutter.update();
}
});
config.defineOption("lineHeight", {
value: 20,
async: true,
immediate: true,
handler(mutaion) {
let renderer = this.renderer;
let content = renderer.content;
let newLineHeight = mutaion.newValue + 'px';
content.style.lineHeight = newLineHeight;
renderer.$gutter.setLineHeight(newLineHeight);
}
});
config.defineOption("selectionBackground", {
value: "rgba(0,0,0,.3)",
async: true
});
config.defineOption("cursorColor", {
value: "#000",
async: true
});
config.defineOption("cursorWidth", {
value: 1,
async: true
});
return config;
}
});
define("editor/render_descriptor", function (require, exports, module) {
let utils = require('../utils');
exports.addDefaultDescriptor = function (loop) {
loop.addRenderDescriptor({
setData(arg) {
},
exec(renderer) {
renderer.$gutter.update();
},
name: "change_gutter"
});
loop.addRenderDescriptor({
setData(arg) {
},
exec: function(renderer) {
renderer.$marker.update();
renderer.$cursor.update();
if (renderer.$textInput.isFocus) {
renderer.$cursor.pause();
}
},
name: "change_selection"
});
loop.addRenderDescriptor({
setData(arg) {
},
exec(renderer) {
renderer.$text.update();
},
name: "change_text"
});
loop.addRenderDescriptor({
exec(renderer) {
renderer.$scrollbar.updateScrollHeight();
},
name: "change_scroll_height"
});
loop.addRenderDescriptor({
exec(renderer) {
renderer.$scrollbar.updateScrollWidth();
},
name: "change_scroll_width"
});
loop.addRenderDescriptor({
exec(renderer) {
renderer.scrollContentY();
},
name: "change_scroll_v"
});
loop.addRenderDescriptor({
exec(renderer) {
},
name: "change_scroll_h"
});
loop.addRenderDescriptor({
exec(renderer, types) {
if (!types["change_gutter"]) {
renderer.$gutter.update();
}
if (!types["change_selection"]) {
renderer.$marker.update();
renderer.$cursor.update();
}
if (!types["change_scroll_height"]) {
renderer.$scrollbar.updateScrollHeight();
}
if (!types["change_scroll_width"]) {
renderer.$scrollbar.updateScrollWidth();
}
if (!types["change_scroll_v"]) {
renderer.scrollContentY();
}
},
name: "change_full"
});
}
});
define("editor/renderloop", function (require, exports, module) {
module.exports = class RenderLoop {
constructor(renderer) {
this.renderer = renderer;
this.pendding = false;
this.changes = 0;
this.$desc = [];
this.flush = (function () {
let filter = this.getDescriptorByChanges(this.changes);
let changesMap = filter.toMap(function (type) {
return { key: type.name, value: type };
});
filter.forEach(type => {
if (type.exec instanceof Function) {
type.exec(this.renderer, changesMap);
}
});
this.pendding = false;
this.changes = 0;
}).bind(this);
}
schedule(changes) {
if (typeof changes == "string") {
changes = this.getDescriptorByName(changes);
} else if (typeof changes != "number") {
return;
}
this.changes |= changes;
let args = Array.prototype.slice.call(arguments, 1);
this.getDescriptorByChanges(changes).forEach(type => {
if (typeof type.setData instanceof Function) {
type.setData.apply(type, args);
}
});
if (this.pendding) {
return;
}
this.pendding = true;
requestAnimationFrame(this.flush);
}
addRenderDescriptor(desc) {
this.$desc.push(desc);
}
getDescriptorByName(name) {
let changes = 0;
this.$desc.find((t, index) => {
if (t.name == name) {
changes = 2 ** index;
return true;
}
});
return changes;
}
getDescriptorByChanges(changes) {
return this.$desc.filter((_, index) => {
return changes & (2 ** index);
});
}
}
});
define("editor/mouse_handler", function (require, exports, module) {
let Range = require('../range');
let utils = require('../utils');
module.exports = class MouseHandler {
constructor(renderer) {
this.renderer = renderer;
this.session = renderer.session;
this.mouseEvent = null;
this.mouseDownEvent = null;
this.activeRange = null;
let _self = this;
let container = renderer.container;
let scroller = renderer.scroller;
let textInput = renderer.$textInput;
let selection = this.session.selection;
let loop = renderer.$loop;
container.on("mousedown", function (e) {
e.preventDefault();
textInput.focus();
});
container.on("wheel", function (e) {
renderer.$scrollbar.scrollVBy(e.deltaY);
});
scroller.on("mousedown", function (e) {
_self.startSelect(e);
});
textInput.on("keydown", function (e) {
(e.altKey || e.ctrlKey) && e.preventDefault();
});
selection.on("change", function () {
loop.schedule("change_selection");
});
}
startSelect(e) {
if (e.button != 0 && e.button != 1) {
return;
}
let _self = this;
let selection = this.session.selection;
let renderer = this.renderer;
let pos = renderer.screenToDocumentCoordinates(e.clientX, e.clientY);
this.mouseEvent = this.mouseDownEvent = e;
if (e.shiftKey) {
selection.moveSelectionHead(pos.row,pos.column);
} else {
selection.moveTo(pos.row,pos.column);
}
let mouseSelectChanged = true;
function onSelectionInterval() {
if (mouseSelectChanged) {
let event = _self.mouseEvent;
let mouseDownEvent = _self.mouseDownEvent;
if (event.altKey || (mouseDownEvent && mouseDownEvent.button == 1)) {
_self.blockSelect(event);
} else if (event.ctrlKey) {
_self.multiSelect(event);
} else {
_self.singleSelect(event);
}
mouseSelectChanged = false;
}
}
let interval = setInterval(onSelectionInterval, 15);
function callback(e) {
let pos = renderer.screenToDocumentCoordinates(e.clientX, e.clientY);
_self.mouseEvent = e;
selection.moveSelectionHead(pos.row, pos.column);
mouseSelectChanged = true;
}
function onMouseSelectionEnd() {
clearInterval(interval);
onSelectionInterval();
_self.activeRange = null;
}
this.captureMouse(null, null, callback, onMouseSelectionEnd, renderer.$scrollbar, callback);
}
blockSelect(e) {
let renderer = this.renderer;
let selection = this.session.selection;
let startPos = selection.anchor;
let endPos = renderer.screenToDocumentCoordinates(e.clientX, e.clientY);
let isBackwards = Range.comparePoints(startPos, endPos) > 0;
let startRow = isBackwards ? endPos.row : startPos.row;
let endRow = isBackwards ? startPos.row : endPos.row;
for (let i = startRow; i <= endRow; ++i) {
if ((i - startRow + 1) > selection.MAX_RANGE_COUNT) {
break;
}
let rangeIndex = i - startRow;
let range = selection.getRangeAt(rangeIndex);
let contains = Boolean(range);
let column = renderer.screenToRowCoordinate(i, e.clientX);
if (!contains) {
range = new Range(i, startPos.column, i, column);
selection.addRange(range);
}
if (contains) {
range.setStart(i, startPos.column);
range.setEnd(i, column);
}
}
selection.limitRangeCount(endRow - startRow + 1);
renderer.scrollCursorIntoView();
}
singleSelect(e) {
let pos = this.renderer.screenToDocumentCoordinates(e.clientX, e.clientY);
this.session.selection.selectTo(pos.row, pos.column);
this.renderer.scrollCursorIntoView();
}
multiSelect(e) {
let renderer = this.renderer;
let selection = this.session.selection;
let pos = renderer.screenToDocumentCoordinates(e.clientX, e.clientY);
let activeRange = this.activeRange;
if (!activeRange) {
let toDelete = selection.getRangesFromPoint(pos);
if (toDelete.length) {
for (let range of toDelete) {
if (selection.rangeCount() < 2) {
break;
}
selection.removeRange(range);
}
} else {
let anchor = selection.anchor;
let head = selection.head;
let range = new Range(anchor.row, anchor.column, head.row, head.column);
selection.addRange(range);
this.activeRange = range;
}
} else if (activeRange instanceof Range) {
activeRange.setEnd(pos.row, pos.column);
let result = selection.merge(activeRange.end.dir);
for (let item of result) {
if (item.removed == activeRange) {
this.activeRange = item.range;
break;
}
}
renderer.scrollCursorIntoView();
}
}
captureMouse(mouseTarget, onMouseDown, onMouseMove, onMouseUp, scrollTarget, onScroll) {
let mouseEvent = null;
function _onMouseMove(e) {
mouseEvent = e;
if (typeof onMouseMove == "function") {
onMouseMove.call(this, e);
}
}
function _onMouseUp(e) {
document.off("mousemove", _onMouseMove);
if (typeof onMouseUp == "function") {
onMouseUp.call(this, e);
}
if (scrollTarget) {
scrollTarget.off("scroll", _onScroll);
}
mouseEvent = null;
}
function _onScroll(e) {
if (typeof onScroll == "function") {
onScroll.call(this, mouseEvent, e);
}
}
if (scrollTarget) {
scrollTarget.on("scroll", _onScroll);
}
if (mouseTarget) {
mouseTarget.once("mousedown", function (e) {
mouseEvent = e;
if (typeof onMouseDown == "function") {
onMouseDown.call(this, e);
}
});
}
document.on("mousemove", _onMouseMove);
document.once("mouseup", _onMouseUp);
}
}
});
define("editor/keyboard_handler", function (require, exports, module) {
module.exports = class KeyboardHandler {
constructor(renderer) {
this.renderer = renderer;
this.session = renderer.session;
let $textInput = renderer.$textInput,
selection = this.session.selection;
$textInput.on("keydown", function (e) {
let isShift = e.shiftKey;
let isControl = e.ctrlKey;
let key = e.key;
if (key == "ArrowLeft") {
if (isShift) {
selection.selectLeft();
} else {
selection.moveCursorLeft();
}
} else if (key == "ArrowRight") {
if (isShift) {
selection.selectRight();
} else {
selection.moveCursorRight();
}
} else if (key == "ArrowUp" && isShift) {
selection.selectUp();
} else if (key == "ArrowDown" && isShift) {
selection.selectDown();
} else if (key == "a" && isControl) {
selection.selectAll();
}
});
}
}
});
define("editor/default_handler", function (require, exports, module) {
let MouseHandler = require('../mouse_handler');
let KeyboardHandler = require('../keyboard_handler');
let utils = require('../utils');
module.exports = class DefaultHandler {
constructor(renderer) {
this.renderer = renderer;
this.session = renderer.session;
this.$mouseHandler = new MouseHandler(renderer);
this.$keyboardHandler = new KeyboardHandler(renderer);
let $scrollbar = renderer.$scrollbar,
$textInput = renderer.$textInput,
$loop = renderer.$loop,
$cursor = renderer.$cursor;
$scrollbar.on("scrollV", function () {
$loop.schedule("change_scroll_v");
});
$textInput.on("blur", function () {
$cursor.stop();
$cursor.layer.setStyles({
opacity: 0.5
});
});
$textInput.on("focus", function () {
$cursor.start();
$cursor.layer.setStyles({
opacity: 1
});
});
this.session.doc.on("change", function (e) {
console.log(e);
$loop.schedule("change_text");
});
}
}
});
define("editor/renderer", function (require, exports, module) {
let FontMetrics = require('../font_metrics');
let Config = require('../config');
let defaultOptions = require('../config/default_options');
let Cursor = require('../layer/cursor');
let Gutter = require('../layer/gutter');
let Marker = require('../layer/marker');
let Text = require('../layer/text');
let ScrollBar = require('../layer/scrollbar');
let TextInput = require('../text_input');
let RenderLoop = require('../renderloop');
let addDefaultDescriptor = require('../render_descriptor').addDefaultDescriptor;
let Emitter = require('../emitter');
let DefaultHandler = require('../default_handler');
module.exports = class Renderer extends Emitter {
constructor(parent, session) {
super();
let $self = this;
this.session = session;
this.container = document.buildElement({
tag: "div",
parent,
clazz: "editor_container"
});
this.metrics = new FontMetrics(this.container);
this.$gutter = new Gutter(this.container, this);
this.$gutter.on("resize", function (_, width) {
$self.scroller.setStyles({
left: width + 'px',
width: `calc(100% - ${width}px)`
});
$self.$scrollbar.hscrollbar.setStyles({
left: width + 'px',
width: `calc(100% - ${width}px)`
})
});
this.scroller = document.buildElement({
tag: "div",
parent: this.container,
clazz: "scroller"
});
this.content = document.buildElement({
tag: "div",
clazz: "content",
parent: this.scroller
});
this.$marker = new Marker(this.content, this);
this.$text = new Text(this.content, this);
this.$cursor = new Cursor(this.content, this);
this.$scrollbar = new ScrollBar(this.container, this);
this.$textInput = new TextInput(this.scroller, this);
this.config = defaultOptions(new Config(this));
this.$linesWidth = [];
this.$loop = new RenderLoop(this);
addDefaultDescriptor(this.$loop);
this.$handler = new DefaultHandler(this);
this.$loop.schedule("change_full");
}
update() {
this.$loop.schedule("change_full");
}
getScreenRows() {
let lineHeight = this.config.getOption("lineHeight");
let scrollTop = this.$scrollbar.getScrollTop();
let start = Math.floor(scrollTop / lineHeight);
let height = this.scroller.getBoundingClientRect().height;
let lineCount = this.session.doc.lineCount();
let to = Math.min(Math.floor((scrollTop + height) / lineHeight), Math.max(0, lineCount - 1));
return { start, to };
}
getScreenTextLines() {
let screenRows = this.getScreenRows();
return this.session.doc.getLines(screenRows.start, screenRows.to + 1);
}
scrollContentY() {
this.$gutter.update();
this.$marker.update();
this.$text.update();
this.$cursor.update();
this.scrollContentToScrollOffsetY();
}
screenToDocumentCoordinates(x, y) {
let rect = this.scroller.getBoundingClientRect();
let scrollTop = this.$scrollbar.getScrollTop();
let scrollLeft = this.$scrollbar.getScrollLeft();
let clipX = x - rect.x + scrollLeft;
let clipY = y - rect.y + scrollTop;
let posX = clipX < 0 ? 0 : clipX;
let posY = clipY < 0 ? 0 : clipY;
let lineHeight = this.config.getOption("lineHeight");
let row = Math.min(Math.floor(posY / lineHeight), Math.max(this.session.doc.lineCount() - 1, 0));
let column = 0;
let line = this.session.doc.getLine(row);
let width = 0;
for (; column < line.length; ++column) {
let boundLeft = width;
let charWidth = this.metrics.getTextWdith(line[column]);
let boundRight = boundLeft + charWidth;
width += charWidth;
if (posX >= boundLeft && posX <= boundRight) {
if (Math.round((posX - boundLeft) / charWidth)) {
column++;
}
break;
}
}
return { row, column };
}
screenToTextCoordinate(line, x) {
let column = 0;
let width = 0;
x = x < 0 ? 0 : x;
for (; column < line.length; ++column) {
let boundLeft = width;
let charWidth = this.measureText(line[column]);
let boundRight = boundLeft + charWidth;
width += charWidth;
if (x >= boundLeft && x <= boundRight) {
if (Math.round((x - boundLeft) / charWidth)) {
column++;
}
break;
}
}
return column;
}
screenToRowCoordinate(row, x) {
let rect = this.scroller.getBoundingClientRect();
let left = x - rect.left + this.$scrollbar.getScrollLeft();
let line = this.session.doc.getLine(row);
if (typeof line != "string") {
return;
}
return this.screenToTextCoordinate(line, left);
}
measureText(text) {
return this.metrics.getTextWdith(text);
}
getRowTextWidth(row) {
if (typeof this.$linesWidth[row] != "number") {
let line = this.session.doc.getLine(row);
if (typeof line === "string") {
this.$linesWidth[row] = this.measureText(line);
}
}
return this.$linesWidth[row];
}
isPointInScreen(position) {
let screenRows = this.getScreenRows();
return position.row >= screenRows.start && position.row <= screenRows.to;
}
getFullyVisibleScreenRow() {
let lineHeight = this.config.getOption("lineHeight");
let scrollTop = this.$scrollbar.getScrollTop();
let remain = parseInt(scrollTop % lineHeight);
let start = remain ? Math.ceil(scrollTop / lineHeight) : Math.floor(scrollTop / lineHeight);
let height = this.scroller.getBoundingClientRect().height;
let lineCount = this.session.doc.lineCount();
let to = Math.min(Math.floor((scrollTop + height - remain) / lineHeight), Math.max(0, lineCount - 1));
return { start, to };
}
scrollContentToScrollOffsetY() {
let lineHeight = this.config.getOption('lineHeight');
let scrollTop = this.$scrollbar.getScrollTop();
let offset = -(scrollTop % lineHeight) + 'px';
this.content.setStyles({
top: offset
})
this.$gutter.updateScrollOffset();
}
scrollVBy(delta) {
this.$scrollbar.scrollVBy(delta);
}
scrollBy(x, y) {
}
scrollCursorIntoView() {
let head = this.session.selection.head;
let row = head.row;
let lineHeight = this.config.getOption("lineHeight");
let top = row * lineHeight;
let bottom = top + lineHeight;
let scrollTop = this.$scrollbar.getScrollTop();
let scrollBottom = this.scroller.getBoundingClientRect().height + scrollTop;
let remain = Math.floor(scrollTop % lineHeight);
let boundTop = scrollTop + lineHeight - remain;
let boundBottom = scrollBottom - lineHeight + remain;
if (top > boundTop && bottom < boundBottom) {
return;
}
if (top <= boundTop) {
let offset = -(boundTop - top);
this.$scrollbar.scrollVBy(offset);
} else if (bottom >= boundBottom) {
let offset = bottom - boundBottom + remain;
this.$scrollbar.scrollVBy(offset);
}
}
}
});
define("editor/layer/gutter", function (require, exports, module) {
let Emitter = require('../../emitter');
module.exports = class Gutter extends Emitter {
constructor(parent, renderer) {
super();
this.renderer = renderer;
this.el = document.buildElement({
tag: "div",
parent,
clazz: "gutter"
});
this.layer = document.buildElement({
tag: "div",
parent: this.el,
clazz: "gutter_layer"
});
this.width = 0;
}
drawLineNumber(start, to) {
let oldNodes = this.layer.extractChildElement();
let config = this.renderer.config;
for (let i = start; i <= to; ++i) {
let childIndex = i - start;
let child = oldNodes[childIndex];
if (!child) {
child = document.buildElement({
tag: "div",
clazz: "line_number"
});
oldNodes.push(child);
}
child.innerHTML = i + config.getOption("startLine");
}
this.layer.replaceChildren.apply(this.layer, oldNodes);
}
resize() {
let computedWidth = this.getComputedWidth();
if (computedWidth != this.width) {
let oldWidth = this.width;
this.width = computedWidth;
this.el.setStyles({ width: computedWidth + 'px' });
this.emit("resize", oldWidth, computedWidth);
}
}
getComputedWidth() {
let doc = this.renderer.session.doc;
let config = this.renderer.config;
let metrics = this.renderer.metrics;
let lineCount = doc.lineCount();
let startLine = config.getOption("startLine");
let measureText = (lineCount - 1 + startLine).toString();
return metrics.getTextWdith(measureText) + 25;
}
update() {
let screenRows = this.renderer.getScreenRows();
this.drawLineNumber(screenRows.start, screenRows.to);
this.resize();
this.updateScrollOffset();
}
setLineHeight(height) {
if (typeof height == 'number') {
height += "px";
}
this.el.setStyles({ lineHeight: height });
}
$onScroll(scrollTop) {
let config = this.renderer.config;
let lineHeight = config.getOption('lineHeight');
let offset = -(scrollTop % lineHeight) + 'px';
this.layer.setStyles({ top: offset });
}
updateScrollOffset() {
this.$onScroll(this.renderer.$scrollbar.getScrollTop());
}
}
});
define("editor/layer/cursor", function (require, exports, module) {
module.exports = class Cursor {
constructor(parent, renderer) {
this.renderer = renderer;
this.session = renderer.session;
this.layer = document.buildElement({
tag: "div",
clazz: ["cursor", "layer"],
parent
});
this.timer = null;
this.visible = true;
this.interval = 700;
}
start() {
clearInterval(this.timer);
this.timer = setInterval(() => {
this.toggle();
}, this.interval);
}
stop() {
clearInterval(this.timer);
this.show();
}
toggle() {
if (this.visible) {
this.hide();
this.visible = false;
} else {
this.show();
this.visible = true;
}
}
hide() {
this.layer.style.opacity = 0;
this.visible = false;
}
show() {
this.layer.style.opacity = 1;
this.visible = true;
}
pause() {
this.stop();
this.show();
this.start();
}
update() {
let ranges = this.session.selection.ranges;
let oldNodes = this.layer.extractChildElement();
let childIndex = 0;
let children = [];
let screenRows = this.renderer.getScreenRows();
let config = this.renderer.config;
for (let i = 0; i < ranges.length; ++i) {
let range = ranges[i];
if (!this.renderer.isPointInScreen(range.end)) {
continue;
}
let endRow = range.end.row;
let endColumn = range.end.column;
let color = config.getOption("cursorColor");
let line = this.session.doc.getLine(endRow);
let lineHeight = config.getOption("lineHeight");
let child = oldNodes[childIndex++];
let left = this.renderer.measureText(line.slice(0, endColumn));
let top = (endRow - screenRows.start) * lineHeight;
let width = config.getOption("cursorWidth");
if (!child) {
child = document.buildElement({
tag: "div",
clazz: "cursor_caret"
});
}
children.push(child);
child.setStyles({
height: lineHeight + 'px',
position: "absolute",
top: top + 'px',
left: left + 'px',
border: `${width}px solid ${color}`,
boxSizing: "border-box"
})
}
this.layer.replaceChildren.apply(this.layer, children);
}
}
});
define("editor/layer/marker", function (require, exports, module) {
module.exports = class Marker {
constructor(parent, renderer) {
this.renderer = renderer;
this.session = renderer.session;
this.layer = document.buildElement({
tag: "div",
clazz: ["marker", "layer"],
parent
});
}
update() {
let screenRows = this.renderer.getScreenRows();
let clippedRanges = this.session.selection.clipRange(screenRows.start, screenRows.to + 1);
let lineIndex = 0;
let oldNodes = this.layer.extractChildElement();
let children = [];
let rect = [];
let config = this.renderer.config;
for (let i = 0; i < clippedRanges.length; ++i) {
let range = clippedRanges[i];
for (let j = range.start.row; j <= range.end.row; ++j) {
let startRow = range.start.row;
let startColumn = range.start.column;
let endRow = range.end.row;
let endColumn = range.end.column;
let isStart = j == startRow;
let isEnd = j == endRow;
let isSame = startRow == endRow;
let lineHeight = config.getOption("lineHeight");
let width = 0;
let height = lineHeight;
let left = 0;
let top = lineHeight * (j - screenRows.start);
let line = this.session.doc.getLine(j);
let background = config.getOption("selectionBackground");
if (isSame) {
left = this.renderer.measureText(line.slice(0, startColumn));
width = this.renderer.measureText(line.slice(startColumn, endColumn));
} else if (isStart) {
left = this.renderer.measureText(line.slice(0, startColumn));
width = this.renderer.getRowTextWidth(startRow) - left;
} else if (isEnd) {
left = 0;
width = this.renderer.measureText(line.slice(0, endColumn));
} else {
left = 0;
width = this.renderer.getRowTextWidth(j);
}
if (!isEnd) {
width += this.renderer.measureText("x");
}
let child = oldNodes[lineIndex++];
if (!child) {
child = document.buildElement({
tag: "div",
clazz: "marker_line"
});
}
child.setStyles({
width: width + 'px',
height: height + 'px',
left: left + 'px',
top: top + 'px',
position: "absolute",
background
});
children.push(child);
rect.push({
el: child,
left,
right: left + width
});
}
this.$markerCornerToRounded(rect);
rect = [];
}
this.layer.replaceChildren.apply(this.layer, children);
}
$markerCornerToRounded(rect) {
rect.forEach((item, index) => {
let prev = rect[index - 1];
let next = rect[index + 1];
let el = item.el;
if (prev) {
if ((prev.left > item.left || prev.right < item.left) && item.left) {
el.addClass("btlr");
} else {
el.removeClass("btlr");
}
if (prev.left > item.right || prev.right < item.right) {
el.addClass("btrr");
} else {
el.removeClass("btrr");
}
} else {
el.addClass("btrr");
if (item.left) {
el.addClass("btlr");
} else {
el.removeClass("btlr");
}
}
if (next) {
if ((next.left > item.left || next.right < item.left) && item.left) {
el.addClass("bblr");
} else {
el.removeClass("bblr");
}
if (next.right < item.right) {
el.addClass("bbrr");
} else {
el.removeClass("bbrr");
}
} else {
el.addClass("bbrr");
}
});
}
}
});
define("editor/layer/text", function (require, exports, module) {
module.exports = class Text {
constructor(parent, renderer) {
this.renderer = renderer;
this.session = renderer.session;
this.layer = document.buildElement({
tag: "div",
clazz: ["text", "layer"],
parent
});
}
update() {
let screenLines = this.renderer.getScreenTextLines();
let oldNodes = this.layer.extractChildElement();
for (let i = 0; i < screenLines.length; ++i) {
let child = oldNodes[i];
if (!child) {
child = document.buildElement({
tag: "div",
clazz: 'text_line'
});
oldNodes.push(child);
}
child.innerHTML = screenLines[i].replace(/ /g, " ");
child.style.height = this.renderer.config.getOption("lineHeight") + 'px';
}
this.layer.replaceChildren.apply(this.layer, oldNodes);
}
}
});
define("editor/layer/scrollbar", function (require, exports, module) {
let Emitter = require('../../emitter');
module.exports = class Scrollbar extends Emitter {
constructor(parent, renderer) {
super();
let $self = this;
this.renderer = renderer;
this.session = renderer.session;
this.layer = document.buildElement({
tag: "div",
clazz: "scrollbar",
parent
});
this.vscrollbar = document.buildElement({
tag: "div",
clazz: "vsc",
parent: this.layer
});
this.hscrollbar = document.buildElement({
tag: "div",
clazz: "hsc",
parent: this.layer
});
this.vscrollbarHeight = document.buildElement({
tag: "div",
parent: this.vscrollbar,
style: {
width: '1px'
}
});
this.hscrollbarWidth = document.buildElement({
tag: "div",
parent: this.hscrollbar,
style: {
height: '1px'
}
});
this.scrollTop = 0;
this.scrollLeft = 0;
this.vscrollbar.on("scroll", function (e) {
let old = $self.scrollTop;
$self.scrollTop = $self.getScrollTop();
$self.emit("scroll", e);
$self.emit("scrollV", e, old, $self.scrollTop);
});
this.hscrollbar.on("scroll", function (e) {
$self.emit("scroll", e);
$self.emit("scrollH", e);
});
}
setScrollHeight(height) {
this.vscrollbarHeight.setStyles({
height: typeof height == "number" ? height + 'px' : height
});
}
setScrollWidth(width) {
this.hscrollbarWidth.setStyles({
width: typeof width == "number" ? width + 'px' : width
});
}
getScrollTop() {
return this.vscrollbar.scrollTop;
}
getScrollLeft() {
return this.hscrollbar.scrollLeft;
}
updateScrollHeight() {
let config = this.renderer.config;
let height = this.session.doc.lineCount() * config.getOption("lineHeight");
this.setScrollHeight(height);
}
updateScrollWidth() {
}
scrollV(scrollTop) {
this.vscrollbar.scroll(0, scrollTop);
}
scrollVBy(delta) {
this.vscrollbar.scrollBy(0, delta);
}
}
});
define("editor/text_input", function (require, exports, module) {
let Emitter = require('../emitter');
module.exports = class TextInput extends Emitter {
constructor(parent, renderer) {
super();
let $self = this;
this.isFocus = false;
this.renderer = renderer;
this.textarea = document.buildElement({
tag: "textarea",
parent,
style: {
position: "absolute",
left: 0,
top: 0,
opacity: 0,
border: "none",
outline: "none",
margin: 0,
padding: 0,
width: "1px",
height: "1px",
fontSize: "1px",
resize: "none"
}
});
this.textarea.on("focus", function (e) {
$self.isFocus = true;
$self.emit("focus", e);
});
this.textarea.on("blur", function (e) {
$self.isFocus = false;
$self.emit("blur", e);
});
this.textarea.on("keydown", function (e) {
$self.emit("keydown", e);
});
this.textarea.on("keyup", function (e) {
$self.emit("keyup", e);
});
this.isFocus = document.activeElement == this.textarea;
}
focus(opt) {
this.textarea.focus(opt);
}
blur() {
this.textarea.blur();
}
moveToCursor() {
}
}
});
define("editor/doc", function (require, exports, module) {
let Emitter = require('../emitter');
let Range = require('../range');
module.exports = class Doc extends Emitter {
constructor(text) {
super();
this.lines = [""];
if (typeof text == "string") {
this.lines = text.split("\n");
}
}
lineCount() {
return this.lines.length;
}
getLines(start, end) {
return this.lines.slice(start, end);
}
iter(callback) {
this.lines.forEach(callback);
}
getLine(row) {
return this.lines[row];
}
insert(row, column, lines) {
let clippedPos = this.clipPositionToDocument(row, column);
let line = this.getLine(clippedPos.row);
this.lines.splice(clippedPos.row, 1, ...lines);
this.lines[clippedPos.row] = line.slice(0, clippedPos.column) + this.lines[clippedPos.row];
this.lines[clippedPos.row + lines.length - 1] += line.slice(clippedPos.column);
this.emit("change", {
action: "insert",
row: clippedPos.row,
column: clippedPos.column,
lines
});
}
removeLeft(row, column) {
let startRow = row;
let startColumn = column;
if (column == 0) {
let line = this.getLine(--row) || "";
column = line.length;
}
this.remove(startRow, startColumn, row, column);
}
removeRight(row, column) {
let startRow = row;
let startColumn = column;
let line = this.getLine(row) || "";
if (startColumn >= line.length) {
startColumn = 0;
++row;
}
this.remove(startRow, startColumn, row, column);
}
remove(startRow, startColumn, endRow, endColumn) {
let clipStart = this.clipPositionToDocument(startRow, startColumn);
let clipEnd = this.clipPositionToDocument(endRow, endColumn);
let isBackwards = Range.comparePoints(clipStart, clipEnd) > 0;
let startLine = this.getLine(clipStart.row);
let endLine = this.getLine(clipEnd.row);
let _startRow = isBackwards ? clipEnd.row : clipStart.row;
let _endRow = isBackwards ? clipStart.row : clipEnd.row;
let _startColumn = isBackwards ? clipEnd.column : clipStart.column;
let _endColumn = isBackwards ? clipStart.column : clipEnd.column;
let deleteCount = _endRow - _startRow + 1;
let insertLine = startLine.slice(0, _startColumn) + endLine.slice(_endColumn);
let removed = this.lines.splice(_startRow, deleteCount, insertLine);
this.emit("change", {
action: "remove",
startRow,
startColumn,
endRow,
endColumn,
removedLines: removed
});
}
clipPositionToDocument(row, column) {
let pos = row instanceof Object ? { ...row } : { row, column };
let maxRow = Math.max(0, this.lineCount() - 1);
if (pos.row < 0) {
pos.row = 0;
pos.column = 0;
} else if (pos.row > maxRow) {
pos.row = maxRow;
pos.column = this.getLine(maxRow).length;
} else {
let line = this.getLine(pos.row);
pos.column = Math.min(line.length, Math.max(pos.column, 0));
}
return pos;
}
}
});
define("editor/session", function (require, exports, module) {
let Doc = require('../doc');
let Selection = require('../selection');
let Range = require('../range');
module.exports = class Session {
constructor() {
this.doc = new Doc(`define("editor/fontMetrics", function (require, exports, module) {
let dom = require('../lib/dom');
module.exports = class FontMetrics {
\t\tconstructor(parent) {
this.measureNode = document.buildElement({
tag: "pre",
style: {
position: "fixed",
left: 0,
top: 0,
opacity: 0,
pointerEvents: "none",
margin: 0,
padding: 0,
zIndex: -1
},
parent
});
this.sizes = {};
}
getCharaterWidth(char) {
if (!this.sizes[char]) {
this.measureNode.innerText = char;
this.sizes[char] = this.measureNode.getBoundingClientRect().width;
}
return this.sizes[char];
}
getTextWdith(text) {
let width = 0;
for (let char of text) {
width += this.getCharaterWidth(char);
}
return width;
}
setStyle(style) {
dom.setStyle(this.measureNode, style);
this.sizes = {};
}
}
});\n`.repeat(500));
this.selection = new Selection(this);
}
insertText(text) {
let lines = text.split("\n");
let sel = this.selection;
let doc = this.doc;
this.removeSelected();
}
removeLeft() {
}
removeRight() {
}
removeSelected() {
let sel = this.selection;
let doc = this.doc;
sel.ranges.forEach(range => {
if (!range.isCollapsed()) {
let start = range.start;
let end = range.end;
doc.remove(start.row, start.column, end.row, endColumn);
moveRange(range, sel.ranges, "remove");
range.collapse();
}
});
}
}
});
define("editor/position", function (require, exports, module) {
let Emitter = require('../emitter');
function comparePoints(p1, p2) {
let value = p1.row - p2.row || p1.column - p2.column;
if (value < -1) {
value = -1;
}
if (value > 1) {
value = 1;
}
return value;
}
module.exports = class Position extends Emitter {
constructor(row, column) {
super();
this.row = row;
this.column = column;
this.dir = "";
}
setPosition(row, column) {
if (row == this.row && column == this.column) {
this.dir = "";
return;
}
let p1 = { row, column };
let p2 = { row: this.row, column: this.column };
let result = comparePoints(p1, p2);
if (result < 0) {
this.dir = "backward";
} else if (result > 0) {
this.dir = "forward";
}
let mutaion = {
oldPos: {
row: this.row,
column: this.column
},
newPos: {
row,
column
},
target: this
};
this.row = row;
this.column = column;
this.emit("change", mutaion);
}
}
});
define("editor/range", function (require, exports, module) {
let Emitter = require('../emitter');
let Position = require('../position');
let utils = require('../utils');
class Range extends Emitter {
constructor(startRow, startColumn, endRow, endColumn) {
super();
let $self = this;
this.start = new Position(startRow, startColumn);
this.end = new Position(endRow, endColumn);
this._onPositionChanged = function () {
if (this == $self.start || this == $self.end) {
$self.emit("change");
}
}
this.start.on("change", this._onPositionChanged);
this.end.on("change", this._onPositionChanged);
}
setStart(row, column) {
this.start.setPosition(row, column);
}
setEnd(row, column) {
this.end.setPosition(row, column);
}
moveTo(row, column) {
this.setStart(row, column);
this.setEnd(row, column);
}
isBackwards() {
return utils.comparePoints(this.end, this.start) < 0;
}
collapseToStart() {
this.end.setPosition(this.start.row, this.start.column);
}
collapseToEnd() {
this.start.setPosition(this.end.row, this.end.column);
}
collapse() {
if (this.isBackwards()) {
this.collapseToEnd();
} else {
this.collapseToStart();
}
}
toString() {
return "start: " + this.start.row + " " + this.start.column +
" end: " + this.end.row + " " + this.end.column;
}
isCollapsed() {
return utils.comparePoints(this.start, this.end) == 0;
}
isMultiLine() {
return Boolean(this.start.row - this.end.row);
}
forward() {
return utils.setRangeForward(this);
}
backward() {
return utils.setRangeBackward(this);
}
compare(point) {
return utils.comparePoint(point, this);
}
destroy() {
this.start.off("change", this._onPositionChanged);
this.end.off("change", this._onPositionChanged);
this.removeAllListeners();
}
}
Range.comparePoints = utils.comparePoints;
module.exports = Range;
});
define("editor/selection", function (require, exports, module) {
let Emitter = require('../emitter');
let Position = require('../position');
let Range = require('../range');
module.exports = class Selection extends Emitter {
constructor(session) {
super();
this.session = session;
this.doc = session.doc;
this.ranges = [];
this.anchor = new Position(0, 0);
this.head = new Position(0, 0);
this.MAX_RANGE_COUNT = 5000;
let $self = this;
this._onRangeUpdate = function () {
if (this instanceof Range) {
$self.clipRangeToDocument(this);
$self.emit("change");
}
}
this._onPositionUpdate = function () {
if (this instanceof Position) {
let pos = $self.doc.clipPositionToDocument(this.row, this.column);
this.row = pos.row;
this.column = pos.column;
}
}
this.anchor.on("change", this._onPositionUpdate);
this.head.on("change", this._onPositionUpdate);
this.on("change", function () {
if (this.rangeCount() > this.MAX_RANGE_COUNT) {
this.limitRangeCount(this.MAX_RANGE_COUNT);
}
});
}
addRange(range) {
range.on("change", this._onRangeUpdate);
this.ranges.push(range);
this.merge("forward");
this.emit("change");
}
containsRange(range) {
return this.indexOfRange(range) != -1;
}
indexOfRange(range) {
return this.ranges.indexOf(range);
}
removeRange(range) {
this.removeAt(this.indexOfRange(range));
}
removeAt(i) {
if (this.getRangeAt(i) instanceof Range) {
let removedRange = this.ranges.splice(i, 1)[0];
removedRange.off("change", this._onRangeUpdate);
this.emit("change");
return removedRange;
}
}
getRangeAt(i) {
return this.ranges[i];
}
rangeCount() {
return this.ranges.length;
}
merge(dir) {
let rangeList = this.ranges.sort(Range.comparePoints);
let result = [];
for (let i = 0; ;) {
let range = rangeList[i];
let next = rangeList[i + 1];
if (!range || !next) {
break;
}
let endPoint = range.isBackwards() ? range.start : range.end;
let startPoint = next.isBackwards() ? next.end : next.start;
if (Range.comparePoints(startPoint, endPoint) < 0) {
let end = next.isBackwards() ? next.start : next.end;
if (range.compare(end) > 0) {
if (range.isBackwards()) {
range.setStart(end.row, end.column);
} else {
range.setEnd(end.row, end.column);
}
}
switch (dir) {
case "forward":
range.forward();
break;
case "backward":
range.backward();
break;
}
this.removeRange(next);
result.push({ range, removed: next });
} else {
++i;
continue;
}
}
return result;
}
clipRange(start, to) {
let clipped = [];
for (let i = 0; i < this.ranges.length; ++i) {
let range = this.ranges[i];
let isBack = range.isBackwards();
let startPoint = isBack ? range.end : range.start;
let endPoint = isBack ? range.start : range.end;
if (endPoint.row < start || startPoint.row > to) {
continue;
}
let startRow = startPoint.row < start ? start : startPoint.row;
let endRow = endPoint.row > to ? to : endPoint.row;
let startColumn = startPoint.row < start ? 0 : startPoint.column;
let endLine = this.session.doc.getLine(endPoint.row);
let endColumn = endPoint.row > to ? endLine.length : endPoint.column;
let clippedRange = new Range(startRow, startColumn, endRow, endColumn);
clipped.push(clippedRange);
}
return clipped;
}
limitRangeCount(i) {
while (this.rangeCount() && this.rangeCount() > i) {
this.removeAt(this.rangeCount() - 1);
}
}
clipRangeToDocument(range) {
let start = this.doc.clipPositionToDocument(range.start);
let end = this.doc.clipPositionToDocument(range.end);
range.start.row = start.row;
range.start.column = start.column;
range.end.row = end.row;
range.end.column = end.column;
return range;
}
moveTo(row, column) {
this.anchor.setPosition(row, column);
this.moveSelectionHead(row, column);
}
moveSelectionRange(startRow, startColumn, endRow, endColumn) {
this.moveSelectionAnchor(startRow, startColumn);
this.moveSelectionHead(endRow, endColumn);
}
moveSelectionHead(row, column) {
this.head.setPosition(row, column);
}
moveSelectionAnchor(row, column) {
this.anchor.setPosition(row, column);
}
getRangesFromPoint(row, column) {
let p = row instanceof Object ? row : { row, column };
return this.ranges.filter(range => {
return range.compare(p) == 0;
});
}
isRangeOverPoint(point) {
for (let i = 0; i < this.ranges.length; ++i) {
let range = this.getRangeAt(i);
if (range.compare(point) == 0) {
return true;
}
}
return false;
}
select(startRow, startColumn, endRow, endColumn) {
this.moveSelectionRange(startRow, startColumn, endRow, endColumn);
let range = this.getRangeAt(0);
if (!range) {
range = new Range(0, 0, 0, 0);
this.addRange(range);
}
range.setStart(startRow, startColumn);
range.setEnd(endRow, endColumn);
this.limitRangeCount(1);
}
selectTo(row, column) {
this.select(this.anchor.row, this.anchor.column, row, column);
}
selectLeft() {
this.ranges.forEach(range => {
let endRow = range.end.row;
let endColumn = range.end.column;
if (endColumn <= 0) {
let prevRow = --endRow;
let line = this.doc.getLine(prevRow) || "";
endColumn = line.length;
} else {
endColumn--;
}
range.setEnd(endRow, endColumn);
});
this.merge("backward");
}
selectRight() {
this.ranges.forEach(range => {
let endRow = range.end.row;
let endColumn = range.end.column;
let line = this.doc.getLine(endRow) || "";
if (endColumn >= line.length) {
endColumn = 0;
endRow++;
} else {
endColumn++;
}
range.setEnd(endRow, endColumn);
});
this.merge("forward");
}
selectUp() {
this.ranges.forEach(range => {
let endRow = range.end.row;
let endColumn = this.head.column;
let pos = this.doc.clipPositionToDocument(endRow - 1, endColumn);
range.setEnd(pos.row, pos.column);
});
this.merge("backward");
}
selectDown() {
this.ranges.forEach(range => {
let endRow = range.end.row;
let endColumn = this.head.column;
let pos = this.doc.clipPositionToDocument(endRow + 1, endColumn);
range.setEnd(pos.row, pos.column);
});
this.merge("forward");
}
selectAll() {
this.select(0, 0, Infinity, Infinity);
}
moveCursorLeft() {
this.ranges.forEach(range => {
if (range.isCollapsed()) {
let row = range.start.row;
let column = range.start.column;
if (column <= 0) {
let prevRow = --row;
let preLine = this.doc.getLine(prevRow) || "";
column = preLine.length;
} else {
--column;
}
range.moveTo(row, column);
} else {
if (range.isBackwards()) {
range.collapseToEnd();
} else {
range.collapseToStart();
}
}
});
this.merge("backward");
}
moveCursorRight() {
this.ranges.forEach(range => {
if (range.isCollapsed()) {
let row = range.start.row;
let column = range.start.column;
let line = this.doc.getLine(row) || "";
if (column >= line.length) {
column = 0;
++row;
} else {
++column;
}
range.moveTo(row, column);
} else {
if (range.isBackwards()) {
range.collapseToStart();
} else {
range.collapseToEnd();
}
}
});
this.merge("forward");
}
}
});
define("editor", function (require, exports, module) {
let Renderer = require("./renderer");
let Session = require('./session');
module.exports = class Editor {
constructor(parent) {
this.session = new Session();
this.renderer = new Renderer(parent, this.session);
}
}
});
require("editor", ["require"], function (Editor, require) {
require('./native_extend');
window["Editor"] = Editor;
});
let editor = new Editor("#container");
console.log(editor);
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="container"></div>
</body>
</html>
* {
margin: 0;
padding: 0;
}
#container {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.editor_container {
position: relative;
width: 100%;
height: 100%;
user-select: none;
}
.editor_container .gutter,
.editor_container .scroller {
position: absolute;
top: 0;
height: 100%;
overflow: hidden;
}
.editor_container .gutter .gutter_layer {
position: absolute;
right: 10px;
top: 0;
text-align: right;
}
.editor_container .scroller {
cursor: text;
}
.editor_container .scroller .content {
position: absolute;
left: 0;
top: 0;
}
.editor_container .scrollbar .vsc {
position: absolute;
right: 0;
top: 0;
height: 100%;
overflow: hidden;
overflow-y: auto;
}
.editor_container .scrollbar .hsc {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
overflow: hidden;
overflow-x: auto;
}
.editor_container .scroller .layer {
position: absolute;
}
.editor_container .marker .btlr {
border-top-left-radius: 3px;
}
.editor_container .marker .btrr {
border-top-right-radius: 3px;
}
.editor_container .marker .bbrr {
border-bottom-right-radius: 3px;
}
.editor_container .marker .bblr {
border-bottom-left-radius: 3px;
}