vorpal icon indicating copy to clipboard operation
vorpal copied to clipboard

Multiple Instances of Vorpal / Switching

Open mke66djx opened this issue 7 years ago • 24 comments

Hi, I've been working on the issue of multiple instances for a while now and it seems no matter what I do I do not get what seems to be separate instances of Vorpal. What I'm trying to accomplish is having multiple instances of vorpal that use a different set of commands(via use) and being able to switch through them.

In Case-1 below I try an example #56 given by dhtree as a solution to switching via multiple instances- I experience a lot of unexpected behavior, one of them being issues with tab-tab to get list of commands, and same for autocomplete using autocomplete-fs. It will return the commands 4 times for each tab-tab or as well as the autocomplete-fs directory results. Which suggests to me that there is only one instance being created of Vorpal. Even without the autocomplete, a regular tab will return 4 sets of commands. Aside from this behavior, command history also does not work properly when switching for me - the history is shared between the instances or new history is not recorded due to some unknown conflict.

In Case-2 which is another example given by dhtree the results are same as above, to me, suggesting there are not two seperate instances of vorpal but shared.

I would even take working with one instance of Vorpal and changing the commands it uses but I do not know of any way to remove .use from the instance to remove commands you have already issued .use(commands) on. The only downside of this is that it would not have separate attributes like autocomplete history and possibly others.

I'm really hoping I'm missing something - any ideas or solutions would be greatly appreciated!

Case-1 const Vorpal = require('vorpal'); const mainVorpal = new Vorpal(); const path = require('path'); const fsAutocomplete = require('vorpal-autocomplete-fs');

mainVorpal .delimiter('X:') .use(swap) .show();

function swap(mainVorpal) { mainVorpal.command('swap') .autocomplete(fsAutocomplete()) .action(function (args, cb) { instances[args.instance].show(); cb(); }); return mainVorpal; }

var instances = { 'a': new Vorpal().use(swap).delimiter('a:'), 'b': new Vorpal().use(swap).delimiter('b:'), 'c': new Vorpal().use(swap).delimiter('c:'), }

Case-2 function swap(mainVorpal) { mainVorpal.command('do <instance>') .action(function (args, cb) { console.log("walk"); cb(); }); return mainVorpal; }

function swap2(mainVorpal) { mainVorpal.command('say <instance>') .action(function (args, cb) { console.log("hello"); cb(); }); return mainVorpal; }

const Vorpal = require('vorpal'); const chalk = Vorpal().chalk;

const unicorns = new Vorpal() .delimiter(chalk.magenta('unicorn-land~$')) .use(swap) .history('unicorn-command-history'); .show()

const narwhals = new Vorpal() .delimiter(chalk.cyan('narwhal-land~$')) .use(swap2) .history('narwhal-command-history');

mke66djx avatar Jun 15 '17 01:06 mke66djx

I was working with a similar idea. I wanted to use multiple instances of Vorpal in a stacked fashion. You could open up a vorpal instance within another vorpal instance.

I managed to get it working in my playground repo. It has the ability to jump to another app and jump back to the main app. Hopefully, something like this will fit your needs.

There are some issues I've found with it though. More specifically, old event listeners not being removed when you call the show method on your new Vorpal instance #230.

Genide avatar Jun 15 '17 11:06 Genide

Yes, there appears to be some issues with this, at least in the more recent builds.

dthree avatar Jun 15 '17 17:06 dthree

Hi @dthree, is there any older build that you think this might work with? I'll try to resolve this myself but I'm fresh to node and it will probably take me months. If not multiple instances, do you know of any way to remove use(commands) to change the group of commands being used?

mke66djx avatar Jun 15 '17 17:06 mke66djx

Hi, this is hack, which is working for me (vorpal v1.12.0).

Insert is before first use of vorpal.

// == BEGIN OF VORPAL HACK FOR WORKING MULTIPLE INSTANCES OF VORPAL ==
require('vorpal').prototype._init = function () {
    let self = this;

    self.ui.on('vorpal_ui_keypress', function (data) {
        if (self === self.ui.parent) {
            self.emit('keypress', data);
            self._onKeypress(data.key, data.value);
        }
    });

    self.use(require('vorpal/dist/vorpal-commons'));
};
// == END OF VORPAL HACK FOR WORKING MULTIPLE INSTANCES OF VORPAL ==

