mirror of
https://github.com/slap-editor/slap.git
synced 2024-09-11 12:58:30 +03:00
Uses higher-level blessed widgets
This commit is contained in:
parent
21f967a5e2
commit
22f71513da
@ -8,8 +8,6 @@ var extend = require('xtend');
|
||||
var packageJSON = require('../package');
|
||||
var Program = require('./program');
|
||||
|
||||
var program = new Program(rc(packageJSON.name, extend(argv, {
|
||||
pageLines: 10
|
||||
})));
|
||||
var program = new Program(rc(packageJSON.name, argv));
|
||||
|
||||
program.open(argv._[0]);
|
||||
|
151
lib/editor.js
151
lib/editor.js
@ -1,5 +1,7 @@
|
||||
var _ = require('lazy.js');
|
||||
var extend = require('xtend');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var blessed = require('blessed');
|
||||
|
||||
var util = require('./util');
|
||||
var coordinate = require('./coordinate');
|
||||
@ -12,28 +14,40 @@ function Line (text) {
|
||||
Line.prototype.text = util.getterSetter('text');
|
||||
|
||||
util.inherits(Editor, EventEmitter);
|
||||
function Editor (program) {
|
||||
function Editor (opts) {
|
||||
var self = this;
|
||||
EventEmitter.call(self);
|
||||
|
||||
self.program = program;
|
||||
if (!(self instanceof blessed.Node)) { return new Editor(opts); }
|
||||
|
||||
blessed.Box.call(self, extend({
|
||||
tags: true,
|
||||
wrap: false,
|
||||
border: {type: 'line'},
|
||||
|
||||
// Custom
|
||||
pageLines: 10,
|
||||
selectStyle: 'red-bg'
|
||||
}, opts));
|
||||
|
||||
self
|
||||
.text('')
|
||||
.cursor(0, 0)
|
||||
.scroll(0, 0)
|
||||
.pos(0, 0)
|
||||
.size(0, 0)
|
||||
._initHandlers();
|
||||
}
|
||||
Editor.prototype.__proto__ = blessed.Box.prototype;
|
||||
|
||||
Editor.prototype._initHandlers = function () {
|
||||
var self = this;
|
||||
|
||||
self._mouseDown = false;
|
||||
|
||||
self.on('keypress', function (ch, key) {
|
||||
var direction = {
|
||||
up: -1, down: 1,
|
||||
left: -1, right: 1,
|
||||
pageup: -1, pagedown: 1
|
||||
pageup: -1, pagedown: 1,
|
||||
home: -1, end: 1
|
||||
}[key.name];
|
||||
if (direction) {
|
||||
var distance = direction;
|
||||
@ -56,8 +70,6 @@ Editor.prototype._initHandlers = function () {
|
||||
}
|
||||
}
|
||||
self.moveCursorVertical(distance);
|
||||
} else if (key.name === 'pageup' || key.name === 'pagedown') {
|
||||
self.moveCursorVertical(direction * self.program.rc.pageLines);
|
||||
} else if (key.name === 'left' || key.name === 'right') {
|
||||
if (key.ctrl) {
|
||||
var line = self.line().text();
|
||||
@ -67,13 +79,16 @@ Editor.prototype._initHandlers = function () {
|
||||
}[key.name], 1);
|
||||
}
|
||||
self.moveCursorHorizontal(distance);
|
||||
} else if (key.name === 'pageup' || key.name === 'pagedown') {
|
||||
self.moveCursorVertical(direction * self.options.pageLines);
|
||||
} else if (key.name === 'home' || key.name === 'end') {
|
||||
self.cursor(key.name === 'home' ? 0 : Infinity, self.cursor().y);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self._mouseDown = false;
|
||||
self.on('mouse', function (data) {
|
||||
var mouse = coordinate.add(data.pos, self.scroll());
|
||||
var mouse = coordinate.add(coordinate.subtract(data, self.pos()), self.scroll());
|
||||
|
||||
if (data.action === 'wheeldown' || data.action === 'wheelup') {
|
||||
if (!data.shift && !self._mouseDown) {
|
||||
@ -84,7 +99,7 @@ Editor.prototype._initHandlers = function () {
|
||||
self.moveCursorVertical({
|
||||
wheelup: -1,
|
||||
wheeldown: 1
|
||||
}[data.action] * self.program.rc.pageLines);
|
||||
}[data.action] * self.options.pageLines);
|
||||
} else {
|
||||
if (data.action === 'mousedown') {
|
||||
self._mouseDown = true;
|
||||
@ -113,11 +128,11 @@ Editor.prototype._initHandlers = function () {
|
||||
});
|
||||
|
||||
self.on('text', function (text) {
|
||||
self.render();
|
||||
self._editorRender();
|
||||
});
|
||||
|
||||
self.on('scroll', function (scroll) {
|
||||
self.render();
|
||||
self._editorRender();
|
||||
});
|
||||
};
|
||||
|
||||
@ -130,8 +145,6 @@ Editor.prototype.line = function (n) {
|
||||
return this._lines[arguments.length ? Math.max(Math.min(n, this._lines.length - 1), 0) : this.cursor().y];
|
||||
};
|
||||
|
||||
Editor.prototype.pos = util.getterSetter('pos', util.clone, coordinate.setter());
|
||||
Editor.prototype.size = util.getterSetter('size', util.clone, coordinate.setter());
|
||||
Editor.prototype.scroll = util.getterSetter('scroll', util.clone, coordinate.setter(function (c) {
|
||||
return {
|
||||
x: Math.max.apply(Math, _(this._lines).invoke('text').pluck('length').toArray()), // TODO: cache this
|
||||
@ -166,6 +179,7 @@ Editor.prototype.moveCursorHorizontal = function (columns) {
|
||||
var cursor = self.cursor();
|
||||
while (true) {
|
||||
if (-columns > cursor.x) {
|
||||
// Up a line
|
||||
columns += cursor.x + 1;
|
||||
if (cursor.y > 0) {
|
||||
cursor.y -= 1;
|
||||
@ -174,12 +188,14 @@ Editor.prototype.moveCursorHorizontal = function (columns) {
|
||||
} else {
|
||||
var restOfLineLength = self.line(cursor.y).text().length - cursor.x;
|
||||
if (columns > restOfLineLength) {
|
||||
// Down a line
|
||||
columns -= restOfLineLength + 1;
|
||||
if (cursor.y < self._lines.length - 1) {
|
||||
cursor.x = 0;
|
||||
cursor.y += 1;
|
||||
}
|
||||
} else {
|
||||
// Same line
|
||||
cursor.x += columns;
|
||||
self.preferredCursorX = cursor.x;
|
||||
|
||||
@ -231,60 +247,71 @@ Editor.prototype.textRange = function (start, end) {
|
||||
}).join('\n');
|
||||
};
|
||||
|
||||
Editor.prototype.render = function () {
|
||||
Editor.prototype.pos = function () {
|
||||
return {
|
||||
x: this.left + this.ileft,
|
||||
y: this.top + this.itop
|
||||
};
|
||||
};
|
||||
|
||||
Editor.prototype.size = function () {
|
||||
return {
|
||||
x: this.width - this.iwidth,
|
||||
y: this.height - this.iheight
|
||||
};
|
||||
};
|
||||
|
||||
var markupRegex = /{(\/?)([\w\-,;!#]*)}/;
|
||||
Editor._realIndex = function (markup, index) {
|
||||
var i = 0;
|
||||
while (index > 0) {
|
||||
var match = markup.match(markupRegex);
|
||||
if (!match || match.index >= index) { break; }
|
||||
var accounted = match.index + match[0].length;
|
||||
markup = markup.slice(accounted);
|
||||
i += accounted;
|
||||
if (match[2].match(/(\/|!|no )?curly/)) { i--; } // Account for { and } which replace {curly} and {!curly}
|
||||
index -= match.index;
|
||||
}
|
||||
return i + index;
|
||||
}
|
||||
|
||||
Editor.prototype._editorRender = function () {
|
||||
var self = this;
|
||||
|
||||
var program = self.program.program;
|
||||
|
||||
var pos = self.pos();
|
||||
var size = self.size();
|
||||
var scroll = self.scroll();
|
||||
var selection = self.select();
|
||||
|
||||
// program.move(pos.x, pos.y);
|
||||
// program.write(self._lines
|
||||
// .slice(scroll.y, scroll.y + size.y)
|
||||
// .map(function (line) {
|
||||
// return line.text().slice(scroll.x, scroll.x + size.x);
|
||||
// })
|
||||
// .join('\n'));
|
||||
self.setContent(self._lines
|
||||
// .concat(_.repeat(new Line(''), self.size().y).toArray())
|
||||
.slice(scroll.y, scroll.y + self.size().y)
|
||||
.map(function (line, y) {
|
||||
y += scroll.y;
|
||||
line = (line.text() + _.repeat(' ', self.size().x).join(''))
|
||||
.slice(scroll.x, scroll.x + self.size().x)
|
||||
.replace(/[{}]/g, function (match) {
|
||||
return {
|
||||
'{': '{curly}',
|
||||
'}': '{!curly}'
|
||||
}[match];
|
||||
});
|
||||
|
||||
// program.move(pos.x, pos.y);
|
||||
// program.write(self._lines
|
||||
// .concat(_.repeat(new Line(''), size.y).toArray())
|
||||
// .slice(scroll.y, scroll.y + size.y)
|
||||
// .map(function (line, y) {
|
||||
// var text = line.text() + _.repeat(' ', size.x).join('');
|
||||
// return text.slice(scroll.x, scroll.x + size.x);
|
||||
// })
|
||||
// .join('\n'));
|
||||
|
||||
var selection = this.select();
|
||||
|
||||
for (var y = scroll.y; y < scroll.y + size.y; y++) {
|
||||
var line = (this._lines[y] || new Line('')).text();
|
||||
line = line + _.repeat(' ', size.x).join('');
|
||||
line = line.slice(scroll.x, scroll.x + size.x);
|
||||
|
||||
program.move(pos.x, y + pos.y - scroll.y);
|
||||
if (selection && selection.start.y <= y && y <= selection.end.y) {
|
||||
var isStartSelection = y === selection.start.y;
|
||||
var isEndSelection = y === selection.end.y;
|
||||
if (isStartSelection) {
|
||||
program.write(line.slice(0, selection.start.x));
|
||||
if (selection && selection.start.y <= y && y <= selection.end.y) {
|
||||
var start = y === selection.start.y ? Editor._realIndex(line, selection.start.x - scroll.x) : 0;
|
||||
var end = y === selection.end.y ? Editor._realIndex(line, selection.end.x - scroll.x) : Infinity;
|
||||
line = line.substring(0, start) +
|
||||
'{'+self.options.selectStyle+'}' + line.substring(start, end) + '{!'+self.options.selectStyle+'}' +
|
||||
line.substring(end);
|
||||
}
|
||||
program.bg('red');
|
||||
program.write(line.slice(!isStartSelection ? 0 : selection.start.x, !isEndSelection ? undefined : selection.end.x));
|
||||
program.bg('!red');
|
||||
if (isEndSelection) {
|
||||
program.write(line.slice(selection.end.x));
|
||||
}
|
||||
} else {
|
||||
program.write(line);
|
||||
}
|
||||
}
|
||||
|
||||
var cursorOnScreen = coordinate.add(this.cursor(), coordinate.subtract(this.pos(), this.scroll()));
|
||||
program.move(cursorOnScreen.x, cursorOnScreen.y);
|
||||
return line;
|
||||
})
|
||||
.join('\n'));
|
||||
|
||||
var cursorOnScreen = coordinate.add(self.pos(), coordinate.subtract(self.cursor(), self.scroll()));
|
||||
self.screen.program.move(cursorOnScreen.x, cursorOnScreen.y);
|
||||
|
||||
self.screen.render();
|
||||
|
||||
return self;
|
||||
};
|
||||
|
110
lib/program.js
110
lib/program.js
@ -1,65 +1,43 @@
|
||||
var fs = require('fs');
|
||||
var _ = require('lazy.js');
|
||||
var blessed = require('blessed');
|
||||
var extend = require('xtend');
|
||||
var fs = require('fs');
|
||||
|
||||
var coordinate = require('./coordinate');
|
||||
var util = require('./util');
|
||||
var Editor = require('./editor');
|
||||
|
||||
function Program (rc) {
|
||||
function Program (opts) {
|
||||
var self = this;
|
||||
|
||||
self.rc = rc;
|
||||
self.opts = opts;
|
||||
|
||||
self.program = blessed.program();
|
||||
self.program.alternateBuffer();
|
||||
self.program.enableMouse();
|
||||
self.screen = blessed.screen();
|
||||
|
||||
self.elements = {
|
||||
editor: new Editor(self).pos(0, 0)
|
||||
};
|
||||
self.editor = new Editor(extend({
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 1
|
||||
}, opts.editor));
|
||||
self.screen.append(self.editor);
|
||||
self.editor.focus();
|
||||
|
||||
self.screen.program.showCursor();
|
||||
|
||||
self
|
||||
._initHandlers()
|
||||
.keyboardFocus('editor')
|
||||
.render();
|
||||
}
|
||||
Program.prototype._initHandlers = function () {
|
||||
var self = this;
|
||||
|
||||
self.program.key('C-q', function () {
|
||||
if (!self.keyboardFocusedElement().unfinished) {
|
||||
self.screen.key('C-q', function () {
|
||||
if (self.screen.focused.unfinished) {
|
||||
// FIXME: warn user focused element needs finishing
|
||||
} else {
|
||||
self.exit();
|
||||
}
|
||||
});
|
||||
self.program.on('keypress', function (ch, key) {
|
||||
if (key.name !== 'mouse') { // Bug in blessed that mouse events come through the keypress handler only when a mouse handler exists
|
||||
self.keyboardFocusedElement().emit('keypress', ch, key);
|
||||
}
|
||||
});
|
||||
self.program.on('mouse', function (data) {
|
||||
var element = self.mouseFocusedElement();
|
||||
if (!element) {
|
||||
var name = self.elementAtCoordinate(data);
|
||||
element = self.elements[name];
|
||||
|
||||
if (data.action === 'mousedown') {
|
||||
self.mouseFocus(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (element) {
|
||||
data.pos = coordinate.subtract(data, element.pos());
|
||||
element.emit('mouse', data);
|
||||
}
|
||||
|
||||
if (data.action === 'mouseup') {
|
||||
self.mouseFocus(null);
|
||||
}
|
||||
});
|
||||
|
||||
self.program.on('resize', function () {
|
||||
self.screen.on('resize', function () {
|
||||
self.render();
|
||||
});
|
||||
|
||||
@ -71,66 +49,20 @@ Program.prototype.open = function (path) {
|
||||
|
||||
fs.readFile(path, function (err, data) {
|
||||
if (err) { throw err; }
|
||||
self.elements.editor.text(data);
|
||||
self.editor.text(data);
|
||||
});
|
||||
|
||||
return self;
|
||||
};
|
||||
|
||||
Program.prototype.elementAtCoordinate = function (c) {
|
||||
var self = this;
|
||||
|
||||
return _(self.elements)
|
||||
.keys()
|
||||
.dropWhile(function (name) {
|
||||
var element = self.elements[name];
|
||||
var pos = coordinate.subtract(c, element.pos());
|
||||
return !coordinate.within(pos, coordinate.returnsOrigin(), element.size());
|
||||
})
|
||||
.first();
|
||||
};
|
||||
|
||||
Program.prototype.keyboardFocus = util.getterSetter('keyboardFocus', null, function (keyboardFocus) {
|
||||
if (this.elements.hasOwnProperty(keyboardFocus)) {
|
||||
return keyboardFocus;
|
||||
} else {
|
||||
return this.keyboardFocus();
|
||||
}
|
||||
});
|
||||
Program.prototype.keyboardFocusedElement = function () {
|
||||
return this.elements[this.keyboardFocus()];
|
||||
};
|
||||
|
||||
Program.prototype.mouseFocus = util.getterSetter('mouseFocus', null, function (mouseFocus) {
|
||||
if (this.elements.hasOwnProperty(mouseFocus) || mouseFocus === null) {
|
||||
return mouseFocus;
|
||||
} else {
|
||||
return this.mouseFocus();
|
||||
}
|
||||
});
|
||||
Program.prototype.mouseFocusedElement = function () {
|
||||
return this.elements[this.mouseFocus()];
|
||||
};
|
||||
|
||||
Program.prototype.exit = function () {
|
||||
this.program.clear();
|
||||
this.program.disableMouse();
|
||||
this.program.normalBuffer();
|
||||
process.exit(0);
|
||||
|
||||
return this; // Just in case
|
||||
};
|
||||
|
||||
Program.prototype.render = function (force) {
|
||||
this.program.clear();
|
||||
this.elements.editor
|
||||
// .pos(0, 0)
|
||||
.size(this.program.cols, this.program.rows - 1)
|
||||
.render(force);
|
||||
// this.elements.footer
|
||||
// .pos(0, this.program.rows - 1)
|
||||
// .size(this.program.cols, 1)
|
||||
// .render(force);
|
||||
this.screen.render();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
@ -13,7 +13,7 @@
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"blessed": "^0.0.29",
|
||||
"blessed": "beardtree/blessed",
|
||||
"lazy.js": "^0.3.2",
|
||||
"xtend": "^3.0.0",
|
||||
"rc": "^0.3.5",
|
||||
|
Loading…
Reference in New Issue
Block a user