-
Notifications
You must be signed in to change notification settings - Fork 352
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
fix: add zsh autocomplete setup and file permissions instructions to completion:install #6882
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,12 +1,15 @@ | ||||||||||||||||||||||||||||||||||||||||
import fs from 'fs' | ||||||||||||||||||||||||||||||||||||||||
import { homedir } from 'os' | ||||||||||||||||||||||||||||||||||||||||
import { dirname, join } from 'path' | ||||||||||||||||||||||||||||||||||||||||
import { fileURLToPath } from 'url' | ||||||||||||||||||||||||||||||||||||||||
import inquirer from 'inquirer' | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
import { OptionValues } from 'commander' | ||||||||||||||||||||||||||||||||||||||||
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'tabt... Remove this comment to see the full error message | ||||||||||||||||||||||||||||||||||||||||
import { install, uninstall } from 'tabtab' | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
import { generateAutocompletion } from '../../lib/completion/index.js' | ||||||||||||||||||||||||||||||||||||||||
import { error } from '../../utils/command-helpers.js' | ||||||||||||||||||||||||||||||||||||||||
import { error, log, chalk, checkFileForLine } from '../../utils/command-helpers.js' | ||||||||||||||||||||||||||||||||||||||||
import BaseCommand from '../base-command.js' | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
const completer = join(dirname(fileURLToPath(import.meta.url)), '../../lib/completion/script.js') | ||||||||||||||||||||||||||||||||||||||||
|
@@ -20,13 +23,53 @@ export const completionGenerate = async (options: OptionValues, command: BaseCom | |||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
generateAutocompletion(parent) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
await install({ | ||||||||||||||||||||||||||||||||||||||||
name: parent.name(), | ||||||||||||||||||||||||||||||||||||||||
completer, | ||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
console.log(`Completion for ${parent.name()} successful installed!`) | ||||||||||||||||||||||||||||||||||||||||
const TABTAB_CONFIG_LINE = '[[ -f ~/.config/tabtab/__tabtab.zsh ]] && . ~/.config/tabtab/__tabtab.zsh || true' | ||||||||||||||||||||||||||||||||||||||||
const AUTOLOAD_COMPINIT = 'autoload -U compinit; compinit' | ||||||||||||||||||||||||||||||||||||||||
const zshConfigFilepath = join(process.env.HOME || homedir(), '.zshrc') | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
if ( | ||||||||||||||||||||||||||||||||||||||||
fs.existsSync(zshConfigFilepath) && | ||||||||||||||||||||||||||||||||||||||||
checkFileForLine(zshConfigFilepath, TABTAB_CONFIG_LINE) && | ||||||||||||||||||||||||||||||||||||||||
!checkFileForLine(zshConfigFilepath, AUTOLOAD_COMPINIT) | ||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||
log(`To enable Tabtab autocompletion with zsh, the following line may need to be added to your ~/.zshrc:`) | ||||||||||||||||||||||||||||||||||||||||
log(chalk.bold.cyan(`\n${AUTOLOAD_COMPINIT}\n`)) | ||||||||||||||||||||||||||||||||||||||||
await inquirer | ||||||||||||||||||||||||||||||||||||||||
.prompt([ | ||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||
type: 'confirm', | ||||||||||||||||||||||||||||||||||||||||
name: 'compinitAdded', | ||||||||||||||||||||||||||||||||||||||||
message: `Would you like to add it?`, | ||||||||||||||||||||||||||||||||||||||||
default: true, | ||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||
]) | ||||||||||||||||||||||||||||||||||||||||
.then((answer) => { | ||||||||||||||||||||||||||||||||||||||||
Comment on lines
+42
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Generally you wouldn't use
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
if (answer['compinitAdded']) { | ||||||||||||||||||||||||||||||||||||||||
fs.readFile(zshConfigFilepath, 'utf8', (err, data) => { | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that we add the
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would await this. So await fs.readFile or use fs.readFileSync. |
||||||||||||||||||||||||||||||||||||||||
const updatedZshFile = AUTOLOAD_COMPINIT + '\n' + data | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
fs.writeFileSync(zshConfigFilepath, updatedZshFile, 'utf8') | ||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
log('Successfully added compinit line to .zshrc') | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
log(`Completion for ${parent.name()} successfully installed!`) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
if (process.platform !== 'win32') { | ||||||||||||||||||||||||||||||||||||||||
log("\nTo ensure proper functionality, you'll need to set appropriate file permissions.") | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because |
||||||||||||||||||||||||||||||||||||||||
log(chalk.bold('Add executable permissions by running the following command:')) | ||||||||||||||||||||||||||||||||||||||||
log(chalk.bold.cyan(`\nchmod +x ${completer}\n`)) | ||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||
log(`\nTo ensure proper functionality, you may need to set appropriate file permissions to ${completer}.`) | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
export const completionUninstall = async (options: OptionValues, command: BaseCommand) => { | ||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import { once } from 'events' | ||
import os from 'os' | ||
import fs from 'fs' | ||
import process from 'process' | ||
import { format, inspect } from 'util' | ||
|
||
|
@@ -300,3 +301,14 @@ export const nonNullable = <T>(value: T): value is NonNullable<T> => value !== n | |
export const noOp = () => { | ||
// no-op | ||
} | ||
|
||
export const checkFileForLine = (filename: string, line: string) => { | ||
let filecontent = '' | ||
try { | ||
filecontent = fs.readFileSync(filename, 'utf8') | ||
} catch (error_) { | ||
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pull in the main branch to get updates from this PR #6877 to fix this type error |
||
error(error_) | ||
} | ||
return !!filecontent.match(`${line}`) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { describe, expect, test, beforeAll, afterAll } from 'vitest' | ||
import fs from 'fs' | ||
import { rm } from 'fs/promises' | ||
import { temporaryDirectory } from 'tempy' | ||
import { handleQuestions, CONFIRM, DOWN, NO, answerWithValue } from '../../utils/handle-questions.js' | ||
import execa from 'execa' | ||
import { cliPath } from '../../utils/cli-path.js' | ||
import { join } from 'path' | ||
|
||
const TABTAB_CONFIG_LINE = '[[ -f ~/.config/tabtab/__tabtab.zsh ]] && . ~/.config/tabtab/__tabtab.zsh || true' | ||
const AUTOLOAD_COMPINIT = 'autoload -U compinit; compinit' | ||
Comment on lines
+10
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pull these from |
||
|
||
describe('completion:install command', () => { | ||
let tempDir | ||
let zshConfigPath | ||
let options | ||
|
||
beforeAll(() => { | ||
tempDir = temporaryDirectory() | ||
zshConfigPath = join(tempDir, '.zshrc') | ||
options = { cwd: tempDir, env: { HOME: tempDir } } | ||
}) | ||
|
||
afterAll(async () => { | ||
await rm(tempDir, { force: true, recursive: true }) | ||
}) | ||
|
||
test.skipIf(process.env.SHELL !== '/bin/zsh')( | ||
'should add compinit to .zshrc when user confirms prompt', | ||
async (t) => { | ||
fs.writeFileSync(zshConfigPath, TABTAB_CONFIG_LINE) | ||
const childProcess = execa(cliPath, ['completion:install'], options) | ||
|
||
handleQuestions(childProcess, [ | ||
{ | ||
question: 'Which Shell do you use ?', | ||
answer: answerWithValue(DOWN), | ||
}, | ||
{ | ||
question: 'We will install completion to ~/.zshrc, is it ok ?', | ||
answer: CONFIRM, | ||
}, | ||
{ | ||
question: 'Would you like to add it?', | ||
answer: CONFIRM, | ||
}, | ||
]) | ||
|
||
await childProcess | ||
const content = fs.readFileSync(zshConfigPath, 'utf8') | ||
expect(content).toContain(AUTOLOAD_COMPINIT) | ||
}, | ||
) | ||
|
||
test.skipIf(process.env.SHELL !== '/bin/zsh')( | ||
'should not add compinit to .zshrc when user does not confirm prompt', | ||
async (t) => { | ||
fs.writeFileSync(zshConfigPath, TABTAB_CONFIG_LINE) | ||
const childProcess = execa(cliPath, ['completion:install'], options) | ||
|
||
handleQuestions(childProcess, [ | ||
{ | ||
question: 'Which Shell do you use ?', | ||
answer: answerWithValue(DOWN), | ||
}, | ||
{ | ||
question: 'We will install completion to ~/.zshrc, is it ok ?', | ||
answer: CONFIRM, | ||
}, | ||
{ | ||
question: 'Would you like to add it?', | ||
answer: answerWithValue(NO), | ||
}, | ||
]) | ||
|
||
await childProcess | ||
const content = fs.readFileSync(zshConfigPath, 'utf8') | ||
expect(content).not.toContain(AUTOLOAD_COMPINIT) | ||
}, | ||
) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Throw these in the constants file in this folder