editx
Version:
Simple but powerful text and code editor for the console.
335 lines (299 loc) • 9.25 kB
JavaScript
const fs = require('fs');
const engine = require('./engine.js');
const Notification = require('./notification.js');
const syntax = require("./syntax.js");
const sideBarWidth = 5;
const navHeight = 2;
process.argv = process.argv.slice(2);
let filename = process.argv[0];
if (!fs.existsSync(filename)) {
console.warn('Error! That file does not exist.');
process.exit();
}
let modified = false;
let notSavedWarning = false;
let scrollY = 0, scrollX = 0;
let cursorPosX = 0, cursorPosY = 0;
let wantedCursorPosX = 0;
typeableChars = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()-=_+[]{}|;':\",.<>/?`~\\"
typeableChars = typeableChars.split('');
let notification;
let file;
let lines = [];
let copyBuffer = "";
function setup() {
loadFile();
notification = new Notification.Notification(engine);
engine.render();
}
function loadFile() {
let lastDotIndex = filename.lastIndexOf(".");
let fileExtension = lastDotIndex == -1 ? '' : filename.slice(lastDotIndex + 1);
syntax.setLanguage(fileExtension);
file = fs.readFileSync(filename);
lines = file.toString().split("\n");
}
const draw = function () {
// HEADER
engine.fillBackground('yellow')
engine.fillForeground('yellow');
engine.drawLine(0, 0, 1, 0, engine.width, engine.BOX);
engine.fillForeground('black');
engine.drawText(2, 0, "ESC to close");
engine.drawText(Math.floor(engine.width / 2) - Math.floor(filename.length / 2), 0, filename);
if (modified) {
engine.drawText(engine.width - 10, 0, "modified");
} else {
engine.drawText(engine.width - 7, 0, "saved");
}
engine.noBg();
// File Rendering
for (let y = navHeight; y < engine.height; y++) {
let index = y - navHeight + scrollY;
let lineNum = index + 1;
if (index < lines.length) {
engine.fillForeground('lightblack');
engine.drawText((sideBarWidth - 1) - lineNum.toString().length - scrollX, y, lineNum.toString());
engine.fillForeground('white');
let line = lines[index].toString();
renderLine(line, y);
}
}
notification.show(engine);
engine.setCursor(cursorPosX + sideBarWidth - scrollX, cursorPosY - scrollY + navHeight);
// engine.drawPoint(cursorPosX + sideBarWidth, cursorPosY - scrollY + navHeight, engine.BOX);
}
function keyPressed(key) {
// Cursor Control:
if (key.name == 'left') {
moveCursorX(-1);
}
if (key.name == 'right') {
moveCursorX(1);
}
if (key.name == 'up') {
moveCursorY(-1);
}
if (key.name == 'down') {
moveCursorY(1);
}
// Special Keys
if (key.name == 'tab' && !key.ctrl) {
let remainder = cursorPosX % 4;
remainder = 4 - remainder == 0 ? 4 : 4 - remainder;
for (let i = 0; i < remainder; i++) {
typeChar(' ');
}
}
if (key.name == 'backspace') {
if (key.ctrl) {
// delete whole word
} else {
deleteKey();
}
}
if (key.name == 'return' && !key.ctrl) {
returnKey();
}
if (key.name == 'escape') {
exit();
}
if (key.ctrl) {
if (key.name == 's') {
saveFile();
}
if (key.name == 'c') {
copyBuffer = lines[cursorPosY];
notification.notify("Line copied", 1000);
}
if (key.name == 'v') {
lines.splice(cursorPosY, 0, copyBuffer);
moveCursorY(1);
modified = true;
engine.render();
}
if (key.name == 'x') {
copyBuffer = lines[cursorPosY];
lines.splice(cursorPosY, 1);
if (lines.length == 0) {
lines.push("");
}
moveCursorY(-1);
notification.notify("Line cut", 1000);
modified = true;
engine.render();
}
if (key.name == 'q') {
if (cursorPosY > 0) {
let temp = lines[cursorPosY - 1];
lines[cursorPosY - 1] = lines[cursorPosY];
lines[cursorPosY] = temp;
moveCursorY(-1);
modified = true;
engine.render();
}
}
if (key.name == 'a') {
if (cursorPosY < lines.length - 1) {
let temp = lines[cursorPosY + 1];
lines[cursorPosY + 1] = lines[cursorPosY];
lines[cursorPosY] = temp;
moveCursorY(1);
modified = true;
engine.render();
}
}
if (key.name == 'e') {
exit();
}
}
// Normal Keys
if (typeableChars.indexOf(key.sequence) != -1) {
typeChar(key.sequence);
}
}
function typeChar(char) {
lineStr = lines[cursorPosY];
lines[cursorPosY] = lineStr.slice(0, cursorPosX) + char + lineStr.slice(cursorPosX);
moveCursorX(1);
engine.render();
modified = true;
}
function returnKey() {
let newStr = lines[cursorPosY].slice(cursorPosX);
lines[cursorPosY] = lines[cursorPosY].slice(0, cursorPosX)
lines.splice(cursorPosY + 1, 0, newStr);
moveCursorX(1);
modified = true;
engine.render();
}
function deleteKey() {
if (cursorPosX <= 0) {
if (cursorPosY > 0) {
let originalLineLength = lines[cursorPosY].length;
lines[cursorPosY - 1] += lines[cursorPosY];
moveCursorX(-1);
moveCursorX(originalLineLength);
lines.splice(cursorPosY + 1, 1);
modified = true;
}
} else {
lines[cursorPosY] = lines[cursorPosY].slice(0, cursorPosX - 1) + lines[cursorPosY].slice(cursorPosX);
moveCursorX(-1);
modified = true;
}
engine.render();
}
function moveCursorX(dir) {
cursorPosX += dir;
if (cursorPosX < 0) {
if (cursorPosY > 0) {
cursorPosY--;
cursorPosX = lines[cursorPosY].length;
} else {
cursorPosX = 0;
cursorPosY = 0;
}
}
if (cursorPosX > lines[cursorPosY].length) {
if (cursorPosY < lines.length - 1) {
cursorPosY++;
cursorPosX = 0;
} else {
cursorPosX = lines[cursorPosY].length;
cursorPosY = lines.length - 1;
}
}
wantedCursorPosX = cursorPosX;
engine.setCursor(cursorPosX + sideBarWidth - scrollX, cursorPosY - scrollY + navHeight);
updateScrollY();
updateScrollX();
}
function moveCursorY(dir) {
cursorPosX = wantedCursorPosX;
cursorPosY += dir;
if (cursorPosY < 0) {
cursorPosY = 0;
cursorPosX = 0;
}
if (cursorPosY >= lines.length) {
cursorPosY = lines.length - 1;
cursorPosX = lines[cursorPosY].length;
}
if (cursorPosX > lines[cursorPosY].length) {
cursorPosX = lines[cursorPosY].length;
}
engine.setCursor(cursorPosX + sideBarWidth - scrollX, cursorPosY - scrollY + navHeight);
updateScrollY();
updateScrollX();
}
function updateScrollY() {
if (cursorPosY - scrollY > engine.height - 3 - navHeight) {
scrollY++;
engine.render();
}
if (cursorPosY - scrollY < 2 && scrollY > 0) {
scrollY--;
engine.render();
}
}
function updateScrollX() {
if (lines[cursorPosY].length > engine.width - 2 - sideBarWidth) {
if (cursorPosX - scrollX > engine.width - 3 - sideBarWidth) {
scrollX = (cursorPosX - scrollX) - (engine.width - 3 - sideBarWidth);
engine.render();
}
if (cursorPosX - scrollX < 2 && scrollX > 0) {
scrollX--;
engine.render();
}
} else if (scrollX > 0) {
scrollX = 0;
engine.render();
}
}
function renderLine(line, y) {
let renderStr = syntax.computeSyntax(line);
let currentColour = 'white';
engine.fillForeground(currentColour);
let offset = 0;
let stack = 0;
for (let x = 0; x < renderStr.length; x++) {
let col = syntax.getColourFromSyntaxEscapeStr(renderStr[x]);
if (col != '') {
if (col == 'white') {
stack--;
} else {
stack++;
}
if (stack == 1 && col != 'white') {
engine.fillForeground(col);
} else if (stack == 0) {
engine.fillForeground('white');
}
offset++;
} else {
engine.drawPoint(x + sideBarWidth - scrollX - offset, y, renderStr[x]);
}
}
}
function saveFile() {
let string = lines.join('\n');
fs.writeFile(filename, string, (err) => {
if (err) throw err;
modified = false;
engine.render();
});
}
function exit() {
if (!modified || notSavedWarning) {
engine.exit();
} else {
// User has not saved. Create popup.
notSavedWarning = true;
notification.notify("File is not saved. Changes will be lost. Press ESC to continue", 2200, 'red');
setTimeout(() => { notSavedWarning = false; }, 2200);
}
}
engine.init(setup, draw, keyPressed, 1000, true);