Merge branch 'master' into feature-cli-at-line

Conflicts:
	lib/ui/Editor.js
This commit is contained in:
Maciek Jurczyk 2015-06-26 17:13:18 +02:00
commit a2f8a37252
31 changed files with 327 additions and 2290 deletions

View File

@ -1,5 +1,8 @@
; This is your customizable slap configuration. Defaults are located here:
; https://github.com/slap-editor/slap/blob/master/slap.ini
; https://github.com/slap-editor/editor-widget/blob/master/editor-widget.ini
; https://github.com/slap-editor/base-widget/blob/master/base-widget.ini
; https://github.com/slap-editor/slap-clibboard-plugin/blob/master/slap-clibboard-plugin.ini
[logger]
level = "info"

View File

@ -1,13 +1,15 @@
var _ = require('lazy.js');
var _ = require('lodash');
var path = require('path');
var rc = require('rc');
var util = require('./util');
var util = require('slap-util');
var package = require('../package');
var baseDir = path.join(__dirname, '..');
var configFile = path.join(baseDir, package.name + '.ini');
var opts = util.parseOpts(rc(package.name, configFile));
opts = _(opts).merge(opts.slap).toObject();
opts = _.merge(opts, opts.slap);
var info = console._error || console.error;
// special invocation modes
if (opts.h || opts.help) {
@ -23,7 +25,7 @@ if (opts.h || opts.help) {
if (path.dirname(command) === '.') command = '.' + path.sep + command;
}
console.error([
info([
"Usage: " + command + " [options...] [<file1> [<file2> [...]]]",
"",
package.description,
@ -36,15 +38,11 @@ if (opts.h || opts.help) {
}
if (opts.v || opts.version) {
var SORT_ORDER = ['slap', 'node', 'v8'];
var versions = process.versions;
var SORT_ORDER = ['slap', 'node', 'v8'].reverse();
var versions = _.clone(process.versions);
versions[package.name] = package.version;
console.error(Object.keys(versions)
.sort(function (a, b) {
var sa = SORT_ORDER.indexOf(a); if (sa < 0) return 1;
var sb = SORT_ORDER.indexOf(b); if (sb < 0) return -1;
return sa - sb;
})
info(Object.keys(versions)
.sort(function (a, b) { return SORT_ORDER.indexOf(b) - SORT_ORDER.indexOf(a); })
.map(function (name) { return name+"@"+versions[name]; })
.join(", "));
return process.exit(0);
@ -62,25 +60,24 @@ if (opts.perf.profile && process.execArgv.indexOf('--prof') === -1) {
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));
var iconv = require('iconv-lite');
var logger = require('./logger');
var highlightClient = require('./highlight/client');
var blessed = require('base-widget').blessed;
var Slap = require('./ui/Slap');
var Pane = require('./ui/Pane');
module.exports = function (options) {
opts = _(opts).merge(options || {}).toObject();
return util.getUserDir().catch(Promise.resolve()).then(function (userDir) {
opts.logger = _(userDir ? {dir: userDir} : {}).merge(opts.logger).toObject();
opts = _.merge(opts, options);
return Slap.getUserDir().catch(Promise.resolve()).then(function (userDir) {
if (userDir) opts = _.merge({logger: {file: path.resolve(userDir, package.name+'.log')}}, opts);
opts = _.merge({
editor: {logger: opts.logger},
screenOpts: {logger: opts.logger}
}, opts);
util.logger(opts.logger);
logger(opts.logger);
highlightClient.call('send', {type: 'logger', options: opts.logger}).done();
iconv.extendNodeEncodings();
logger.info("loading...");
logger.verbose("configuration:", opts);
util.logger.info("loading...");
util.logger.verbose("configuration:", opts);
if (!opts.screen) opts.screen = new blessed.Screen(opts.screenOpts);
var slap = new Slap(opts);
Promise.all(opts._.map(function (path, i) {
@ -88,7 +85,7 @@ module.exports = function (options) {
})).done();
if (!opts._.length) { // if no files are passed
new Pane().setCurrent(); // open a new empty file
new Pane({parent: slap}).setCurrent(); // open a new empty file
if (!opts.config) { // first run without a file passed
slap.open(path.join(baseDir, 'README.md'), true)
.tap(function (pane) { pane.editor.readOnly(true); })

View File

@ -1,30 +0,0 @@
var fork = require('child_process').fork;
var path = require('path');
var minimist = require('rc/node_modules/minimist');
var Promise = require('bluebird');
var init = Promise.resolve();
var opts = minimist(process.execArgv);
var forkOpts = {silent: false};
if (['debug', 'debug-brk'].some(function (opt) { return opt in opts; })) {
init = init
.then(require('get-random-port'))
.then(function (port) { forkOpts.execArgv = ['--debug=' + port]; });
}
var client;
function initClient () {
var oldMessageListeners = client ? client.listeners('message') : [];
client = fork(path.join(__dirname, 'server.js'), forkOpts);
client.setMaxListeners(100);
client.on('exit', initClient);
oldMessageListeners.forEach(client.on.bind(client, 'message'));
return client;
}
init = init.then(initClient);
module.exports = init;
var buckets = 0;
module.exports.getBucket = function () { return buckets++; };

View File

@ -1,63 +0,0 @@
#!/usr/bin/env node
var hljs = require('highlight.js'); hljs.configure({classPrefix: ''});
var cheerio = require('cheerio');
var logger = require('../logger');
var textUtil = require('../textUtil');
function highlight (text, language) {
if (language === false) return [];
var highlighted;
if (language) {
try { highlighted = hljs.highlight(language, text, true); } catch (e) {}
}
if (!highlighted) highlighted = hljs.highlightAuto(text);
var $ = cheerio.load(highlighted.value);
var ranges = [];
do {
var lastElCount = elCount;
var elCount = $('*:not(:has(*))').replaceWith(function () {
var $el = $(this);
var text = '';
[this].concat($el.parents().get(), [$.root()]).reverse().reduce(function (parent, el) {
$(parent).contents().each(function () {
var $sibling = $(this);
if ($sibling.is(el)) return false;
text += $sibling.text();
});
return el;
});
var lines = textUtil.splitLines(text);
var linesPlusEl = textUtil.splitLines(text + $el.text());
ranges.push({
range: [
[lines .length - 1, lines[lines.length - 1] .length],
[linesPlusEl.length - 1, linesPlusEl[linesPlusEl.length - 1].length]
],
properties: {
type: 'syntax',
syntax: ($el.attr('class') || '').match(/\S+/g) || []
}
});
return $el.text();
}).length;
} while (lastElCount !== elCount);
return ranges;
};
process.on('message', function (message) {
switch (message.type) {
case 'highlight':
process.send({
ranges: highlight(message.text, message.language),
revision: message.revision,
bucket: message.bucket
});
break;
case 'logger': logger(message.options); break;
}
});

View File

@ -1,39 +0,0 @@
var path = require('path');
var winston = require('winston');
var fs = require('fs');
var packageName = require('../package').name;
function logger (opts) {
var logFile = path.join(opts.dir || '.', opts.filename || packageName + '.log');
logger.stream = fs.createWriteStream(logFile, {flags: 'a'});
var winstonLogger = new winston.Logger({
exitOnError: false,
transports: [
new winston.transports.File({
stream: logger.stream,
level: opts.level || 'info',
handleExceptions: true,
json: false,
prettyPrint: true,
colorize: true
})
]
});
var levels = winston.config.npm.levels;
if (levels[opts.level] > levels.debug) {
console._error = console.error;
console.error = function () {
return logger.stream.write([].join.call(arguments, ' ') + '\n');
};
} else {
require('longjohn');
require('bluebird').longStackTraces();
}
winstonLogger.extend(logger);
}
module.exports = logger;
global.logger = logger;

View File

@ -1,141 +0,0 @@
// Deals with blessed-style {bold}tags{/bold}
var logger = require('./logger');
var blessed = require('blessed');
function Markup (style) {
var self = this;
self.style = style || '';
self.contents = [];
[].slice.call(arguments, 1).forEach(function (arg) { self.push(arg); });
}
Markup.TAG_RE = /\{(\/?)([\w\-,;!#]*)\}/;
Markup.TAG_RE_G = new RegExp(Markup.TAG_RE.source, 'g');
Markup.parse = function (text) {
if (text instanceof Markup) return text;
var markup = new Markup();
var hierarchy = [markup], match;
while (match = text.match(Markup.TAG_RE)) {
var tag = match[0];
var parent = hierarchy[hierarchy.length - 1];
if (match.index) parent.push(text.slice(0, match.index));
if (!match[1]) { // open tag
var replace = {open: '{', close: '}'}[match[2]];
if (replace) {
parent.push(replace);
} else {
var newMarkup = new Markup(match[0]);
parent.push(newMarkup);
hierarchy.push(newMarkup);
}
} else { // close tag
var closed;
if (match[0] === '{/}') closed = hierarchy.splice(1, Infinity);
else if (parent.style === '{'+match[2]+'}') closed = [hierarchy.pop()];
else throw new Error("invalid close tag");
var lastItem = hierarchy[hierarchy.length - 1];
closed.some(function (item) {
if (!item.contents.length) {
lastItem.contents.pop();
return true;
}
lastItem = item;
});
}
text = text.slice(match.index + tag.length);
}
if (hierarchy.length !== 1) throw new Error("mismatched tag");
if (text) markup.push(text);
return markup.clean();
};
Markup.closeTags = function (markedUp) {
return (markedUp
.replace(Markup.TAG_RE_G, '{/$2}', 'g') // 'g' flag ignored :(
.match(Markup.TAG_RE_G) || [])
.reverse()
.join('');
};
Markup.getTaglessLength = function (val) {
if (val instanceof Markup) return val.contents.reduce(function (total, item) {
return total + Markup.getTaglessLength(item);
}, 0);
return val.length;
};
Markup.prototype.clean = function () {
if (!this.style && this.contents.length === 1) {
var child = this.contents[0];
if (child instanceof Markup) return child;
}
return this;
};
Markup.prototype.tag = function (style, start, end) {
if (typeof start !== 'number') start = 0;
if (typeof end !== 'number') end = Infinity;
if (!style) return this;
return this.slice(0, start).push(
new Markup(style, this.slice(start, end)),
this.slice(end));
};
Markup.prototype.slice = function (start, end) {
if (typeof start !== 'number') start = 0;
if (typeof end !== 'number') end = Infinity;
var i = 0;
var markup = new Markup(this.style);
this.contents.some(function (item) {
var nextI = i + Markup.getTaglessLength(item);
if (start < nextI && end >= i) {
markup.push(item.slice(Math.max(0, start - i), Math.max(0, end - i)));
}
if (nextI >= end) return true;
i = nextI;
});
return markup;
};
Markup.prototype.push = function () {
var self = this;
var contents = self.contents;
// unoptimized version of the following:
// contents.push.apply(contents, arguments);
var lastItem = contents[contents.length - 1];
[].forEach.call(arguments, function (item) {
if (!item) return;
if (item instanceof Markup) {
if (!item.style || item.style === self.style) {
self.push.apply(self, item.contents);
lastItem = contents[contents.length - 1];
return;
}
if (lastItem instanceof Markup && item.style === lastItem.style) {
return lastItem.push.apply(lastItem, item.contents);
}
}
if (typeof item === 'string' && typeof lastItem === 'string') {
return contents[contents.length - 1] += item;
}
contents.push(item);
lastItem = item;
});
return self.clean();
};
Markup.prototype.toString = function () {
return this.style + this.contents.map(function (item) {
return typeof item === 'string' ? blessed.escape(item) : item;
}).join('') + Markup.closeTags(this.style);
};
Object.defineProperty(Markup.prototype, 'length', {get: function () {
return this.toString().length;
}});
function markup (text, style, start, end) {
return Markup.parse(text).tag(style, start, end);
}
markup.parse = Markup.parse;
module.exports = markup;

View File

@ -1,37 +0,0 @@
var _ = require('lazy.js');
var Point = require('text-buffer/lib/point');
var util = require('./util');
exports._regExpRegExp = /^\/(.+)\/([im]?)$/;
exports._lineRegExp = /\r\n|\r|\n/;
exports.splitLines = function (text) {
var lines = [];
var match, line;
while (match = exports._lineRegExp.exec(text)) {
line = text.slice(0, match.index) + match[0];
text = text.slice(line.length);
lines.push(line);
}
lines.push(text);
return lines;
};
exports.escapeRegExp = function (text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
};
exports.regExpIndexOf = function(str, regex, index) {
index = index || 0;
var offset = str.slice(index).search(regex);
return (offset >= 0) ? (index + offset) : offset;
};
exports.regExpLastIndexOf = function (str, regex, index) {
if (index === 0 || index) str = str.slice(0, Math.max(0, index));
var i;
var offset = -1;
while ((i = str.search(regex)) !== -1) {
offset += i + 1;
str = str.slice(i + 1);
}
return offset;
};

View File

@ -1,107 +0,0 @@
var Promise = require('bluebird');
var blessed = require('blessed');
var _ = require('lazy.js');
var Point = require('text-buffer/lib/point');
var Slap = require('./Slap');
var util = require('../util');
function BaseElement (opts) {
var self = this;
if (!(self instanceof blessed.Node)) return new BaseElement(opts);
opts = _(Slap.global.options.element).merge(opts || {}).toObject();
if (!('slap' in opts)) opts.slap = Slap.global;
if (!('parent' in opts)) opts.parent = opts.slap;
if (self instanceof BaseElement) blessed.Box.call(self, opts); // this should not be called if an element inherits from built-in blessed classes
if (self.parent instanceof Pane) self.pane = self.parent;
self.slap = opts.slap;
self.focusable = opts.focusable;
self.ready = Promise.delay(0)
.then(function () { return self._initHandlers(); })
.return(self);
}
BaseElement.prototype.__proto__ = blessed.Box.prototype;
BaseElement.prototype.walkDepthFirst = function (direction, after, fn) {
if (arguments.length === 2) fn = after;
var children = this.children.slice();
if (direction === -1) children.reverse();
if (after) children = children.slice(children.indexOf(after) + 1);
return children.some(function (child) {
return fn.apply(child, arguments) || BaseElement.prototype.walkDepthFirst.call(child, direction, fn);
});
};
BaseElement.prototype.focusFirst = function (direction, after) {
return this.walkDepthFirst(direction, after, function () {
if (this.visible && this.focusable) {
this.focus();
return true;
}
});
};
BaseElement.prototype._focusDirection = function (direction) {
var self = this;
var descendantParent;
var descendant = self.screen.focused;
while (descendant.hasAncestor(self)) {
descendantParent = descendant.parent;
if (BaseElement.prototype.focusFirst.call(descendantParent, direction, descendant)) return self;
descendant = descendantParent;
}
if (!self.focusFirst(direction)) throw new Error("no focusable descendant");
return self;
};
BaseElement.prototype.focusNext = function () {
return this._focusDirection(1);
};
BaseElement.prototype.focusPrev = function () {
return this._focusDirection(-1);
};
BaseElement.prototype.focus = function () {
if (!this.hasFocus()) return blessed.Box.prototype.focus.apply(this, arguments);
return this;
};
BaseElement.prototype.isAttached = function () {
return this.hasAncestor(this.screen);
};
BaseElement.prototype.hasFocus = function (asChild) {
var self = this;
var focused = self.screen.focused;
return focused.visible && (focused === self || focused.hasAncestor(self) || (asChild && self.hasAncestor(focused)));
};
BaseElement.prototype.pos = function () {
return new Point(this.atop + this.itop, this.aleft + this.ileft);
};
BaseElement.prototype.size = function () {
if (!this.isAttached()) return new Point(0, 0); // hack
return new Point(this.height - this.iheight, this.width - this.iwidth);
};
BaseElement.prototype.shrinkWidth = function () { return this.content.length + this.iwidth; };
BaseElement.prototype._initHandlers = function () {
var self = this;
self.on('focus', function () {
logger.debug('focus', util.typeOf(self));
if (!self.focusable) self.focusNext();
});
self.on('blur', function () { logger.debug('blur', util.typeOf(self)); });
self.on('show', function () { self.setFront(); });
self.on('keypress', _.noop); // 'element keypress' doesn't work correctly without this
self.on('element keypress', function (el, ch, key) {
switch (util.getBinding(self.options.bindings, key)) {
case 'hide': self.hide(); return false;
case 'focusNext': self.focusNext(); return false;
case 'focusPrev': self.focusPrev(); return false;
}
});
};
module.exports = BaseElement;
var Pane = require('./Pane'); // circular import

View File

@ -1,36 +1,33 @@
var blessed = require('blessed');
var _ = require('lazy.js');
var _ = require('lodash');
var Slap = require('./Slap');
var BaseForm = require('./BaseForm');
var Field = require('./Field');
var BaseWidget = require('base-widget');
var Field = require('editor-widget').Field;
var util = require('../util');
var util = require('slap-util');
function BaseFindForm (opts) {
var self = this;
if (!(self instanceof blessed.Node)) return new BaseFindForm(opts);
if (!(self instanceof BaseFindForm)) return new BaseFindForm(opts);
BaseForm.call(self, _({
prevEditorState: {}
})
.merge(Slap.global.options.form.baseFind || {})
.merge(opts || {})
.toObject());
BaseForm.call(self, _.merge({
prevEditorState: {}
}, Slap.global.options.form.baseFind, opts));
self.findField = new Field(_({
self.findField = new Field(_.merge({
parent: self,
top: 0,
left: 0,
right: 0
}).merge(self.options.findField || {}).toObject());
}, Slap.global.options.editor, Slap.global.options.field, self.options.findField));
}
BaseFindForm.prototype.__proto__ = BaseForm.prototype;
BaseFindForm.prototype.find = function (text, direction) {
var self = this;
self.slap.header.message(null);
self.screen.slap.header.message(null);
if (text) self.emit('find', text, direction);
else self.resetEditor();
return self;
@ -55,7 +52,7 @@ BaseFindForm.prototype._initHandlers = function () {
self.find(textBuf.getText());
});
self.on('hide', function () {
if (_(self.pane.forms).pluck('visible').compact().none()) {
if (!_.some(self.pane.forms, 'visible')) {
prevEditorState.selection = null;
prevEditorState.scroll = null;
}
@ -64,7 +61,7 @@ BaseFindForm.prototype._initHandlers = function () {
textBuf.on('changed', function () { self.find(textBuf.getText()); });
self.findField.on('keypress', function (ch, key) {
var text = textBuf.getText();
switch (util.getBinding(self.options.bindings, key)) {
switch (self.resolveBinding(key)) {
case 'next': self.find(text, 1); return false;
case 'prev': self.find(text, -1); return false;
};

View File

@ -1,50 +1,49 @@
var blessed = require('blessed');
var _ = require('lazy.js');
var _ = require('lodash');
var util = require('slap-util');
var BaseWidget = require('base-widget');
var Slap = require('./Slap');
var BaseElement = require('./BaseElement');
var util = require('../util');
function BaseForm (opts) {
var self = this;
if (!(self instanceof blessed.Node)) return new BaseForm(opts);
if (!(self instanceof BaseForm)) return new BaseForm(opts);
BaseElement.call(self, _({
hidden: true,
height: 1,
left: 0,
right: 0,
bottom: 0
})
.merge(Slap.global.options.form || {})
.merge(opts || {})
.toObject());
if (self.parent instanceof Pane) self.pane.forms.push(self);
BaseWidget.call(self, _.merge({
hidden: true,
height: 1,
left: 0,
right: 0,
bottom: 0
}, Slap.global.options.form, opts));
if (self.parent instanceof Pane) {
self.pane = self.parent;
self.pane.forms.push(self);
}
}
BaseForm.prototype.__proto__ = BaseElement.prototype;
BaseForm.prototype.__proto__ = BaseWidget.prototype;
BaseForm.prototype.cancel = function () { this.emit('cancel'); };
BaseForm.prototype.submit = function () { this.emit('submit'); };
BaseForm.prototype._initHandlers = function () {
var self = this;
self.on('element keypress', function (el, ch, key) {
switch (util.getBinding(self.options.bindings, key)) {
switch (self.resolveBinding(key)) {
case 'cancel': self.cancel(); return false;
};
});
self.on('show', function () { self.focus(); });
self.on('hide', function () {
self.slap._stopKeyPropagation().done();
if (self.screen.focused.hasAncestor(self.pane) && !self.screen.focused.visible) self.pane.focus();
self.screen.slap._stopKeyPropagation().done();
if (self.screen.focused.hasAncestor(self.pane) && !self.screen.slap.focused.visible) self.pane.focus();
});
self.on('element blur', function (el) { if (self.visible && !self.hasFocus(true)) self.cancel(); });
self.on('element submit', function (el) { if (el !== self) self.submit(); });
self.on('element cancel', function (el) { if (el !== self) self.cancel(); });
self.on('cancel', function () { self.hide(); });
return BaseElement.prototype._initHandlers.apply(self, arguments);
return BaseWidget.prototype._initHandlers.apply(self, arguments);
};
module.exports = BaseForm;

View File

@ -1,33 +1,33 @@
var blessed = require('blessed');
var _ = require('lazy.js');
var _ = require('lodash');
var util = require('slap-util');
var BaseWidget = require('base-widget');
var Slap = require('./Slap');
var BaseElement = require('./BaseElement');
var util = require('../util');
function Button (opts) {
var self = this;
if (!(self instanceof blessed.Node)) return new Button(opts);
if (!(self instanceof Button)) return new Button(opts);
opts = _(Slap.global.options.button).merge({
opts = _.merge({
mouse: true,
focusable: true,
shrink: true,
padding: {left: 1, right: 1}
}).merge(opts || {}).toObject();
}, Slap.global.options.button, opts);
opts.style.focus = opts.style.hover;
blessed.Button.call(self, opts);
BaseElement.call(self, opts);
BaseWidget.blessed.Button.call(self, opts);
BaseWidget.call(self, opts);
}
Button.prototype.__proto__ = blessed.Button.prototype;
Button.prototype.__proto__ = BaseWidget.blessed.Button.prototype;
Button.prototype._initHandlers = function () {
var self = this;
self.on('keypress', function (ch, key) {
if (key.name === 'enter') self.slap._stopKeyPropagation().done(); // FIXME: hack
if (key.name === 'enter') self.screen.slap._stopKeyPropagation().done(); // FIXME: hack
});
return BaseElement.prototype._initHandlers.apply(self, arguments);
return BaseWidget.prototype._initHandlers.apply(self, arguments);
};
module.exports = Button;

View File

@ -1,41 +0,0 @@
var blessed = require('blessed');
var _ = require('lazy.js');
var Slap = require('./Slap');
var Editor = require('./Editor');
var BaseForm = require('./BaseForm');
var util = require('../util');
var textUtil = require('../textUtil');
function Field (opts) {
var self = this;
if (!(self instanceof blessed.Node)) return new Field(opts);
Editor.call(self, _({
height: 1,
multiLine: false
})
.merge(Slap.global.options.field || {})
.merge(opts || {})
.toObject());
if (self.parent instanceof BaseForm) self.form = self.parent;
self.language(false);
}
Field.prototype.__proto__ = Editor.prototype;
Field.prototype.submit = function (value) { this.emit('submit', value); }
Field.prototype.cancel = function () { this.emit('cancel'); }
Field.prototype._initHandlers = function () {
var self = this;
self.on('keypress', function (ch, key) {
switch (util.getBinding(self.options.bindings, key)) {
case 'submit': self.submit(self.textBuf.getText()); return false;
case 'cancel': self.cancel(); return false;
};
});
return Editor.prototype._initHandlers.apply(self, arguments);
}
module.exports = Field;

View File

@ -1,36 +1,36 @@
var blessed = require('blessed');
var _ = require('lazy.js');
var _ = require('lodash');
var util = require('slap-util');
var BaseWidget = require('base-widget');
var Slap = require('./Slap');
var BaseElement = require('./BaseElement');
var util = require('../util');
function FileBrowser (opts) {
var self = this;
if (!(self instanceof blessed.Node)) return new FileBrowser(opts);
if (!(self instanceof FileBrowser)) return new FileBrowser(opts);
opts = _(Slap.global.options.fileBrowser).merge({
focusable: true
}).merge(opts || {}).toObject();
BaseElement.call(self, opts);
blessed.FileManager.call(self, _({
opts = _.merge({
keys: true,
mouse: true
}).merge(opts).toObject());
mouse: true,
focusable: true
}, Slap.global.options.fileBrowser, opts);
BaseWidget.blessed.FileManager.call(self, opts);
BaseWidget.call(self, opts);
self.refresh();
self.data.selectedStyle = self.style.selected;
self.data.itemStyle = self.style.item;
}
FileBrowser.prototype.__proto__ = blessed.FileManager.prototype;
FileBrowser.prototype.__proto__ = BaseWidget.blessed.FileManager.prototype;
FileBrowser.prototype._initHandlers = function () {
var self = this;
self.on('element mousedown', function (el) { self.focus(); });
self.on('file', function (path) { self.slap.open(path, true).done(); });
self.on('file', function (path) { self.screen.slap.open(path, true).done(); });
self.on('cancel', function () {
var slap = self.slap;
var slap = self.screen.slap;
var currentPane = slap.panes[slap.data.currentPane];
if (currentPane) currentPane.focus();
});
@ -43,7 +43,7 @@ FileBrowser.prototype._initHandlers = function () {
self.style.selected = self.data.itemStyle;
});
return BaseElement.prototype._initHandlers.apply(self, arguments);
return BaseWidget.prototype._initHandlers.apply(self, arguments);
};
module.exports = FileBrowser;

View File

@ -1,39 +1,32 @@
var blessed = require('blessed');
var _ = require('lazy.js');
var _ = require('lodash');
var lodash = require('lodash');
var Point = require('text-buffer/lib/point');
var util = require('slap-util');
var BaseWidget = require('base-widget');
var Slap = require('./Slap');
var BaseElement = require('./BaseElement');
var BaseFindForm = require('./BaseFindForm');
var textUtil = require('../textUtil');
FindForm._label = " find (/.*/ for regex): ";
function FindForm (opts) {
var self = this;
if (!(self instanceof blessed.Node)) return new FindForm(opts);
if (!(self instanceof FindForm)) return new FindForm(opts);
BaseFindForm.call(self, _({
findField: {left: FindForm._label.length}
})
.merge(Slap.global.options.form.find || {})
.merge(opts || {})
.toObject());
BaseFindForm.call(self, _.merge({
findField: {left: FindForm._label.length}
}, Slap.global.options.form.find, opts));
self.findLabel = new BaseElement(_({
parent: self,
tags: true,
content: FindForm._label,
top: 0,
height: 1,
left: 0,
width: FindForm._label.length,
style: self.options.style
})
.merge(self.options.findLabel || {})
.toObject());
self.findLabel = new BaseWidget(_.merge({
parent: self,
tags: true,
content: FindForm._label,
top: 0,
height: 1,
left: 0,
width: FindForm._label.length,
style: self.options.style
}, self.options.findLabel));
}
FindForm.prototype.__proto__ = BaseFindForm.prototype;
@ -48,7 +41,7 @@ FindForm.prototype.selectRange = function (range) {
};
FindForm.prototype._initHandlers = function () {
var self = this;
var header = self.slap.header;
var header = self.screen.slap.header;
var editor = self.pane.editor;
var selection = editor.selection;
@ -59,10 +52,10 @@ FindForm.prototype._initHandlers = function () {
self.on('find', lodash.throttle(function (pattern, direction) {
direction = direction || 0;
editor.destroyMarkers({type: 'findMatch'});
var regExpMatch = pattern.match(textUtil._regExpRegExp);
var regExpMatch = pattern.match(util.text._regExpRegExp);
pattern = regExpMatch
? new RegExp(regExpMatch[1], regExpMatch[2])
: new RegExp(textUtil.escapeRegExp(pattern), 'img');
: new RegExp(util.text.escapeRegExp(pattern), 'img');
var selectionRange = selection.getRange();
var matches = [];

View File

@ -1,36 +1,30 @@
var blessed = require('blessed');
var _ = require('lazy.js');
var Point = require('text-buffer/lib/point');
var _ = require('lodash');
var BaseWidget = require('base-widget');
var Slap = require('./Slap');
var BaseElement = require('./BaseElement');
var BaseFindForm = require('./BaseFindForm');
GoLineForm._label = " line number: ";
function GoLineForm (opts) {
var self = this;
if (!(self instanceof blessed.Node)) return new GoLineForm(opts);
if (!(self instanceof GoLineForm)) return new GoLineForm(opts);
BaseFindForm.call(self, _({
findField: {left: GoLineForm._label.length}
})
.merge(Slap.global.options.form.goLine || {})
.merge(opts || {})
.toObject());
BaseFindForm.call(self, _.merge({
findField: {left: GoLineForm._label.length}
}, Slap.global.options.form.goLine, opts));
self.goLineLabel = new BaseElement(_({
parent: self,
tags: true,
content: GoLineForm._label,
top: 0,
height: 1,
left: 0,
width: GoLineForm._label.length,
style: self.options.style
})
.merge(self.options.goLineLabel || {})
.toObject());
self.goLineLabel = new BaseWidget(_.merge({
parent: self,
tags: true,
content: GoLineForm._label,
top: 0,
height: 1,
left: 0,
width: GoLineForm._label.length,
style: self.options.style
}, self.options.goLineLabel));
}
GoLineForm.prototype.__proto__ = BaseFindForm.prototype;
@ -42,7 +36,7 @@ GoLineForm.prototype._initHandlers = function () {
lineNumber = Number(lineNumber) - 1;
if (lineNumber !== lineNumber) return; // isNaN(lineNumber)
var selection = self.pane.editor.selection;
selection.setHeadPosition(new Point(lineNumber, 0));
selection.setHeadPosition([lineNumber, 0]);
selection.clearTail();
if (direction) self.hide();
return self;

View File

@ -1,73 +1,58 @@
var _ = require('lazy.js');
var _ = require('lodash');
var blessed = require('blessed');
var path = require('path');
var Slap = require('./Slap');
var BaseElement = require('./BaseElement');
var Button = require('./Button');
var util = require('slap-util');
var util = require('../util');
var markup = require('../markup');
var BaseWidget = require('base-widget');
var Slap = require('./Slap');
var Button = require('./Button');
function Header (opts) {
var self = this;
if (!(self instanceof blessed.Node)) return new Header(opts);
if (!(self instanceof Header)) return new Header(opts);
var opts = _({
BaseWidget.call(self, _.merge(opts.headerPosition !== 'bottom'
? {top: 0}
: {bottom: 0}, {
left: 0,
right: 0,
height: 1
})
.merge(Slap.global.options.header || {})
.merge(opts || {})
.toObject();
BaseElement.call(self, _(opts)
.merge(opts.headerPosition !== 'bottom'
? {top: 0, headerPosition: 'top'}
: {bottom: 0, headerPosition: 'bottom'})
.toObject());
}, Slap.global.options.header, opts));
self.leftContent = new BaseElement(_({
parent: self,
tags: true,
left: 1,
shrink: true,
style: self.options.style
})
.merge(self.options.leftContent || {})
.toObject());
self.leftContent = new BaseWidget(_.merge({
parent: self,
tags: true,
left: 1,
shrink: true,
style: self.options.style
}, self.options.leftContent));
var helpBinding = self.slap.options.bindings.help;
var helpBinding = Slap.global.options.bindings.help;
helpBinding = Array.isArray(helpBinding) ? helpBinding[0] : helpBinding;
self.helpButton = new Button(_({
parent: self,
content: "Help" + (helpBinding ? ": " + helpBinding : "")
})
.merge(self.options.helpButton || {})
.toObject());
self.helpButton = new Button(_.merge({
parent: self,
content: "Help" + (helpBinding ? ": " + helpBinding : "")
}, self.options.helpButton));
self.rightContent = new BaseElement(_({
parent: self,
tags: true,
shrink: true,
style: self.options.style
})
.merge(self.options.rightContent || {})
.toObject());
self.rightContent = new BaseWidget(_.merge({
parent: self,
tags: true,
shrink: true,
style: self.options.style
}, self.options.rightContent));
self.messageContent = new BaseElement(_({
parent: self,
tags: true,
shrink: true,
style: self.options.style
})
.merge(self.options.messageContent || {})
.toObject());
self.messageContent = new BaseWidget(_.merge({
parent: self,
tags: true,
shrink: true,
style: self.options.style
}, self.options.messageContent));
// self._blink(true);
}
Header.prototype.__proto__ = BaseElement.prototype;
Header.prototype.__proto__ = BaseWidget.prototype;
Header.prototype._blink = util.getterSetter('blink', null, function (blink) {
var self = this;
@ -90,7 +75,7 @@ Header.prototype.message = util.getterSetter('message', null, function (message,
}
// self._blink(false);
return message !== null ? markup(' '+message+' ', self.options.style[styleName || 'info']) : null;
return message !== null ? util.markup(' '+message+' ', self.options.style[styleName || 'info']) : null;
});
Header.prototype._initHandlers = function () {
@ -98,8 +83,8 @@ Header.prototype._initHandlers = function () {
['message', 'blink'].forEach(function (evt) {
self.on(evt, function () { self.parent.render(); });
});
self.helpButton.on('press', function () { self.slap.help(); });
return BaseElement.prototype._initHandlers.apply(self, arguments);
self.helpButton.on('press', function () { self.screen.slap.help(); });
return BaseWidget.prototype._initHandlers.apply(self, arguments);
};
Header.prototype.render = function () {
@ -110,15 +95,15 @@ Header.prototype.render = function () {
var right = [];
var style = self.options.style;
var slap = self.slap;
var slap = self.screen.slap;
var editor = (slap.panes[slap.data.currentPane] || {}).editor;
if (editor) {
var originalPath = editor.textBuf.getPath();
var markupPath = originalPath
? blessed.escape(path.relative(self.slap.fileBrowser.cwd, originalPath))
? blessed.escape(path.relative(slap.fileBrowser.cwd, originalPath))
: "new file";
if (editor.textBuf.isModified()) {
markupPath = markup(markupPath+"*", style.changed);
markupPath = util.markup(markupPath+"*", style.changed);
}
left.push(markupPath);
@ -129,24 +114,24 @@ Header.prototype.render = function () {
"("+editor.textBuf.getLineCount()+")"
];
if (originalEncoding) right.push(blessed.escape(originalEncoding));
if (editor.readOnly()) right.push(markup("read-only", style.warning));
if (!slap.insertMode()) right.unshift(markup("OVR", style.overwrite));
if (editor.readOnly()) right.push(util.markup("read-only", style.warning));
if (!editor.insertMode()) right.unshift(util.markup("OVR", style.overwrite));
}
self.leftContent.setContent(left.join(" "));
self.rightContent.setContent(right.join(" "));
var message = self.message() || "";
if (self._blink()) message = markup(message, style.blinkStyle);
if (self._blink()) message = util.markup(message, style.blinkStyle);
self.messageContent.setContent(message.toString());
// float: right basically
['helpButton', 'rightContent', 'messageContent'].reduce(function (right, key) {
self[key].right = right;
return 2 + right + BaseElement.prototype.shrinkWidth.call(self[key]);
return 2 + right + BaseWidget.prototype.shrinkWidth.call(self[key]);
}, 1);
return BaseElement.prototype.render.apply(self, arguments);
return BaseWidget.prototype.render.apply(self, arguments);
};
module.exports = Header;

View File

@ -1,68 +1,68 @@
var blessed = require('blessed');
var _ = require('lazy.js');
var _ = require('lodash');
var BaseElement = require('./BaseElement');
var util = require('slap-util');
var util = require('../util');
var BaseWidget = require('base-widget');
var Editor = require('editor-widget');
function Pane (opts) {
var self = this;
if (!(self instanceof blessed.Node)) return new Pane(opts);
if (!(self instanceof Pane)) return new Pane(opts);
BaseElement.call(self, _({
top: Slap.global.header.options.headerPosition === 'top' ? 1 : 0,
bottom: Slap.global.header.options.headerPosition === 'bottom' ? 1 : 0,
left: 0,
right: 0,
})
.merge(Slap.global.options.pane || {})
.merge(opts || {})
.toObject());
self.left = self.slap.fileBrowser.visible ? self.slap.fileBrowser.width : 0;
BaseWidget.call(self, _.merge({
top: Slap.global.header.options.headerPosition === 'top' ? 1 : 0,
bottom: Slap.global.header.options.headerPosition === 'bottom' ? 1 : 0,
left: 0,
right: 0,
}, Slap.global.options.pane, opts));
self.left = Slap.global.fileBrowser.visible ? Slap.global.fileBrowser.width : 0;
self.forms = self.forms || [];
self.editor = self.options.editor || new Editor({
self.editor = self.options.editor || new Editor(_.merge({
parent: self,
top: 0,
left: 0,
right: 0,
bottom: 0
});
}, Slap.global.options.editor));
self.findForm = new FindForm({parent: self});
self.goLineForm = new GoLineForm({parent: self});
self.saveAsForm = new SaveAsForm({parent: self});
self.saveAsCloseForm = new SaveAsCloseForm({parent: self});
self.slap.panes.push(self);
if (!self.parent.panes) self.parent.panes = [];
self.parent.panes.push(self);
}
Pane.prototype.__proto__ = BaseElement.prototype;
Pane.prototype.__proto__ = BaseWidget.prototype;
Pane.prototype.setCurrent = function () {
var self = this;
var slap = self.slap;
var slap = self.screen.slap;
var panes = slap.panes;
var paneIndex = panes.indexOf(self);
if (paneIndex === -1) { paneIndex = panes.length; panes.push(self); }
slap.data.currentPane = paneIndex;
self.ready.then(function () { if (self.isAttached()) self.focus(); }).done();
self.ready
.then(function () {
if (!self.isAttached()) return;
slap.data.currentPane = paneIndex;
self.focus();
})
.done();
return self;
};
Pane.prototype.close = function () {
var self = this;
self.detach();
var slap = self.slap;
var slap = self.screen.slap;
var paneIndex = slap.panes.indexOf(self);
if (paneIndex !== -1) {
slap.panes.splice(paneIndex, 1);
if (slap.panes.length) {
slap.panes[Math.max(paneIndex - 1, 0)].setCurrent();
} else {
slap.fileBrowser.focus();
}
if (slap.panes.length) slap.panes[Math.max(paneIndex - 1, 0)].setCurrent();
else slap.fileBrowser.focus();
}
self.emit('close');
@ -86,12 +86,28 @@ Pane.prototype.requestClose = function () {
}
};
Pane.prototype.save = function (path) {
var self = this;
var header = self.screen.slap.header;
var editor = self.editor;
return editor.save(path)
.tap(function () { header.message("saved to " + editor.textBuf.getPath(), 'success'); })
.catch(function (err) {
switch ((err.cause || err).code) {
case 'EACCES': case 'EISDIR':
header.message(err.message, 'error');
break;
default: throw err;
}
});
};
Pane.prototype._initHandlers = function () {
var self = this;
var editor = self.editor;
self.on('element keypress', function (el, ch, key) {
switch (util.getBinding(self.options.bindings, key)) {
case 'save': if (!editor.readOnly()) editor.textBuf.getPath() ? editor.save().done() : self.saveAsForm.show(); return false;
switch (self.resolveBinding(key)) {
case 'save': if (!editor.readOnly()) editor.textBuf.getPath() ? self.save().done() : self.saveAsForm.show(); return false;
case 'saveAs': if (!editor.readOnly()) self.saveAsForm.show(); return false;
case 'close': self.requestClose(); return false;
case 'find': self.findForm.show(); return false;
@ -109,13 +125,17 @@ Pane.prototype._initHandlers = function () {
if (!formHasFocus && visibleForms.length) visibleForms[0].focus();
});
return BaseElement.prototype._initHandlers.apply(self, arguments);
['path-changed', 'changed'].forEach(function (evt) {
editor.textBuf.on(evt, function () { self.screen.slap.header.render(); });
});
editor.on('insertMode', function () { self.screen.slap.header.render(); });
return BaseWidget.prototype._initHandlers.apply(self, arguments);
};
module.exports = Pane;
var Slap = require('./Slap');
var Editor = require('./Editor');
var SaveAsForm = require('./SaveAsForm');
var SaveAsCloseForm = require('./SaveAsCloseForm');
var FindForm = require('./FindForm');

View File

@ -1,5 +1,6 @@
var blessed = require('blessed');
var _ = require('lazy.js');
var _ = require('lodash');
var BaseWidget = require('base-widget');
var Slap = require('./Slap');
var SaveAsForm = require('./SaveAsForm');
@ -8,25 +9,22 @@ var Button = require('./Button');
function SaveAsCloseForm (opts) {
var self = this;
if (!(self instanceof blessed.Node)) return new SaveAsCloseForm(opts);
if (!(self instanceof SaveAsCloseForm)) return new SaveAsCloseForm(opts);
SaveAsForm.call(self, _(Slap.global.options.saveAsCloseForm).merge(opts || {}).toObject());
SaveAsForm.call(self, _.merge({}, Slap.global.options.saveAsCloseForm, opts));
self.discardChangesButton = new Button(_({
parent: self,
content: "Discard changes",
top: 0,
right: 0
})
.merge(Slap.global.options.button.warning || {})
.merge(self.options.discardChangesButton || {})
.toObject());
self.discardChangesButton = new Button(_.merge({
parent: self,
content: "Discard changes",
top: 0,
right: 0
}, Slap.global.options.button.warning, self.options.discardChangesButton));
}
SaveAsCloseForm.prototype.__proto__ = SaveAsForm.prototype;
SaveAsCloseForm.prototype._initHandlers = function () {
var self = this;
self.on('show', function () { self.slap.header.message("unsaved changes, please save or discard", 'warning'); });
self.on('show', function () { self.screen.slap.header.message("unsaved changes, please save or discard", 'warning'); });
self.on('save', function () { self.pane.close(); });
self.on('discardChanges', function () { self.pane.close(); });
self.discardChangesButton.on('press', function () { self.emit('discardChanges'); });

View File

@ -1,46 +1,37 @@
var blessed = require('blessed');
var _ = require('lazy.js');
var Point = require('text-buffer/lib/point');
var _ = require('lodash');
var BaseWidget = require('base-widget');
var Field = require('editor-widget').Field;
var Slap = require('./Slap');
var BaseElement = require('./BaseElement');
var BaseForm = require('./BaseForm');
var Field = require('./Field');
SaveAsForm._label = " save as: ";
function SaveAsForm (opts) {
var self = this;
if (!(self instanceof blessed.Node)) return new SaveAsForm(opts);
if (!(self instanceof SaveAsForm)) return new SaveAsForm(opts);
BaseForm.call(self, _(Slap.global.options.form.saveAs)
.merge({
field: {left: SaveAsForm._label.length}
})
.merge(opts || {})
.toObject());
BaseForm.call(self, _.merge({
field: {left: SaveAsForm._label.length}
}, Slap.global.options.form.saveAs, opts));
self.saveAsLabel = new BaseElement(_({
parent: self,
tags: true,
content: SaveAsForm._label,
top: 0,
height: 1,
left: 0,
width: SaveAsForm._label.length,
style: self.options.style
})
.merge(self.options.saveAsLabel || {})
.toObject());
self.saveAsLabel = new BaseWidget(_.merge({
parent: self,
tags: true,
content: SaveAsForm._label,
top: 0,
height: 1,
left: 0,
width: SaveAsForm._label.length,
style: self.options.style
}, self.options.saveAsLabel));
self.pathField = new Field(_({
parent: self,
top: 0,
left: SaveAsForm._label.length,
right: 0
})
.merge(self.options.pathField || {})
.toObject());
self.pathField = new Field(_.merge({
parent: self,
top: 0,
left: SaveAsForm._label.length,
right: 0
}, Slap.global.options.editor, Slap.global.options.field, self.options.pathField));
}
SaveAsForm.prototype.__proto__ = BaseForm.prototype;
@ -48,16 +39,16 @@ SaveAsForm.prototype._initHandlers = function () {
var self = this;
self.on('show', function () {
self.pathField.textBuf.setText(self.pane.editor.textBuf.getPath() || '');
self.pathField.selection.setHeadPosition(new Point(0, Infinity));
self.pathField.selection.setHeadPosition([0, Infinity]);
self.pathField.focus();
});
self.on('submit', function () {
var path = self.pathField.textBuf.getText();
if (!path) {
self.slap.header.message("couldn't save, no filename passed", 'error');
self.screen.slap.header.message("couldn't save, no filename passed", 'error');
return;
}
self.pane.editor.save(path).done(function (newPath) {
self.pane.save(path).done(function (newPath) {
if (newPath) {
self.hide();
self.emit('save', newPath);

View File

@ -1,37 +1,39 @@
var _ = require('lazy.js');
var _ = require('lodash');
var lodash = require('lodash');
var blessed = require('blessed');
var Promise = require('bluebird');
var path = require('path');
var clap = require('node-clap');
var mkdirp = Promise.promisify(require('mkdirp'));
var util = require('../util');
var util = require('slap-util');
var BaseWidget = require('base-widget');
var Editor = require('editor-widget');
function Slap (opts) {
var self = this;
if (!(self instanceof blessed.Node)) return new Slap(opts);
if (!(self instanceof Slap)) return new Slap(opts);
if (!Slap.global) Slap.global = this;
blessed.Screen.call(self, opts);
BaseElement.call(self, opts);
if (opts.screen && !opts.screen.slap) opts.screen.slap = self;
BaseWidget.call(self, opts);
self.panes = [];
self.header = new Header();
self.header = new Header({parent: self});
self.fileBrowser = new FileBrowser({
parent: self,
top: self.header.options.headerPosition === 'top' ? 1 : 0,
bottom: self.header.options.headerPosition === 'bottom' ? 1 : 0,
left: 0,
left: 0
});
self.fileBrowser.focus();
self.toggleInsertMode();
}
Slap.prototype.__proto__ = blessed.Screen.prototype;
Slap.global = null;
Slap.prototype.__proto__ = BaseWidget.prototype;
Slap.prototype.paneForPath = function (panePath) {
var self = this;
@ -49,7 +51,7 @@ Slap.prototype.open = Promise.method(function (filePath, current) {
var self = this;
var pane = self.paneForPath(filePath);
pane = pane || new Pane();
pane = pane || new Pane({parent: self});
return pane.editor.open(filePath)
.then(function () {
if (current) pane.setCurrent();
@ -58,6 +60,9 @@ Slap.prototype.open = Promise.method(function (filePath, current) {
.catch(function (err) {
pane.close();
switch ((err.cause || err).code) {
case 'EACCES':
self.header.message(err.message, 'error');
break;
case 'EISDIR':
self.fileBrowser.refresh(filePath, _.noop);
self.fileBrowser.focus();
@ -67,14 +72,22 @@ Slap.prototype.open = Promise.method(function (filePath, current) {
});
});
Slap.prototype.insertMode = util.getterSetter('insertMode', null, Boolean);
Slap.prototype.toggleInsertMode = function () { return this.insertMode(!this.insertMode()); };
Slap.prototype.quit = function () {
Promise.all([
require('../highlight/client').call('kill'),
Promise.delay(20).then(function () { process.exit(0); }), // FIXME: .delay(20) hack for I/O flush
]).done();
var self = this;
var quit = Promise.resolve();
if (Editor.highlightClient) quit = quit
.return(Editor.highlightClient)
.then(function (client) {
if (!client) return;
client.dontRespawn = true;
self.panes.forEach(function (pane) { pane.detach(); });
});
quit
.delay(20) // FIXME: .delay(20) hack for I/O flush
.then(function () { process.exit(0); })
.done();
// this.program.input.removeAllListeners();
@ -96,32 +109,18 @@ Slap.prototype._stopKeyPropagation = function () {
Slap.prototype._initHandlers = function () {
var self = this;
self.on('keypress', function (ch, key) {
var binding = util.getBinding(self.options.bindings, key);
self.on('element keypress', function (el, ch, key) {
var binding = self.resolveBinding(key);
var logLine = "keypress " + key.full;
if (key.full !== key.sequence) logLine += " [raw: " + JSON.stringify(key.sequence) + "]";
var bindingInfo;
if (binding) {
bindingInfo = binding + " on " + util.typeOf(self);
} else {
var focused = self.focused || {};
do {
var focusedBinding = util.getBinding((focused.options || {}).bindings, key);
if (focusedBinding) {
bindingInfo = focusedBinding + " on " + util.typeOf(focused);
break;
}
} while (focused = focused.parent);
}
if (bindingInfo) {
logger.silly(logLine + " (bound to " + bindingInfo + ")");
} else {
logger.silly(logLine);
}
var focused = {parent: self.screen.focused}, focusedBinding;
while ((focused = focused.parent) && !(focusedBinding = BaseWidget.prototype.resolveBinding.call(focused, key)));
if (focusedBinding) logLine += " (bound to "+focusedBinding+" on "+util.typeOf(focused) + ")";
util.logger.silly(logLine);
switch (binding) {
case 'new': new Pane().setCurrent(); return false;
case 'new': new Pane({parent: self}).setCurrent(); return false;
case 'open':
if (!self.fileBrowser.visible) {
self.fileBrowser.show();
@ -178,7 +177,7 @@ Slap.prototype._initHandlers = function () {
}
});
self.on('mouse', function (mouseData) { logger.silly("mouse", mouseData); });
self.on('mouse', function (mouseData) { util.logger.silly("mouse", mouseData); });
['element blur', 'element focus'].forEach(function (evt) {
self.on(evt, function (el) {
@ -188,19 +187,19 @@ Slap.prototype._initHandlers = function () {
self.on('element show', function (el) { if (el instanceof Pane) self.header.render(); });
['resize', 'element focus', 'insertMode'].forEach(function (evt) {
self.on(evt, function () { self.render(); });
['resize', 'element focus'].forEach(function (evt) {
self.on(evt, function () { self.screen.render(); });
});
return self._initPlugins().then(function () {
return BaseElement.prototype._initHandlers.apply(self, arguments);
});
self.ready.call('_initPlugins').done();
return BaseWidget.prototype._initHandlers.apply(self, arguments);
};
Slap.prototype._initPlugins = function () {
var self = this;
return util.getUserDir().then(function (userDir) {
return Slap.getUserDir().then(function (userDir) {
return clap({
val: self,
module: require.main,
@ -210,15 +209,19 @@ Slap.prototype._initPlugins = function () {
})
.map(function (obj) {
return obj.promise
.then(function () { logger.info("loaded plugin "+obj.plugin); })
.catch(function (err) { logger.error("failed loading plugin "+obj.plugin+": "+(err.stack || err)); });
.then(function () { util.logger.info("loaded plugin "+obj.plugin); })
.catch(function (err) { util.logger.error("failed loading plugin "+obj.plugin+": "+(err.stack || err)); });
});
};
Slap.getUserDir = function () {
var userDir = util.resolvePath('~/.' + require('../../package').name);
return mkdirp(userDir).return(userDir);
};
module.exports = Slap;
// circular imports
var BaseElement = require('./BaseElement');
var Header = require('./Header');
var FileBrowser = require('./FileBrowser');
var Pane = require('./Pane');

View File

@ -1,80 +0,0 @@
var _ = require('lazy.js');
var extend = require('xtend');
var traverse = require('traverse');
var path = require('path');
var Promise = require('bluebird');
var mkdirp = Promise.promisify(require('mkdirp'));
var package = require('../package');
var boolStrings = {true: true, false: false};
var util = _(require('util')).merge({
clone: extend,
extend: extend,
toArray: function (obj) { return [].slice.call(obj); },
mod: function (n, m) { return ((n % m) + m) % m; },
typeOf: function (val) {
return (val.__proto__.constructor.toString().match(/\w+\s+(\w+)/) || [])[1];
},
getterSetter: function (name, getter, setter) {
getter = getter || _.identity;
setter = setter || _.identity;
return function () {
if (arguments.length) {
var newVal = setter.apply(this, arguments);
this.data[name] = newVal;
this.emit && this.emit(name, getter.call(this, newVal));
return this;
} else {
return getter.call(this, this.data[name]);
}
};
},
parseOpts: function (opts) {
return traverse(opts).map(function (opt) {
if (opt && typeof opt === 'string') {
if (opt in boolStrings) return boolStrings[opt];
var number = Number(opt);
if (number === number) return number; // if (!isNaN(number))
}
return opt;
});
},
resolvePath: function (givenPath) {
if (!givenPath) givenPath = '';
if (givenPath[0] === '~') {
givenPath = path.join(process.platform !== 'win32'
? process.env.HOME
: process.env.USERPROFILE
, givenPath.slice(1));
}
return path.resolve.apply(null, [].slice.call(arguments, 1).concat([givenPath]));
},
getUserDir: function () {
var userDir = util.resolvePath('~/.' + package.name);
return mkdirp(userDir).return(userDir);
},
getBinding: function (bindings, key) {
for (var name in bindings) {
if (bindings.hasOwnProperty(name)) {
var keyBindings = bindings[name];
if (!keyBindings) continue;
if (typeof keyBindings === 'string') keyBindings = [keyBindings];
if (keyBindings.some(function (binding) { return binding === key.full || binding === key.sequence; }))
return name;
}
}
}
}).toObject();
module.exports = util;

View File

@ -1,58 +0,0 @@
//var word = /[^\s.\(\){}\[\]]+/g
var word = /\w+/g
exports.prev = prev
exports.next = next
exports.current = current
exports.wordEnd = wordEnd
function prev(string, i, r) {
r = r || word
r.lastIndex = 0
r.global = true
var _m = null, m = null
do {
_m = m
m = r.exec(string)
} while (m && m.index < i);
if(!m || m.index >= i) return _m
return m
}
function next (string, i, r) {
r = r || word
r.lastIndex = i
r.global = true
var _m = null, m = null
do {
m = r.exec(string)
if(!m) return _m
_m = m
} while (m && m.index > i);
return r.exec(string)
}
function current (string, i, r) {
r = r || word
r.lastIndex = i
r.global = true
var m
do {
m = r.exec(string)
if(!m) return null
//take the first match ends after this position.
//console.error(m, i, '<', m.index + m[0].length)
if(i < m.index + m[0].length)
return m
} while (m);
}
function wordEnd (string, i, r) {
var m = current(string, i, r)
return m && m.index + m[0].length
}

View File

@ -1,6 +1,6 @@
{
"name": "slap",
"version": "0.1.32",
"version": "0.1.37",
"description": "Sublime-like terminal-based text editor",
"preferGlobal": true,
"main": "lib/cli.js",
@ -10,8 +10,8 @@
"scripts": {
"start": "./slap.js",
"debug": "node-debug ./slap.js",
"test": "test/index.js",
"cover": "istanbul cover test/index.js"
"test": "spec/index.js",
"cover": "istanbul cover spec/index.js"
},
"repository": {
"type": "git",
@ -21,34 +21,26 @@
"author": "",
"license": "MIT",
"dependencies": {
"base-widget": "^1.0.0",
"blessed": "^0.1.60",
"bluebird": "^2.3.6",
"cheerio": "^0.19.0",
"es6-set": "^0.1.1",
"highlight.js": "^8.2.0",
"iconv-lite": "^0.4.4",
"lazy.js": "^0.4.0",
"editor-widget": "^1.0.0",
"lodash": "^3.9.3",
"longjohn": "^0.2.4",
"mkdirp": "^0.5.0",
"node-clap": "^0.0.5",
"rc": "^1.0.0",
"slap-clipboard-plugin": "^0.0.10",
"text-buffer": "~6.1.3",
"traverse": "^0.6.6",
"slap-clipboard-plugin": "^0.0.12",
"slap-util": "^1.0.1",
"update-notifier": "^0.5.0",
"winston": "^1.0.0",
"xtend": "^4.0.0"
"winston": "^1.0.0"
},
"devDependencies": {
"get-random-port": "0.0.1",
"istanbul": "^0.3.5",
"node-inspector": "^0.10.0",
"npm": "^2.7.4",
"tape": "^2.12.3",
"through2": "^0.6.3"
"tape": "^2.12.3"
},
"engines": {
"node": "<0.12"
"node": "<0.11 >=0.12.5"
}
}

172
slap.ini
View File

@ -7,22 +7,6 @@
[slap]
fullUnicode = false
[editor]
pageLines = 10
doubleClickDuration = 600
defaultEncoding = "UTF-8"
highlight = true
[editor.buffer]
useSpaces = true
tabSize = 2
visibleWhiteSpace = false
visibleLineEndings = false
[editor.gutter]
width = 6
lineNumberWidth = 4
[header]
messageDuration = 5000
blinkRate = 500
@ -31,18 +15,6 @@ headerPosition = "top"
[fileBrowser]
width = 12
[editor.buffer.cursorPadding]
top = 2
left = 2
right = 2
bottom = 2
[field.buffer.cursorPadding]
left = 2
right = 2
top = 0
bottom = 0
[logger]
level = "info"
@ -70,97 +42,6 @@ close = "C-w"
find = "C-f"
goLine = "C-g"
[editor.bindings]
goLeft = "left"
goLeftWord[] = "C-left"
goLeftWord[] = "M-left"
goLeftWord[] = "\u001b\u001b[D"
goLeftWord[] = "M-b"
goLeftWord[] = "M-S-b"
goLeftInfinity[] = "home"
goLeftInfinity[] = "C-a"
goRight = "right"
goRightWord[] = "C-right"
goRightWord[] = "M-right"
goRightWord[] = "\u001b\u001b[C"
goRightWord[] = "M-f"
goRightWord[] = "M-S-f"
goRightInfinity[] = "end"
goRightInfinity[] = "C-e"
; must be above any "up"
goUpParagraph[] = "\u001b\u001b[A"
goUp = "up"
goUpParagraph[] = "C-up"
goUpParagraph[] = "M-up"
goUpParagraph[] = "M-{"
goUpPage = "pageup"
goUpInfinity[] = "C-home"
goUpInfinity[] = "M-home"
goUpInfinity[] = "M-<"
; must be above any "down"
goDownParagraph[] = "\u001b\u001b[B"
goDown = "down"
goDownParagraph[] = "C-down"
goDownParagraph[] = "M-down"
goDownParagraph[] = "M-}"
goDownPage = "pagedown"
goDownInfinity[] = "C-end"
goDownInfinity[] = "M-end"
goDownInfinity[] = "M->"
goMatchingBracket[] = "C-m"
goMatchingBracket[] = "C-]"
goMatchingBracket[] = "\u001d"
; selectAll = "C-a" ; "C-a" used for goLeftInfinity
selectLeft = "S-left"
selectLeftWord[] = "C-S-left"
selectLeftWord[] = "M-S-left"
selectLeftInfinity = "S-home"
selectRight = "S-right"
selectRightWord[] = "C-S-right"
selectRightWord[] = "M-S-right"
selectRightInfinity = "S-end"
selectUp = "S-up"
selectUpParagraph[] = "C-S-up"
selectUpParagraph[] = "M-S-up"
selectUpPage = "S-pageup"
selectUpInfinity[] = "C-S-home"
selectUpInfinity[] = "M-S-home"
selectDown = "S-down"
selectDownParagraph[] = "C-S-down"
selectDownParagraph[] = "M-S-down"
selectDownPage = "S-pagedown"
selectDownInfinity[] = "C-S-end"
selectDownInfinity[] = "M-S-end"
selectMatchingBracket = "C-S-m"
deleteLeft = "backspace"
deleteRight = "delete"
deleteLeftWord[] = "C-backspace"
deleteLeftWord[] = "M-backspace"
deleteLeftWord[] = "C-d"
deleteLeftWord[] = "M-delete"
deleteRightWord[] = "C-delete"
deleteRightWord[] = "M-d"
deleteLeftInfinity[] = "C-S-backspace"
deleteLeftInfinity[] = "M-S-backspace"
deleteRightInfinity[] = "C-S-delete"
deleteRightInfinity[] = "M-S-delete"
deleteLine = "C-k"
duplicateLine = "C-b"
indent[] = "tab"
indent[] = "C-tab"
dedent = "S-tab"
undo = "C-z"
redo = "C-y"
focusNext = false
focusPrev = false
[field.bindings]
submit = "enter"
[form.bindings]
cancel = "escape"
@ -172,12 +53,6 @@ prev = "up"
[dialog.bindings]
hide = "escape"
[element.bindings]
focusNext[] = "tab"
focusNext[] = "right"
focusPrev[] = "S-tab"
focusPrev[] = "left"
;;;;;;;;;;
; Styles ;
;;;;;;;;;;
@ -207,52 +82,9 @@ bold = true
[dialog.style]
bg = "magenta"
[editor.style]
selection = "{cyan-bg}"
match = "{yellow-bg}"
matchingBracket = "{green-bg}{bold}"
mismatchedBracket = "{red-bg}{bold}"
whiteSpace = "{magenta-fg}"
keyword = "{red-fg}"
built_in = "{yellow-fg}"
preprocessor = "{red-fg}"
title = "{underline}"
params = "{bold}"
class = ""
function = ""
decorator = "{bold}"
shebang = "{yellow-bg}{black-fg}"
variable = "{yellow-fg}"
operator = "{green-fg}"
subst = ""
number = "{green-fg}{bold}"
string = "{green-fg}{bold}"
regexp = "{green-fg}{bold}"
literal = "{green-fg}{bold}"
comment = "{white-bg}{black-fg}"
header = "{bold}"
strong = "{bold}"
code = "{green-fg}"
link_label = ""
link_url = "{yellow-fg}"
bullet = "{blue-fg}"
attribute = ""
value = ""
setting = ""
label = ""
symbol = ""
constant = ""
[editor.gutter.style]
bg = "magenta"
currentLine = "{white-bg}{magenta-fg}{bold}"
[fileBrowser]
selectedBg = "blue"
[field.style]
default = "{underline}"
[button.style]
bg = "white"
fg = "black"
@ -277,9 +109,5 @@ bg = "red"
[perf]
profile = false
[editor.perf]
matchesRenderThrottle = 150
updateContentThrottle = 16
[form.find.perf]
findThrottle = 150

View File

@ -3,29 +3,19 @@
var test = require('tape');
var Promise = require('bluebird');
var through2 = require('through2');
var fs = require('fs');
var util = require('base-widget/spec/util');
var cli = require('../lib/cli');
var Slap = require('../lib/ui/Slap');
test("cli", function (t) {
var input = through2();
input.setRawMode = function () {};
input.isTTY = true;
var output = through2();
output.isTTY = true;
Promise.using(cli({input: input, output: output}), function (slap) {
Promise.using(cli({screen: util.screenFactory()}), function (slap) {
t.test("should create an instance of slap", function (st) {
st.plan(1);
st.ok(slap instanceof Slap);
});
require('./Editor')(t)(slap);
return new Promise(function (resolve) { t.on('end', resolve); });
}).done();
});

View File

@ -1,5 +1,4 @@
#!/usr/bin/env node
/*global require*/
require('./textUtil');
require('./cli');

View File

@ -1,36 +0,0 @@
/*global require, global*/
var fs = require('fs');
var path = require('path');
var Pane = require('../lib/ui/Pane');
module.exports = function (t) {
return function (slap) {
t.test("Editor", function (st) {
var pane = new Pane();
var editor = pane.editor;
st.test(".open", function (sst) {
sst.test("should open a file with perms 000 correctly", function (ssst) {
ssst.plan(1);
var perms000File = path.resolve(__dirname, 'fixtures/perms-000');
// can't be checked in with 000 perms
var originalPerms = (fs.statSync(perms000File).mode.toString(8).match(/[0-7]{3}$/) || [])[0] || '644';
fs.chmodSync(perms000File, '000');
editor.open(perms000File)
.then(function () {
ssst.equal(editor.textBuf.getText(), '');
})
.finally(function () { fs.chmodSync(perms000File, originalPerms); })
.done();
});
sst.end();
});
st.end();
});
};
};

View File

@ -1 +0,0 @@
{"web-app":{"servlet":[{"servlet-name":"cofaxCDS","servlet-class":"org.cofax.cds.CDSServlet","init-param":{"configGlossary:installationAt":"Philadelphia, PA","configGlossary:adminEmail":"ksm@pobox.com","configGlossary:poweredBy":"Cofax","configGlossary:poweredByIcon":"/images/cofax.gif","configGlossary:staticPath":"/content/static","templateProcessorClass":"org.cofax.WysiwygTemplate","templateLoaderClass":"org.cofax.FilesTemplateLoader","templatePath":"templates","templateOverridePath":"","defaultListTemplate":"listTemplate.htm","defaultFileTemplate":"articleTemplate.htm","useJSP":false,"jspListTemplate":"listTemplate.jsp","jspFileTemplate":"articleTemplate.jsp","cachePackageTagsTrack":200,"cachePackageTagsStore":200,"cachePackageTagsRefresh":60,"cacheTemplatesTrack":100,"cacheTemplatesStore":50,"cacheTemplatesRefresh":15,"cachePagesTrack":200,"cachePagesStore":100,"cachePagesRefresh":10,"cachePagesDirtyRead":10,"searchEngineListTemplate":"forSearchEnginesList.htm","searchEngineFileTemplate":"forSearchEngines.htm","searchEngineRobotsDb":"WEB-INF/robots.db","useDataStore":true,"dataStoreClass":"org.cofax.SqlDataStore","redirectionClass":"org.cofax.SqlRedirection","dataStoreName":"cofax","dataStoreDriver":"com.microsoft.jdbc.sqlserver.SQLServerDriver","dataStoreUrl":"jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon","dataStoreUser":"sa","dataStorePassword":"dataStoreTestQuery","dataStoreTestQuery":"SET NOCOUNT ON;select test=\'test\';","dataStoreLogFile":"/usr/local/tomcat/logs/datastore.log","dataStoreInitConns":10,"dataStoreMaxConns":100,"dataStoreConnUsageLimit":100,"dataStoreLogLevel":"debug","maxUrlLength":500}},{"servlet-name":"cofaxEmail","servlet-class":"org.cofax.cds.EmailServlet","init-param":{"mailHost":"mail1","mailHostOverride":"mail2"}},{"servlet-name":"cofaxAdmin","servlet-class":"org.cofax.cds.AdminServlet"},{"servlet-name":"fileServlet","servlet-class":"org.cofax.cds.FileServlet"},{"servlet-name":"cofaxTools","servlet-class":"org.cofax.cms.CofaxToolsServlet","init-param":{"templatePath":"toolstemplates/","log":1,"logLocation":"/usr/local/tomcat/logs/CofaxTools.log","logMaxSize":"","dataLog":1,"dataLogLocation":"/usr/local/tomcat/logs/dataLog.log","dataLogMaxSize":"","removePageCache":"/content/admin/remove?cache=pages&id=","removeTemplateCache":"/content/admin/remove?cache=templates&id=","fileTransferFolder":"/usr/local/tomcat/webapps/content/fileTransferFolder","lookInContext":1,"adminGroupID":4,"betaServer":true}}],"servlet-mapping":{"cofaxCDS":"/","cofaxEmail":"/cofaxutil/aemail/*","cofaxAdmin":"/admin/*","fileServlet":"/static/*","cofaxTools":"/tools/*"},"taglib":{"taglib-uri":"cofax.tld","taglib-location":"/WEB-INF/tlds/cofax.tld"}}}

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
perms-000

View File

@ -1,26 +0,0 @@
#!/usr/bin/env node
/*global require, global*/
var test = require('tape');
var textUtil = require('../lib/textUtil');
test("textUtil", function (t) {
t.test(".splitLines", function (st) {
var text = "This is a line.\rThis is another line.\r\nHere's a third line.\n";
var lines = textUtil.splitLines(text);
st.test("should split on varying line endings correctly", function (sst) {
sst.plan(4);
sst.equal(lines[0], "This is a line.\r");
sst.equal(lines[1], "This is another line.\r\n");
sst.equal(lines[2], "Here's a third line.\n");
sst.equal(lines[3], "");
});
st.test("should join back together to restore original string", function (sst) {
sst.plan(1);
sst.equal(lines.join(''), text);
});
});
});