mirror of
https://github.com/slap-editor/slap.git
synced 2024-09-11 12:58:30 +03:00
Populate repository
This commit is contained in:
parent
de718604ca
commit
21f967a5e2
3
index.js
Executable file
3
index.js
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
module.exports = require('./lib/cli');
|
15
lib/cli.js
Normal file
15
lib/cli.js
Normal file
@ -0,0 +1,15 @@
|
||||
var argv = require('optimist')
|
||||
.usage("Usage: $0 [options] <file>")
|
||||
.demand(1)
|
||||
.argv;
|
||||
var rc = require('rc');
|
||||
var extend = require('xtend');
|
||||
|
||||
var packageJSON = require('../package');
|
||||
var Program = require('./program');
|
||||
|
||||
var program = new Program(rc(packageJSON.name, extend(argv, {
|
||||
pageLines: 10
|
||||
})));
|
||||
|
||||
program.open(argv._[0]);
|
61
lib/coordinate.js
Normal file
61
lib/coordinate.js
Normal file
@ -0,0 +1,61 @@
|
||||
function coordinate (x, y) {
|
||||
return arguments.length === 1
|
||||
? {x: x.x, y: x.y}
|
||||
: {x: x, y: y};
|
||||
}
|
||||
coordinate.pointwise = function (fn) {
|
||||
return function (one, two) {
|
||||
return {
|
||||
x: fn(one.x, two.x),
|
||||
y: fn(one.y, two.y)
|
||||
};
|
||||
};
|
||||
};
|
||||
coordinate.add = coordinate.pointwise(function (left, right) { return left + right; });
|
||||
coordinate.subtract = coordinate.pointwise(function (left, right) { return left - right; });
|
||||
coordinate.min = coordinate.pointwise(Math.min);
|
||||
coordinate.max = coordinate.pointwise(Math.max);
|
||||
|
||||
coordinate.lt = function (self, other) {
|
||||
return self.x < other.x && self.y < other.y;
|
||||
};
|
||||
coordinate.lte = function (self, other) {
|
||||
return self.x <= other.x && self.y <= other.y;
|
||||
};
|
||||
|
||||
coordinate.within = function (c, cornerOne, cornerTwo) {
|
||||
var topLeft = coordinate.min(cornerOne, cornerTwo);
|
||||
var bottomRight = coordinate.max(cornerOne, cornerTwo);
|
||||
return coordinate.lte(topLeft, c) && coordinate.lt(c, bottomRight);
|
||||
};
|
||||
|
||||
coordinate.returnsInfinity = function () { return {x: Infinity, y: Infinity}; };
|
||||
coordinate.returnsOrigin = function () { return {x: 0, y: 0}; };
|
||||
coordinate.setter = function (upper, lower) {
|
||||
upper = upper || coordinate.returnsInfinity;
|
||||
lower = lower || coordinate.returnsOrigin;
|
||||
return function () {
|
||||
var c = coordinate.apply(this, arguments);
|
||||
|
||||
var upperC = upper.call(this, c);
|
||||
var lowerC = lower.call(this, c);
|
||||
return coordinate.max(coordinate.min(c, upperC), lowerC);
|
||||
};
|
||||
};
|
||||
|
||||
coordinate.linear = {
|
||||
cmp: function (left, right) {
|
||||
if (left.y < right.y) { return -1; }
|
||||
if (left.y > right.y) { return 1; }
|
||||
// Same line from this point on
|
||||
|
||||
if (left.x < right.x) { return -1; }
|
||||
if (left.x > right.x) { return 1; }
|
||||
return 0;
|
||||
},
|
||||
within: function (c, start, end) {
|
||||
return coordinate.linear.cmp(start, c) < 1 && coordinate.linear.cmp(c, end) < 1;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = coordinate;
|
292
lib/editor.js
Normal file
292
lib/editor.js
Normal file
@ -0,0 +1,292 @@
|
||||
var _ = require('lazy.js');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
var util = require('./util');
|
||||
var coordinate = require('./coordinate');
|
||||
|
||||
util.inherits(Line, EventEmitter);
|
||||
function Line (text) {
|
||||
EventEmitter.call(this);
|
||||
this.text(text);
|
||||
}
|
||||
Line.prototype.text = util.getterSetter('text');
|
||||
|
||||
util.inherits(Editor, EventEmitter);
|
||||
function Editor (program) {
|
||||
var self = this;
|
||||
EventEmitter.call(self);
|
||||
|
||||
self.program = program;
|
||||
|
||||
self
|
||||
.text('')
|
||||
.cursor(0, 0)
|
||||
.scroll(0, 0)
|
||||
.pos(0, 0)
|
||||
.size(0, 0)
|
||||
._initHandlers();
|
||||
}
|
||||
Editor.prototype._initHandlers = function () {
|
||||
var self = this;
|
||||
|
||||
self.on('keypress', function (ch, key) {
|
||||
var direction = {
|
||||
up: -1, down: 1,
|
||||
left: -1, right: 1,
|
||||
pageup: -1, pagedown: 1
|
||||
}[key.name];
|
||||
if (direction) {
|
||||
var distance = direction;
|
||||
|
||||
if (!key.shift && !self._mouseDown) {
|
||||
self.startSelection(null);
|
||||
} else if (!self.startSelection()) {
|
||||
self.startSelection(self.cursor());
|
||||
}
|
||||
|
||||
if (key.name === 'up' || key.name === 'down') {
|
||||
if (key.ctrl) {
|
||||
while (true) {
|
||||
var y = distance + self.cursor().y;
|
||||
|
||||
if (!(0 <= y && y < self._lines.length - 1)) { break; }
|
||||
if (self.line(y).text().match(/^\s*$/g)) { break; }
|
||||
|
||||
distance += direction;
|
||||
}
|
||||
}
|
||||
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();
|
||||
distance = direction * Math.max({
|
||||
left: line.length - line.slice(0, self.cursor().x).search(/\W\w+$/),
|
||||
right: line.slice(self.cursor().x).search(/\W/)
|
||||
}[key.name], 1);
|
||||
}
|
||||
self.moveCursorHorizontal(distance);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self._mouseDown = false;
|
||||
self.on('mouse', function (data) {
|
||||
var mouse = coordinate.add(data.pos, self.scroll());
|
||||
|
||||
if (data.action === 'wheeldown' || data.action === 'wheelup') {
|
||||
if (!data.shift && !self._mouseDown) {
|
||||
self.startSelection(null);
|
||||
} else if (!self.startSelection()) {
|
||||
self.startSelection(self.cursor());
|
||||
}
|
||||
self.moveCursorVertical({
|
||||
wheelup: -1,
|
||||
wheeldown: 1
|
||||
}[data.action] * self.program.rc.pageLines);
|
||||
} else {
|
||||
if (data.action === 'mousedown') {
|
||||
self._mouseDown = true;
|
||||
self.startSelection(mouse);
|
||||
}
|
||||
if (self._mouseDown) {
|
||||
self.cursor(mouse);
|
||||
self.preferredCursorX = self.cursor().x;
|
||||
}
|
||||
if (data.action === 'mouseup') {
|
||||
self._mouseDown = false;
|
||||
var startSelection = self.startSelection();
|
||||
if (startSelection && coordinate.linear.cmp(startSelection, mouse) === 0) {
|
||||
self.startSelection(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.on('cursor', function (cursor) {
|
||||
var scroll = coordinate.min(self.scroll(), cursor);
|
||||
var maxScroll = coordinate.add(coordinate.subtract(cursor, self.size()), {x: 1, y: 1});
|
||||
scroll = coordinate.max(scroll, maxScroll);
|
||||
|
||||
self.scroll(scroll);
|
||||
});
|
||||
|
||||
self.on('text', function (text) {
|
||||
self.render();
|
||||
});
|
||||
|
||||
self.on('scroll', function (scroll) {
|
||||
self.render();
|
||||
});
|
||||
};
|
||||
|
||||
Editor.prototype.text = util.getterSetter('text', null, function (text) {
|
||||
text = text.toString();
|
||||
this._lines = text.split(/\n/).map(function (line) { return new Line(line); });
|
||||
return text;
|
||||
});
|
||||
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
|
||||
y: this._lines.length
|
||||
};
|
||||
}));
|
||||
|
||||
var cursorSetter = coordinate.setter(function (c) {
|
||||
var line = this.line(c.y);
|
||||
return {
|
||||
x: (line ? line.text() : '').length,
|
||||
y: this._lines.length - 1
|
||||
};
|
||||
});
|
||||
|
||||
Editor.prototype.cursor = util.getterSetter('cursor', util.clone, cursorSetter);
|
||||
Editor.prototype.moveCursorVertical = function (rows) {
|
||||
var cursor = this.cursor();
|
||||
cursor.y += rows;
|
||||
cursor.x = 0 <= cursor.y
|
||||
? cursor.y < this._lines.length
|
||||
? this.preferredCursorX || cursor.x
|
||||
: this.line().text().length
|
||||
: 0;
|
||||
this.cursor(cursor);
|
||||
|
||||
return this;
|
||||
};
|
||||
Editor.prototype.moveCursorHorizontal = function (columns) {
|
||||
var self = this;
|
||||
|
||||
var cursor = self.cursor();
|
||||
while (true) {
|
||||
if (-columns > cursor.x) {
|
||||
columns += cursor.x + 1;
|
||||
if (cursor.y > 0) {
|
||||
cursor.y -= 1;
|
||||
cursor.x = self.line(cursor.y).text().length;
|
||||
}
|
||||
} else {
|
||||
var restOfLineLength = self.line(cursor.y).text().length - cursor.x;
|
||||
if (columns > restOfLineLength) {
|
||||
columns -= restOfLineLength + 1;
|
||||
if (cursor.y < self._lines.length - 1) {
|
||||
cursor.x = 0;
|
||||
cursor.y += 1;
|
||||
}
|
||||
} else {
|
||||
cursor.x += columns;
|
||||
self.preferredCursorX = cursor.x;
|
||||
|
||||
self.cursor(cursor);
|
||||
return self;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Editor.prototype.startSelection = util.getterSetter('startSelection', function (c) {
|
||||
return c ? util.clone(c) : c;
|
||||
}, function (c) {
|
||||
if (c === null) { return null; }
|
||||
return cursorSetter.apply(this, arguments);
|
||||
});
|
||||
|
||||
Editor.prototype.select = function (start, end) {
|
||||
if (arguments.length) {
|
||||
if (arguments.length === 1) {
|
||||
end = start;
|
||||
start = this.cursor();
|
||||
}
|
||||
return this
|
||||
.startSelection(start)
|
||||
.cursor(end);
|
||||
} else {
|
||||
var startSelection = this.startSelection();
|
||||
if (!startSelection) { return null; }
|
||||
|
||||
var selectionBounds = [startSelection, this.cursor()];
|
||||
selectionBounds.sort(coordinate.linear.cmp);
|
||||
return {
|
||||
start: selectionBounds[0],
|
||||
end: selectionBounds[1],
|
||||
text: this.textRange(selectionBounds[0], selectionBounds[1])
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Editor.prototype.textRange = function (start, end) {
|
||||
return this._lines
|
||||
.slice(start.y, end.y + 1)
|
||||
.map(function (line, i) {
|
||||
var line = line.text();
|
||||
if (i + start.y === end.y) { line = line.slice(0, end.x); }
|
||||
if (i === 0) { line = line.slice(start.x); }
|
||||
return line;
|
||||
}).join('\n');
|
||||
};
|
||||
|
||||
Editor.prototype.render = function () {
|
||||
var self = this;
|
||||
|
||||
var program = self.program.program;
|
||||
|
||||
var pos = self.pos();
|
||||
var size = self.size();
|
||||
var scroll = self.scroll();
|
||||
|
||||
// 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'));
|
||||
|
||||
// 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));
|
||||
}
|
||||
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 self;
|
||||
};
|
||||
|
||||
module.exports = Editor;
|
138
lib/program.js
Normal file
138
lib/program.js
Normal file
@ -0,0 +1,138 @@
|
||||
var fs = require('fs');
|
||||
var _ = require('lazy.js');
|
||||
var blessed = require('blessed');
|
||||
|
||||
var coordinate = require('./coordinate');
|
||||
var util = require('./util');
|
||||
var Editor = require('./editor');
|
||||
|
||||
function Program (rc) {
|
||||
var self = this;
|
||||
|
||||
self.rc = rc;
|
||||
|
||||
self.program = blessed.program();
|
||||
self.program.alternateBuffer();
|
||||
self.program.enableMouse();
|
||||
|
||||
self.elements = {
|
||||
editor: new Editor(self).pos(0, 0)
|
||||
};
|
||||
|
||||
self
|
||||
._initHandlers()
|
||||
.keyboardFocus('editor')
|
||||
.render();
|
||||
}
|
||||
Program.prototype._initHandlers = function () {
|
||||
var self = this;
|
||||
|
||||
self.program.key('C-q', function () {
|
||||
if (!self.keyboardFocusedElement().unfinished) {
|
||||
// FIXME: warn user focused element needs finishing
|
||||
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.render();
|
||||
});
|
||||
|
||||
return self;
|
||||
};
|
||||
|
||||
Program.prototype.open = function (path) {
|
||||
var self = this;
|
||||
|
||||
fs.readFile(path, function (err, data) {
|
||||
if (err) { throw err; }
|
||||
self.elements.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);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
module.exports = Program;
|
25
lib/util.js
Normal file
25
lib/util.js
Normal file
@ -0,0 +1,25 @@
|
||||
var _ = require('lazy.js');
|
||||
var extend = require('xtend');
|
||||
|
||||
var util = extend(require('util'), {
|
||||
clone: extend,
|
||||
extend: extend,
|
||||
|
||||
getterSetter: function (name, getter, setter) {
|
||||
var _name = '_'+name;
|
||||
getter = getter || _.identity;
|
||||
setter = setter || _.identity;
|
||||
return function () {
|
||||
if (arguments.length) {
|
||||
var newVal = setter.apply(this, arguments);
|
||||
this[_name] = newVal;
|
||||
this.emit && this.emit(name, getter.call(this, newVal));
|
||||
return this;
|
||||
} else {
|
||||
return getter.call(this, this[_name]);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = util;
|
22
package.json
Normal file
22
package.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "nip",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": ""
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"blessed": "^0.0.29",
|
||||
"lazy.js": "^0.3.2",
|
||||
"xtend": "^3.0.0",
|
||||
"rc": "^0.3.5",
|
||||
"optimist": "^0.6.1"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user