import figures from 'figures'
import * as React from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useDebounceCallback } from 'usehooks-ts'
import {
addDirHelpMessage,
validateDirectoryForWorkspace,
} from '../../../commands/add-dir/validation.js'
import TextInput from '../../../components/TextInput.js'
import { type KeyboardEvent, Box, Text } from '@anthropic/ink'
import { useKeybinding } from '../../../keybindings/useKeybinding.js'
import type { ToolPermissionContext } from '../../../Tool.js'
import { getDirectoryCompletions } from '../../../utils/suggestions/directoryCompletion.js'
import { ConfigurableShortcutHint } from '../../ConfigurableShortcutHint.js'
import { Select } from '../../CustomSelect/select.js'
import { Byline, Dialog, KeyboardShortcutHint } from '@anthropic/ink'
import {
PromptInputFooterSuggestions,
type SuggestionItem,
} from '../../PromptInput/PromptInputFooterSuggestions.js'
type Props = {
onAddDirectory: (path: string, remember?: boolean) => void
onCancel: () => void
permissionContext: ToolPermissionContext
directoryPath?: string // When directoryPath is provided, show selection options instead of input
}
type RememberDirectoryOption = 'yes-session' | 'yes-remember' | 'no'
const REMEMBER_DIRECTORY_OPTIONS: Array<{
value: RememberDirectoryOption
label: string
}> = [
{
value: 'yes-session',
label: 'Yes, for this session',
},
{
value: 'yes-remember',
label: 'Yes, and remember this directory',
},
{
value: 'no',
label: 'No',
},
]
function PermissionDescription(): React.ReactNode {
return (
Claude Code will be able to read files in this directory and make edits
when auto-accept edits is on.
)
}
function DirectoryDisplay({ path }: { path: string }): React.ReactNode {
return (
{path}
)
}
function DirectoryInput({
value,
onChange,
onSubmit,
error,
suggestions,
selectedSuggestion,
}: {
value: string
onChange: (value: string) => void
onSubmit: (value: string) => void
error: string | null
suggestions: SuggestionItem[]
selectedSuggestion: number
}): React.ReactNode {
return (
Enter the path to the directory:
{}}
/>
{suggestions.length > 0 && (
)}
{error && {error}}
)
}
export function AddWorkspaceDirectory({
onAddDirectory,
onCancel,
permissionContext,
directoryPath,
}: Props): React.ReactNode {
const [directoryInput, setDirectoryInput] = useState('')
const [error, setError] = useState(null)
const [suggestions, setSuggestions] = useState([])
const [selectedSuggestion, setSelectedSuggestion] = useState(0)
const options = useMemo(() => REMEMBER_DIRECTORY_OPTIONS, [])
// Fetch directory completions
const fetchSuggestions = useCallback(async (path: string) => {
if (!path) {
setSuggestions([])
setSelectedSuggestion(0)
return
}
const completions = await getDirectoryCompletions(path)
setSuggestions(completions)
setSelectedSuggestion(0)
}, [])
const debouncedFetchSuggestions = useDebounceCallback(fetchSuggestions, 100)
useEffect(() => {
void debouncedFetchSuggestions(directoryInput)
}, [directoryInput, debouncedFetchSuggestions])
const applySuggestion = useCallback((suggestion: SuggestionItem) => {
const newPath = suggestion.id + '/'
setDirectoryInput(newPath)
setError(null)
// Suggestions will update via the useEffect
}, [])
// Handle directory submission from input
const handleSubmit = useCallback(
async (newPath: string) => {
const result = await validateDirectoryForWorkspace(
newPath,
permissionContext,
)
if (result.resultType === 'success') {
onAddDirectory(result.absolutePath, false)
} else {
setError(addDirHelpMessage(result))
}
},
[permissionContext, onAddDirectory],
)
// Handle Esc to cancel (Ctrl+C handled by global keybindings)
// Use Settings context so 'n' key doesn't cancel (allows typing 'n' in input)
useKeybinding('confirm:no', onCancel, { context: 'Settings' })
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
if (suggestions.length > 0) {
// Tab: accept selected suggestion and continue (for drilling into subdirs)
if (e.key === 'tab') {
e.preventDefault()
const suggestion = suggestions[selectedSuggestion]
if (suggestion) {
applySuggestion(suggestion)
}
return
}
// Enter: apply selected suggestion and submit
if (e.key === 'return') {
e.preventDefault()
const suggestion = suggestions[selectedSuggestion]
if (suggestion) {
void handleSubmit(suggestion.id + '/')
}
return
}
if (e.key === 'up' || (e.ctrl && e.key === 'p')) {
e.preventDefault()
setSelectedSuggestion(prev =>
prev <= 0 ? suggestions.length - 1 : prev - 1,
)
return
}
if (e.key === 'down' || (e.ctrl && e.key === 'n')) {
e.preventDefault()
setSelectedSuggestion(prev =>
prev >= suggestions.length - 1 ? 0 : prev + 1,
)
return
}
}
},
[suggestions, selectedSuggestion, applySuggestion, handleSubmit],
)
const handleSelect = useCallback(
(value: string) => {
if (!directoryPath) return
const selectionValue = value as RememberDirectoryOption
switch (selectionValue) {
case 'yes-session':
onAddDirectory(directoryPath, false)
break
case 'yes-remember':
onAddDirectory(directoryPath, true)
break
case 'no':
onCancel()
break
}
},
[directoryPath, onAddDirectory, onCancel],
)
return (
)
}