Skip to content

Commit 75c0e7b

Browse files
fix: add zsh autocomplete setup and file permissions instructions to completion:install (#6882)
* fix: fixed bugs in completion Co-authored-by: Dylan Spyer <[email protected]> * fix: add zsh autocompletion setup and file permissions instructions to completion:install Co-authored-by: Dylan Spyer <[email protected]> * fix: update completion Co-authored-by: Dylan Spyer <[email protected]> * fix: remove obselete ts-expect-error in command-helpers Co-authored-by: Dylan Spyer <[email protected]> * fix: update constant imports in completion:install test file Co-authored-by: Dylan Spyer <[email protected]> --------- Co-authored-by: Dylan Spyer <[email protected]>
1 parent 89e814d commit 75c0e7b

File tree

3 files changed

+140
-3
lines changed

3 files changed

+140
-3
lines changed

src/commands/completion/completion.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
1+
import fs from 'fs'
2+
import { homedir } from 'os'
13
import { dirname, join } from 'path'
24
import { fileURLToPath } from 'url'
5+
import inquirer from 'inquirer'
36

47
import { OptionValues } from 'commander'
58
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'tabt... Remove this comment to see the full error message
69
import { install, uninstall } from 'tabtab'
710

811
import { generateAutocompletion } from '../../lib/completion/index.js'
9-
import { error } from '../../utils/command-helpers.js'
12+
import {
13+
error,
14+
log,
15+
chalk,
16+
checkFileForLine,
17+
TABTAB_CONFIG_LINE,
18+
AUTOLOAD_COMPINIT,
19+
} from '../../utils/command-helpers.js'
1020
import BaseCommand from '../base-command.js'
1121

1222
const completer = join(dirname(fileURLToPath(import.meta.url)), '../../lib/completion/script.js')
@@ -20,13 +30,47 @@ export const completionGenerate = async (options: OptionValues, command: BaseCom
2030
}
2131

2232
generateAutocompletion(parent)
23-
2433
await install({
2534
name: parent.name(),
2635
completer,
2736
})
37+
const zshConfigFilepath = join(process.env.HOME || homedir(), '.zshrc')
38+
39+
if (
40+
fs.existsSync(zshConfigFilepath) &&
41+
checkFileForLine(zshConfigFilepath, TABTAB_CONFIG_LINE) &&
42+
!checkFileForLine(zshConfigFilepath, AUTOLOAD_COMPINIT)
43+
) {
44+
log(`To enable Tabtab autocompletion with zsh, the following line may need to be added to your ~/.zshrc:`)
45+
log(chalk.bold.cyan(`\n${AUTOLOAD_COMPINIT}\n`))
46+
const { compinitAdded } = await inquirer.prompt([
47+
{
48+
type: 'confirm',
49+
name: 'compinitAdded',
50+
message: `Would you like to add it?`,
51+
default: true,
52+
},
53+
])
54+
if (compinitAdded) {
55+
await fs.readFile(zshConfigFilepath, 'utf8', (err, data) => {
56+
const updatedZshFile = AUTOLOAD_COMPINIT + '\n' + data
2857

29-
console.log(`Completion for ${parent.name()} successful installed!`)
58+
fs.writeFileSync(zshConfigFilepath, updatedZshFile, 'utf8')
59+
})
60+
61+
log('Successfully added compinit line to .zshrc')
62+
}
63+
}
64+
65+
log(`Completion for ${parent.name()} successfully installed!`)
66+
67+
if (process.platform !== 'win32') {
68+
log("\nTo ensure proper functionality, you'll need to set appropriate file permissions.")
69+
log(chalk.bold('Add executable permissions by running the following command:'))
70+
log(chalk.bold.cyan(`\nchmod +x ${completer}\n`))
71+
} else {
72+
log(`\nTo ensure proper functionality, you may need to set appropriate file permissions to ${completer}.`)
73+
}
3074
}
3175