I tried to make modifcation and pull-request to dthree/vorpal, but in master isn't last and working version of vorpal.

hradyho avatar Jun 21 '17 08:06 hradyho

Same issue here , when pressing [tab tab] it will gives the whole commands of activated instances.like it wont detach and they stack together !

will be any fix for this ?

omidnavy avatar Aug 06 '17 09:08 omidnavy

@hradyho Hi David Can you please explain it a little more with a morel lines of code , I tried the hack you mentioned and i go the error "Cannot read property 'show' of undefined" , also used the fork in your repository , well still the same error + when i press tab to see help , its only a 'undefined' there. can you please help me out with this ?

omidnavy avatar Aug 13 '17 12:08 omidnavy

Not too sure if this is the answer you are looking for - here are some snippets of how I'm using vorpal to run separate commands over internal modules. side note: You can probably just ignore chalk & I have omitted some of the unneeded functions.

index.js

/**
 * CORE COMMAND LINE INTERFACE FILE
 */

// CONSTANTS
/**
 * Vorpal - Command Line Interface library
 * Docs https://github.com/dthree/vorpal/wiki
 */
const Cli = require('vorpal')

/**
 * Chalk - colour and style the command line text
 * Docs https://github.com/chalk/chalk
 */
const chalk = require('chalk')

/**
 * Lodash JS utility
 * Docs https://lodash.com/docs/
 */
const _ = require('lodash')

/**
 * The path helper functions
 */
const pathTo = require('../library/pathHelper')

/**
 * The module helper functions
 */
const moduleHelper = require('../library/moduleHelper')

/**
 * Declare the empty module Cli's object
 * to house the module Cli instances
 */
const modules = {}

// DELIMITER
/**
 * The project name as the delimiter
 */
const defaultDelimiter = 'myAppCli'

/**
 * The active delimiter value
 * @param {*delimiter as a string (module name)} string 
 */
let activeDelimiter = string => `${string}~$`

/**
 * The parent delimiter - this should always be the 
 * application name, unless deeper levels are required
 */
let parentDelimiter = () => chalk.green.bold(`${defaultDelimiter}/`)

// COMMAND LINE INTERFACE
/**
 * Create the root cli
 */
const cli = new Cli()

/**
 * Show the base delimiter
 */
cli.delimiter(chalk.green.bold(activeDelimiter(defaultDelimiter))).show()

/**
 * Command to chane into a modules Cli and functionality
 */
cli
	.command('cd <module>', 'Enter a module\'s specific cli')
	.action(function (args, cb) {
		// Test to see if the module exists
		(_.has(modules, [args.module]))
			// Yes, load the module's cli
			? modules[args.module].show()
			// No, would you like to create the module?
			: checkCreateModule(args.module)
		cb()
	})

// MODULES
/**
 * Load the individual module Cli instances
 * @param {*the module name as a string} module
 */
let load = module => {
	modules[module] = new Cli()
	modules[module]
		.delimiter(parentDelimiter() + chalk.red.bold(activeDelimiter(module)))
		.use(require(pathTo.modulesCommands(module)))
		.command('cd ..', 'Back to parent') // a shared function across all modules - return to root
		.action(function (args, cb) {
			cli.show()
			cb()
		})
}

/**
 * Iterate over all the modules and load their cli instance
 */
moduleHelper
	.listModules()
	.then(moduleList => {
		moduleList.forEach(module => load(module))
	})
	.catch(error => {
		console.log(chalk.bgRed.bold(error))
		cli.show()
	})

moduleSpecificCommands.js - @ your custom location

/**
 * CUSTOM CLI COMMANDS
 */

//  Functions
/**
 * Repeat the input to the console using the standard Vorpal callback :|
 * @param {*command args as an object} args 
 * @param {*the compeltion callback} cb 
 */
let echo = (args, cb) => {
  console.log(args.string)
  cb()
}

// Commands
module.exports = cli => {
  cli.command('echo <string>', 'An example command. Prints a given string to the console.').action(echo)
}

benluxford avatar Aug 13 '17 12:08 benluxford

@project-es Hi Ben , Thanks for your answer , but can you give me the pathHelper and moduleHelper too ? or is there any external library for them ? ah and I forgot to say , I'm a noob here in node js and trying to learn by projects. could you please help me run this part of code you gave me ?

Thanks

omidnavy avatar Aug 14 '17 12:08 omidnavy

