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