3276
export const completionUninstall = async (options: OptionValues, command: BaseCommand) => {

src/utils/command-helpers.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { once } from 'events'
22
import os from 'os'
3+
import fs from 'fs'
34
import process from 'process'
45
import { format, inspect } from 'util'
56

@@ -313,3 +314,16 @@ export interface APIError extends Error {
313314
status: number
314315
message: string
315316
}
317+
318+
export const checkFileForLine = (filename: string, line: string) => {
319+
let filecontent = ''
320+
try {
321+
filecontent = fs.readFileSync(filename, 'utf8')
322+
} catch (error_) {
323+
error(error_)
324+
}
325+
return !!filecontent.match(`${line}`)
326+
}
327+
328+
export const TABTAB_CONFIG_LINE = '[[ -f ~/.config/tabtab/__tabtab.zsh ]] && . ~/.config/tabtab/__tabtab.zsh || true'
329+
export const AUTOLOAD_COMPINIT = 'autoload -U compinit; compinit'
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { describe, expect, test, beforeAll, afterAll } from 'vitest'
2+
import fs from 'fs'
3+
import { rm } from 'fs/promises'
4+
import { temporaryDirectory } from 'tempy'
5+
import { handleQuestions, CONFIRM, DOWN, NO, answerWithValue } from '../../utils/handle-questions.js'
6+
import execa from 'execa'
7+
import { cliPath } from '../../utils/cli-path.js'
8+
import { join } from 'path'
9+
import { TABTAB_CONFIG_LINE, AUTOLOAD_COMPINIT } from '../../../../src/utils/command-helpers.js'
10+
11+
describe('completion:install command', () => {
12+
let tempDir
13+
let zshConfigPath
14+
let options
15+
16+
beforeAll(() => {
17+
tempDir = temporaryDirectory()
18+
zshConfigPath = join(tempDir, '.zshrc')
19+
options = { cwd: tempDir, env: { HOME: tempDir } }
20+
})
21+
22+
afterAll(async () => {
23+
await rm(tempDir, { force: true, recursive: true })
24+
})
25+
26+
test.skipIf(process.env.SHELL !== '/bin/zsh')(
27+
'should add compinit to .zshrc when user confirms prompt',
28+
async (t) => {
29+
fs.writeFileSync(zshConfigPath, TABTAB_CONFIG_LINE)
30+
const childProcess = execa(cliPath, ['completion:install'], options)
31+
32+
handleQuestions(childProcess, [
33+
{
34+
question: 'Which Shell do you use ?',
35+
answer: answerWithValue(DOWN),
36+
},
37+
{
38+
question: 'We will install completion to ~/.zshrc, is it ok ?',
39+
answer: CONFIRM,
40+
},
41+
{
42+
question: 'Would you like to add it?',
43+
answer: CONFIRM,
44+
},
45+
])
46+
47+
await childProcess
48+
const content = fs.readFileSync(zshConfigPath, 'utf8')
49+
expect(content).toContain(AUTOLOAD_COMPINIT)
50+
},
51+
)
52+
53+
test.skipIf(process.env.SHELL !== '/bin/zsh')(
54+
'should not add compinit to .zshrc when user does not confirm prompt',
55+
async (t) => {
56+
fs.writeFileSync(zshConfigPath, TABTAB_CONFIG_LINE)
57+
const childProcess = execa(cliPath, ['completion:install'], options)
58+
59+
handleQuestions(childProcess, [
60+
{
61+
question: 'Which Shell do you use ?',
62+
answer: answerWithValue(DOWN),
63+
},
64+
{
65+
question: 'We will install completion to ~/.zshrc, is it ok ?',
66+
answer: CONFIRM,
67+
},
68+
{
69+
question: 'Would you like to add it?',
70+
answer: answerWithValue(NO),
71+
},
72+
])
73+
74+
await childProcess
75+
const content = fs.readFileSync(zshConfigPath, 'utf8')
76+
expect(content).not.toContain(AUTOLOAD_COMPINIT)
77+
},
78+
)
79+
})

0 commit comments

Comments
 (0)