@omidnavi Hi Omid, sure can - they are just simple resolver functions to give the absolute path to the module & to load the module names into an array, as they are specific to my project - it might be easier If I edit the code for you.

I have created a small repo here - https://github.com/project-es/vorpal-scratch hope it helps :)

p.s. I'm kinda new to node too

benluxford avatar Aug 14 '17 12:08 benluxford

@project-es Thanks I'll have to check them.

omidnavy avatar Aug 14 '17 13:08 omidnavy

@project-es Ben , can you please check the repo yourself , just run it and press "tab" or "tab tab" for the help andit will print 4 lines , the whole instances commands ! cd exit help quit YourAwesomeApp~$ cd .. exit help m1 echo quit YourAwesomeApp~$ cd .. exit help m2 echo quit YourAwesomeApp~$ cd .. exit help m3 echo quit YourAwesomeApp~$

omidnavy avatar Aug 15 '17 06:08 omidnavy

@omidnavi Hey, no probs, I misunderstood the entirety of the question..

This solution is fairly hacky - but it works and I don't see why you couldn't use it reliably - You can just unset the commands on the vorpal instance e.g.

vorpal.commands = []

You have to set it to an empty array due to an internal vorpal forEach call. I imagine that you can do this for other values too.

Repo has been updated and tested - working as far as I can tell

benluxford avatar Aug 15 '17 07:08 benluxford

I should add - the commands seem to be held in memory - meaning, in the scripts I have provided, you can NOT just unset the vorpal instance and hope for the commands to go e.g.

cli = {}

The commands still show - re declaring the commands value on the vorpal {object} you are moving away from seems to be the easiest way. Its what I came to in the little time I have spent on it, good luck :)

--- I cleared the duplicate code mess in index.js

benluxford avatar Aug 15 '17 07:08 benluxford

@project-es Thanks Ben , everything seems working fine now ,I appreciate your help.

omidnavy avatar Aug 15 '17 08:08 omidnavy

@project-es Hi Ben , um sorry but I've got this question that can I have nested instances ? like Main -> Sub -> Sub.Sub -> Sub.Sub.Sub -> Command it should be possible but I cant figure it out how may i reach it

omidnavy avatar Aug 19 '17 12:08 omidnavy

If you're using the repo I shared you will have to store the delimiter value as a string "to represent the levels" - that's just for looks tho. As for functionality you will most likely need to store a "history" of sorts, maybe an array of module names so that you can switch back through the levels with cd .. (eg as you go a level deeper add the active module name to a history array. as you return a level load module x as it's last in the array, then remove it from the array)

Most importantly tho, the code I shared has modules as kind of "Top level" elements, you will probably need to define a "nested" file structure and write your own functions to retrieve the nested module names and command files.

Hope this helps.

benluxford avatar Aug 19 '17 12:08 benluxford

Yes i am using your repo. You mean i have to have almost same code as the index.js in the modules(eg module1) with same file structure? is that true ?

omidnavy avatar Aug 19 '17 13:08 omidnavy

The repo I shared grabs all the folder names in the modules directory then adds them as modules. If you used the same code you would only be able to have module/submodule folders you would probably have to do something like modules/module1/submodules/submodule1 so that you can loop over the modules and the submodules folder within each module. There are probably other ways, this was the first that came to mind.

benluxford avatar Aug 19 '17 13:08 benluxford

Thanks Ben , I should check all the possible ways to figure it out and understand

omidnavy avatar Aug 19 '17 13:08 omidnavy

@project-es Sorry Ben for bothering you this much , but Do you think should I change the pathHelper module ?

omidnavy avatar Aug 20 '17 06:08 omidnavy

@project-es I'm really confused you know , I know how to do with this project but I really cant tune this for myself , You did it and that was a great help. I've learned a bit es6 by doing react projects but this node is something different for me , I dont know but I'm again asking for help , Can you please show me or help me out of this situation ?

omidnavy avatar Aug 20 '17 07:08 omidnavy

@omidnavi sure, I can have a little look at it, probably not till tomorrow sometime.

benluxford avatar Aug 20 '17 07:08 benluxford

@project-es Thats great , Thanks

omidnavy avatar Aug 20 '17 08:08 omidnavy

I guess what I don't understand about using separate vorpal instances in the same Node.js process, is how do you multiplex process.stdin to two different vorpal instances? Or maybe you don't?

ORESoftware avatar Nov 06 '17 01:11 ORESoftware