421 lines
11 KiB
JavaScript
421 lines
11 KiB
JavaScript
/**
|
|
** ==============================
|
|
** O O O OOOO
|
|
** O O O O O O
|
|
** O O O O O O
|
|
** OOOO OOOO O OOO OOOO
|
|
** O O O O O O O
|
|
** O O O O O O O
|
|
** OOOO OOOO O O OOOO
|
|
** ==============================
|
|
** Dr. Stefan Bosse http://www.bsslab.de
|
|
**
|
|
** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
|
|
** BY THE AUTHOR(S).
|
|
** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
|
|
** MODIFIED, OR OTHERWISE USED IN A CONTEXT
|
|
** OUTSIDE OF THE SOFTWARE SYSTEM.
|
|
**
|
|
** $AUTHORS: Christopher Jeffrey and contributors, Stefan Bosse
|
|
** $INITIAL: (C) 2013-2018, Christopher Jeffrey and contributors
|
|
** $VERSION: 1.4.2
|
|
**
|
|
** $INFO:
|
|
*
|
|
* filemanager.js - file manager element for blessed
|
|
*
|
|
* Events: 'ioerror','cd','file'
|
|
*
|
|
* New options: okayButton, cancelButton, autohide, select (select emits file event),
|
|
* noshow, arrows: {up:'[-]',down:'[+]',width:3,height:1,fg:'red',bg:'default'}},
|
|
* box:{bg,border}, input:{fg,bg,border}
|
|
*
|
|
** $ENDOFINFO
|
|
*/
|
|
|
|
var options = {
|
|
version:'1.4.2'
|
|
}
|
|
/**
|
|
* Modules
|
|
*/
|
|
var Comp = Require('com/compat');
|
|
|
|
var path = Require('path')
|
|
, fs = Require('fs');
|
|
|
|
|
|
var Node = Require('term/widgets/node');
|
|
var List = Require('term/widgets/list');
|
|
var Button = Require('term/widgets/button');
|
|
var Helpers = Require('term/helpers');
|
|
var Box = Require('term/widgets/box');
|
|
var TextBox = Require('term/widgets/textbox');
|
|
var Arrows = Require('term/widgets/arrows');
|
|
var Screen = Require('term/widgets/screen');
|
|
|
|
/**
|
|
* FileManager
|
|
*/
|
|
|
|
function FileManager(options) {
|
|
var self = this,
|
|
bbox,
|
|
off1=0,
|
|
off2=0,
|
|
arrows=options.arrows;
|
|
|
|
if (!instanceOf(this,Node)) {
|
|
return new FileManager(options);
|
|
}
|
|
|
|
options = options || {};
|
|
options.parseTags = true;
|
|
options.mouse = true;
|
|
options.arrows=_; // optional arrows are handled here, not in list
|
|
|
|
if (options.parent == Screen.global) {
|
|
// Screen overlay; adjust top/height settings
|
|
bbox=Helpers.bbox(Screen.global,options);
|
|
if (options.box) bbox.top += 1,bbox.height -= 2,bbox.width -= 4,bbox.left += 2;
|
|
if (options.input) bbox.height -= 3;
|
|
if (options.cancelButton||options.okayButton) bbox.height -= 1;
|
|
if (arrows) bbox.width -= 4,bbox.left += 2;
|
|
options.top=bbox.top;
|
|
options.left=bbox.left;
|
|
options.height=bbox.height;
|
|
options.width=bbox.width;
|
|
}
|
|
|
|
// options.label = ' {blue-fg}%path{/blue-fg} ';
|
|
List.call(this, options);
|
|
|
|
options.arrows=arrows;
|
|
|
|
this.cwd = options.cwd || process.cwd();
|
|
this.file = this.cwd;
|
|
this.value = this.cwd;
|
|
this.noshow = options.noshow;
|
|
|
|
if (options.parent == this.screen) {
|
|
// Collect clickable elements of this widget
|
|
this._clickable=this.screen.clickable;
|
|
this.screen.clickable=[];
|
|
|
|
// compute for button positions
|
|
bbox=Helpers.bbox(this.screen,options);
|
|
if (options.cancelButton||options.okayButton) off1=2;
|
|
if (options.input) off2=3;
|
|
if (options.box)
|
|
this._.box = new Box({
|
|
top:bbox.top-2,
|
|
width:bbox.width+8,
|
|
left:bbox.left-4,
|
|
height:bbox.height+3+off1+off2,
|
|
hidden:options.hidden,
|
|
border:options.box.border,
|
|
style:{
|
|
label:options.label,
|
|
fg:options.box.fg,
|
|
bg:options.box.bg||'white'
|
|
}
|
|
});
|
|
if (this._.box) this.screen.append(this._.box);
|
|
|
|
if (options.input) {
|
|
this._.input = new TextBox({
|
|
screen: this.screen,
|
|
top: bbox.top+bbox.height+(options.box?1:0)-1,
|
|
height: options.input.border&&options.input.border.type=='line'?3:1,
|
|
width: bbox.width,
|
|
left: bbox.left,
|
|
keys: options.input.mutable?true:undefined,
|
|
vi: options.input.mutable?true:undefined,
|
|
mouse: options.input.mutable?true:undefined,
|
|
inputOnFocus: options.input.mutable?true:undefined,
|
|
value: options.input.value||'<input>',
|
|
hidden:options.hidden,
|
|
border: options.input.border,
|
|
style: {
|
|
fg:options.input.fg||'black',
|
|
bg:options.input.bg||'white',
|
|
bold:true
|
|
}
|
|
});
|
|
this.screen.append(this._.input);
|
|
}
|
|
|
|
if (options.okayButton) {
|
|
this._.okay = new Button({
|
|
screen: this.screen,
|
|
top: bbox.top+bbox.height+(options.box?1:0)+off2,
|
|
height: 1,
|
|
left: bbox.left+1,
|
|
width: 10,
|
|
content: options.okayButton,
|
|
align: 'center',
|
|
style: {
|
|
fg:'white',
|
|
bg: 'blue',
|
|
bold:true,
|
|
},
|
|
autoFocus: false,
|
|
hidden:options.hidden,
|
|
mouse: true
|
|
});
|
|
this._.okay.on('press',function () {
|
|
var item=self.items[self.selected],
|
|
value = self._.input?
|
|
self._.input.getValue():
|
|
item.content.replace(/\{[^{}]+\}/g, '').replace(/@$/, ''),
|
|
file=path.resolve(self.cwd, value);
|
|
self.emit('file', file);
|
|
self.hide();
|
|
});
|
|
this.screen.append(this._.okay);
|
|
}
|
|
if (options.cancelButton) {
|
|
this._.cancel = new Button({
|
|
screen: this.screen,
|
|
top: bbox.top+bbox.height+(options.box?1:0)+off2,
|
|
height: 1,
|
|
left: bbox.left+bbox.width-10-1,
|
|
width: 10,
|
|
content: options.cancelButton,
|
|
align: 'center',
|
|
style: {
|
|
fg:'white',
|
|
bg: 'red',
|
|
bold:true,
|
|
},
|
|
autoFocus: false,
|
|
hidden:options.hidden,
|
|
mouse: true
|
|
});
|
|
this._.cancel.on('press',function () { self.hide(); });
|
|
this.screen.append(this._.cancel);
|
|
}
|
|
if (options.arrows)
|
|
Arrows(
|
|
self,
|
|
options,
|
|
function () {self.emit('element wheelup')},
|
|
function () {self.emit('element wheeldown')},
|
|
true
|
|
);
|
|
this._hide=this.hide;
|
|
this.hide = function() {
|
|
self._hide();
|
|
if (self._.box) self._.box.hide();
|
|
if (self._.input) self._.input.hide();
|
|
if (self._.okay) self._.okay.hide();
|
|
if (self._.cancel) self._.cancel.hide();
|
|
if (self._.up) self._.up.hide();
|
|
if (self._.down) self._.down.hide();
|
|
self.screen.render();
|
|
// restore all clickable elements
|
|
self.screen.clickable=self._clickable;
|
|
}
|
|
this._show = this.show;
|
|
this.show = function() {
|
|
// save all screen clickable elements; enable only this clickables
|
|
self._clickable=self.screen.clickable;
|
|
self.screen.clickable=self.clickable;
|
|
self._show();
|
|
if (self._.box) self._.box.show();
|
|
if (self._.input) self._.input.show();
|
|
if (self._.okay) self._.okay.show();
|
|
if (self._.cancel) self._.cancel.show();
|
|
if (self._.up) self._.up.show();
|
|
if (self._.down) self._.down.show();
|
|
self.screen.render();
|
|
}
|
|
|
|
// Save clickable elements of this widget; restore screen
|
|
this.clickable=this.screen.clickable;
|
|
this.screen.clickable=this._clickable;
|
|
}
|
|
if (options.label && ~options.label.indexOf('%path')) {
|
|
this._label.setContent(options.label.replace('%path', this.cwd));
|
|
}
|
|
if (this._.input)
|
|
this.on('selected', function(item) {
|
|
var value = item.content.replace(/\{[^{}]+\}/g, '').replace(/@$/, '');
|
|
if (value.indexOf('/') != -1) value='';
|
|
self._.input.setValue(value);
|
|
self._.input.update();
|
|
});
|
|
|
|
|
|
this.on('select', function(item) {
|
|
var value = item.content.replace(/\{[^{}]+\}/g, '').replace(/@$/, '')
|
|
, file = path.resolve(self.cwd, value);
|
|
return fs.stat(file, function(err, stat) {
|
|
var _cwd=self.cwd;
|
|
if (err) {
|
|
return self.emit('ioerror', err, file);
|
|
}
|
|
self.file = file;
|
|
self.value = file;
|
|
if (stat.isDirectory()) {
|
|
self.cwd = file;
|
|
self.refresh(undefined,function (err) {
|
|
if (err) self.cwd=_cwd;
|
|
else if (options.label && ~options.label.indexOf('%path')) {
|
|
self._label.setContent(options.label.replace('%path', self.cwd));
|
|
self.emit('cd', file, self.cwd);
|
|
self.screen.render();
|
|
}
|
|
});
|
|
} else {
|
|
if (self.options.select) self.emit('file', file);
|
|
if (self.options.select && self.options.autohide) self.hide();
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
}
|
|
|
|
//FileManager.prototype.__proto__ = List.prototype;
|
|
inheritPrototype(FileManager,List);
|
|
|
|
FileManager.prototype.type = 'file-manager';
|
|
|
|
FileManager.prototype.refresh = function(cwd, callback) {
|
|
var self = this;
|
|
|
|
if (cwd) this.cwd = cwd;
|
|
else cwd = this.cwd;
|
|
|
|
return fs.readdir(cwd, function(err, list) {
|
|
if (err && err.code === 'ENOENT') {
|
|
self.cwd = cwd !== process.env.HOME
|
|
? process.env.HOME
|
|
: '/';
|
|
return self.refresh(undefined,callback);
|
|
}
|
|
|
|
if (err) {
|
|
if (callback) return callback(err);
|
|
return self.emit('ioerror', err, cwd);
|
|
}
|
|
|
|
var dirs = []
|
|
, files = [];
|
|
|
|
list.unshift('..');
|
|
|
|
list.forEach(function(name) {
|
|
var f = path.resolve(cwd, name)
|
|
, stat;
|
|
|
|
try {
|
|
stat = fs.lstatSync(f);
|
|
} catch (e) {
|
|
;
|
|
}
|
|
|
|
if ((stat && stat.isDirectory()) || name === '..') {
|
|
dirs.push({
|
|
name: name,
|
|
text: '{light-blue-fg}' + name + '{/light-blue-fg}/',
|
|
dir: true
|
|
});
|
|
} else if (stat && stat.isSymbolicLink()) {
|
|
files.push({
|
|
name: name,
|
|
text: '{light-cyan-fg}' + name + '{/light-cyan-fg}@',
|
|
dir: false
|
|
});
|
|
} else {
|
|
files.push({
|
|
name: name,
|
|
text: name,
|
|
dir: false
|
|
});
|
|
}
|
|
});
|
|
|
|
dirs = Helpers.asort(dirs);
|
|
files = Helpers.asort(files);
|
|
|
|
list = dirs.concat(files).map(function(data) {
|
|
return data.text;
|
|
});
|
|
|
|
self.setItems(list);
|
|
self.select(0);
|
|
self.screen.render();
|
|
|
|
self.emit('refresh');
|
|
|
|
if (callback) callback();
|
|
});
|
|
|
|
};
|
|
|
|
FileManager.prototype.pick = function(cwd, callback) {
|
|
if (!callback) {
|
|
callback = cwd;
|
|
cwd = null;
|
|
}
|
|
var self = this
|
|
, focused = this.screen.focused === this
|
|
, hidden = this.hidden
|
|
, onfile
|
|
, oncancel;
|
|
|
|
function resume() {
|
|
self.removeListener('file', onfile);
|
|
self.removeListener('cancel', oncancel);
|
|
if (hidden) {
|
|
self.hide();
|
|
}
|
|
if (!focused) {
|
|
self.screen.restoreFocus();
|
|
}
|
|
self.screen.render();
|
|
}
|
|
|
|
this.on('file', onfile = function(file) {
|
|
resume();
|
|
return callback(null, file);
|
|
});
|
|
|
|
this.on('cancel', oncancel = function() {
|
|
resume();
|
|
return callback();
|
|
});
|
|
|
|
this.refresh(cwd, function(err) {
|
|
if (err) return callback(err);
|
|
|
|
if (hidden) {
|
|
self.show();
|
|
}
|
|
|
|
if (!focused) {
|
|
self.screen.saveFocus();
|
|
self.focus();
|
|
}
|
|
|
|
self.screen.render();
|
|
});
|
|
};
|
|
|
|
FileManager.prototype.reset = function(cwd, callback) {
|
|
if (!callback) {
|
|
callback = cwd;
|
|
cwd = null;
|
|
}
|
|
this.cwd = cwd || this.options.cwd;
|
|
this.refresh(callback);
|
|
};
|
|
|
|
/**
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = FileManager;
|