Mon 21 Jul 22:43:21 CEST 2025

This commit is contained in:
sbosse 2025-07-21 23:11:47 +02:00
parent 11d1497f94
commit 50598220a3

View File

@ -0,0 +1,420 @@
/**
** ==============================
** 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;