Uses higher-level blessed widgets

This commit is contained in:
Dan Kaplun 2014-04-25 14:58:18 -05:00
parent 21f967a5e2
commit 22f71513da
4 changed files with 112 additions and 155 deletions

View File

@ -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]);

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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",