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 bash completion #241

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ nr -
# rerun the last command
```

```bash
nr --completion >> ~/.bashrc

# add completion script to your shell (only bash supported for now)
```

<br>

### `nlx` - download & execute
Expand Down
66 changes: 49 additions & 17 deletions src/commands/nr.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,64 @@
import type { Choice } from '@posva/prompts'
import type { RunnerContext } from '../runner'
import process from 'node:process'
import prompts from '@posva/prompts'
import { byLengthAsc, Fzf } from 'fzf'
import { rawCompletionScript } from '../completion'
import { getPackageJSON } from '../fs'
import { parseNr } from '../parse'
import { runCli } from '../runner'
import { dump, load } from '../storage'
import { limitText } from '../utils'

function readPackageScripts(ctx: RunnerContext | undefined) {
// support https://www.npmjs.com/package/npm-scripts-info conventions
const pkg = getPackageJSON(ctx)
const scripts = pkg.scripts || {}
const scriptsInfo = pkg['scripts-info'] || {}

return Object.entries(scripts)
.filter(i => !i[0].startsWith('?'))
.map(([key, cmd]) => ({
key,
cmd,
description: scriptsInfo[key] || scripts[`?${key}`] || cmd,
}))
}

runCli(async (agent, args, ctx) => {
const storage = await load()

// Use --completion to generate completion script and do completion logic
// (No package manager would have an argument named --completion)
if (args[0] === '--completion') {
const compLine = process.env.COMP_LINE
const rawCompCword = process.env.COMP_CWORD
if (compLine !== undefined && rawCompCword !== undefined) {
const compCword = Number.parseInt(rawCompCword, 10)
const compWords = args.slice(1)
// Only complete the second word (nr __here__ ...)
if (compCword === 1) {
const raw = readPackageScripts(ctx)
const fzf = new Fzf(raw, {
selector: item => item.key,
casing: 'case-insensitive',
tiebreakers: [byLengthAsc],
})

// compWords will be ['nr'] when the user does not type anything after `nr` so fallback to empty string
const results = fzf.find(compWords[1] || '')

// eslint-disable-next-line no-console
console.log(results.map(r => r.item.key).join('\n'))
}
}
else {
// eslint-disable-next-line no-console
console.log(rawCompletionScript)
}
return
}

if (args[0] === '-') {
if (!storage.lastRunCommand) {
if (!ctx?.programmatic) {
Expand All @@ -24,23 +72,7 @@ runCli(async (agent, args, ctx) => {
}

if (args.length === 0 && !ctx?.programmatic) {
// support https://www.npmjs.com/package/npm-scripts-info conventions
const pkg = getPackageJSON(ctx)
const scripts = pkg.scripts || {}
const scriptsInfo = pkg['scripts-info'] || {}

const names = Object.entries(scripts) as [string, string][]

if (!names.length)
return

const raw = names
.filter(i => !i[0].startsWith('?'))
.map(([key, cmd]) => ({
key,
cmd,
description: scriptsInfo[key] || scripts[`?${key}`] || cmd,
}))
const raw = readPackageScripts(ctx)

const terminalColumns = process.stdout?.columns || 80

Expand Down
18 changes: 18 additions & 0 deletions src/completion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Print completion script
export const rawCompletionScript = `
###-begin-nr-completion-###

if type complete &>/dev/null; then
_nr_completion() {
local words
local cur
local cword
_get_comp_words_by_ref -n =: cur words cword
IFS=$'\\n'
COMPREPLY=($(COMP_CWORD=$cword COMP_LINE=$cur nr --completion \${words[@]}))
}
complete -F _nr_completion nr
fi

###-end-nr-completion-###
`.trim()