Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add fish.ts & bash.ts #19

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 65 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ Shell autocompletions are largely missing in the javascript cli ecosystem. This

Tools like git and their autocompletion experience inspired us to build this tool and make the same ability available for any javascript cli project. Developers love hitting the tab key, hence why they prefer tabs over spaces.

## Examples

Check out the [examples directory](./examples) for complete examples of using Tab with different command-line frameworks:

- [CAC](./examples/demo.cac.ts)
- [Citty](./examples/demo.citty.ts)
- [Commander.js](./examples/demo.commander.ts)

## Usage

```ts
import { Completion, script } from '@bombsh/tab';

Expand Down Expand Up @@ -146,6 +156,47 @@ const cli = createMain(main);
cli();
```

### `@bombsh/tab/commander`

```ts
import { Command } from 'commander';
import tab from '@bombsh/tab/commander';

const program = new Command('my-cli');
program.version('1.0.0');

// Add commands
program
.command('serve')
.description('Start the server')
.option('-p, --port <number>', 'port to use', '3000')
.option('-H, --host <host>', 'host to use', 'localhost')
.action((options) => {
console.log('Starting server...');
});

// Initialize tab completion
const completion = tab(program);

// Configure custom completions
for (const command of completion.commands.values()) {
if (command.name === 'serve') {
for (const [option, config] of command.options.entries()) {
if (option === '--port') {
config.handler = () => {
return [
{ value: '3000', description: 'Default port' },
{ value: '8080', description: 'Alternative port' },
];
};
}
}
}
}

program.parse();
```

## Recipe

`source <(my-cli complete zsh)` won't be enough since the user would have to run this command each time they spin up a new shell instance.
Expand All @@ -157,6 +208,20 @@ my-cli completion zsh > ~/completion-for-my-cli.zsh
echo 'source ~/completion-for-my-cli.zsh' >> ~/.zshrc
```

For other shells:

```bash
# Bash
my-cli complete bash > ~/.bash_completion.d/my-cli
echo 'source ~/.bash_completion.d/my-cli' >> ~/.bashrc

# Fish
my-cli complete fish > ~/.config/fish/completions/my-cli.fish

# PowerShell
my-cli complete powershell > $PROFILE.CurrentUserAllHosts
```

## Autocompletion Server

By integrating tab into your cli, your cli would have a new command called `complete`. This is where all the magic happens. And the shell would contact this command to get completions. That's why we call it the autocompletion server.
Expand All @@ -181,8 +246,3 @@ Other package managers like `npm` and `yarn` can decide whether to support this

- git
- [cobra](https://github.com/spf13/cobra/blob/main/shell_completions.go), without cobra, tab would have took 10x longer to build

## TODO

- [] fish
- [] bash
128 changes: 0 additions & 128 deletions demo.citty.ts

This file was deleted.

2 changes: 1 addition & 1 deletion demo.cac.ts → examples/demo.cac.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import cac from 'cac';
import tab from './src/cac';
import tab from '../src/cac';

const cli = cac('vite');

Expand Down
132 changes: 132 additions & 0 deletions examples/demo.citty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { defineCommand, createMain } from 'citty';
import tab from '../src/citty';

const main = defineCommand({
meta: {
name: 'vite',
version: '0.0.0',
description: 'Vite CLI',
},
args: {
config: {
type: 'string',
description: 'Use specified config file',
alias: 'c',
},
mode: {
type: 'string',
description: 'Set env mode',
alias: 'm',
},
logLevel: {
type: 'string',
description: 'info | warn | error | silent',
alias: 'l',
},
},
subCommands: {
dev: defineCommand({
meta: {
name: 'dev',
description: 'Start dev server',
},
args: {
host: {
type: 'string',
description: 'Specify hostname',
alias: 'H',
},
port: {
type: 'string',
description: 'Specify port',
alias: 'p',
},
},
run: () => {},
}),
build: defineCommand({
meta: {
name: 'build',
description: 'Build project',
},
run: () => {},
}),
lint: defineCommand({
meta: {
name: 'lint',
description: 'Lint project',
},
args: {
files: {
type: 'positional',
description: 'Files to lint',
required: false,
},
},
run: () => {},
}),
},
run: () => {},
});

const completion = await tab(main);

for (const command of completion.commands.values()) {
if (command.name === 'lint') {
command.handler = () => {
return [
{ value: 'main.ts', description: 'Main file' },
{ value: 'index.ts', description: 'Index file' },
];
};
}

for (const [o, config] of command.options.entries()) {
if (o === '--port') {
config.handler = () => {
return [
{ value: '3000', description: 'Development server port' },
{ value: '8080', description: 'Alternative port' },
];
};
}
if (o === '--host') {
config.handler = () => {
return [
{ value: 'localhost', description: 'Localhost' },
{ value: '0.0.0.0', description: 'All interfaces' },
];
};
}
if (o === '--config') {
config.handler = () => {
return [
{ value: 'vite.config.ts', description: 'Vite config file' },
{ value: 'vite.config.js', description: 'Vite config file' },
];
};
}
if (o === '--mode') {
config.handler = () => {
return [
{ value: 'development', description: 'Development mode' },
{ value: 'production', description: 'Production mode' },
];
};
}
if (o === '--logLevel') {
config.handler = () => {
return [
{ value: 'info', description: 'Info level' },
{ value: 'warn', description: 'Warn level' },
{ value: 'error', description: 'Error level' },
{ value: 'silent', description: 'Silent level' },
];
};
}
}
}

// Create the CLI and run it
const cli = createMain(main);
cli();
16 changes: 4 additions & 12 deletions demo.commander.ts → examples/demo.commander.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Command } from 'commander';
import tab from './src/commander';
import tab from '../src/commander';

// Create a new Commander program
const program = new Command('myapp');
Expand Down Expand Up @@ -111,17 +111,9 @@ for (const command of completion.commands.values()) {
if (process.argv[2] === 'test-completion') {
const args = process.argv.slice(3);
console.log('Testing completion with args:', args);

// Special case for deploy command with a space at the end
if (args.length === 1 && args[0] === 'deploy ') {
console.log('staging Deploy to staging environment');
console.log('production Deploy to production environment');
console.log(':2');
} else {
completion.parse(args).then(() => {
// Done
});
}
completion.parse(args).then(() => {
// Done
});
} else {
// Parse command line arguments
program.parse();
Expand Down
Loading