@butlerw/vellum 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +411 -0
- package/__fixtures__/responses/code-generation.json +42 -0
- package/__fixtures__/responses/error-response.json +20 -0
- package/__fixtures__/responses/hello-world.json +32 -0
- package/dist/auth-6MCXESOH.js +26 -0
- package/dist/chunk-SECXJGWA.js +597 -0
- package/dist/index.js +34023 -0
- package/package.json +67 -0
- package/src/__tests__/commands.e2e.test.ts +728 -0
- package/src/__tests__/credentials.test.ts +713 -0
- package/src/__tests__/mode-e2e.test.ts +391 -0
- package/src/__tests__/tui-integration.test.tsx +1271 -0
- package/src/agents/__tests__/task-persistence.test.ts +235 -0
- package/src/agents/commands/delegate.ts +240 -0
- package/src/agents/commands/index.ts +10 -0
- package/src/agents/commands/resume.ts +335 -0
- package/src/agents/index.ts +29 -0
- package/src/agents/task-persistence.ts +272 -0
- package/src/agents/task-resumption.ts +242 -0
- package/src/app.tsx +4737 -0
- package/src/commands/__tests__/.gitkeep +1 -0
- package/src/commands/__tests__/agents.test.ts +606 -0
- package/src/commands/__tests__/auth.test.ts +626 -0
- package/src/commands/__tests__/autocomplete.test.ts +683 -0
- package/src/commands/__tests__/batch.test.ts +287 -0
- package/src/commands/__tests__/chain-pipe-parser.test.ts +654 -0
- package/src/commands/__tests__/completion.test.ts +238 -0
- package/src/commands/__tests__/core.test.ts +363 -0
- package/src/commands/__tests__/executor.test.ts +496 -0
- package/src/commands/__tests__/exit-codes.test.ts +220 -0
- package/src/commands/__tests__/init.test.ts +243 -0
- package/src/commands/__tests__/language.test.ts +353 -0
- package/src/commands/__tests__/mode-cli.test.ts +667 -0
- package/src/commands/__tests__/model.test.ts +277 -0
- package/src/commands/__tests__/parser.test.ts +493 -0
- package/src/commands/__tests__/performance.bench.ts +380 -0
- package/src/commands/__tests__/registry.test.ts +534 -0
- package/src/commands/__tests__/resume.test.ts +449 -0
- package/src/commands/__tests__/security.test.ts +845 -0
- package/src/commands/__tests__/stream-json.test.ts +372 -0
- package/src/commands/__tests__/user-commands.test.ts +597 -0
- package/src/commands/adapters.ts +267 -0
- package/src/commands/agent.ts +395 -0
- package/src/commands/agents/generate.ts +506 -0
- package/src/commands/agents/index.ts +272 -0
- package/src/commands/agents/show.ts +271 -0
- package/src/commands/agents/validate.ts +387 -0
- package/src/commands/auth.ts +883 -0
- package/src/commands/autocomplete.ts +480 -0
- package/src/commands/batch/command.ts +388 -0
- package/src/commands/batch/executor.ts +361 -0
- package/src/commands/batch/index.ts +12 -0
- package/src/commands/commit.ts +235 -0
- package/src/commands/completion/index.ts +371 -0
- package/src/commands/condense.ts +191 -0
- package/src/commands/config.ts +344 -0
- package/src/commands/context-provider.ts +173 -0
- package/src/commands/copy.ts +329 -0
- package/src/commands/core/clear.ts +38 -0
- package/src/commands/core/exit.ts +43 -0
- package/src/commands/core/help.ts +354 -0
- package/src/commands/core/index.ts +15 -0
- package/src/commands/cost.ts +179 -0
- package/src/commands/credentials.tsx +618 -0
- package/src/commands/custom-agents/__tests__/custom-agents.test.ts +709 -0
- package/src/commands/custom-agents/create.ts +377 -0
- package/src/commands/custom-agents/export.ts +135 -0
- package/src/commands/custom-agents/import.ts +199 -0
- package/src/commands/custom-agents/index.ts +372 -0
- package/src/commands/custom-agents/info.ts +318 -0
- package/src/commands/custom-agents/list.ts +267 -0
- package/src/commands/custom-agents/validate.ts +388 -0
- package/src/commands/diff-mode.ts +241 -0
- package/src/commands/env.ts +53 -0
- package/src/commands/executor.ts +579 -0
- package/src/commands/exit-codes.ts +202 -0
- package/src/commands/index.ts +701 -0
- package/src/commands/init/index.ts +15 -0
- package/src/commands/init/prompts.ts +366 -0
- package/src/commands/init/templates/commands-readme.md +80 -0
- package/src/commands/init/templates/example-command.md +79 -0
- package/src/commands/init/templates/example-skill.md +168 -0
- package/src/commands/init/templates/example-workflow.md +101 -0
- package/src/commands/init/templates/prompts-readme.md +52 -0
- package/src/commands/init/templates/rules-readme.md +63 -0
- package/src/commands/init/templates/skills-readme.md +83 -0
- package/src/commands/init/templates/workflows-readme.md +94 -0
- package/src/commands/init.ts +391 -0
- package/src/commands/install.ts +90 -0
- package/src/commands/language.ts +191 -0
- package/src/commands/loaders/.gitkeep +1 -0
- package/src/commands/lsp.ts +199 -0
- package/src/commands/markdown-commands.ts +253 -0
- package/src/commands/mcp.ts +588 -0
- package/src/commands/memory/export.ts +341 -0
- package/src/commands/memory/index.ts +148 -0
- package/src/commands/memory/list.ts +261 -0
- package/src/commands/memory/search.ts +346 -0
- package/src/commands/memory/utils.ts +15 -0
- package/src/commands/metrics.ts +75 -0
- package/src/commands/migrate/index.ts +16 -0
- package/src/commands/migrate/prompts.ts +477 -0
- package/src/commands/mode.ts +331 -0
- package/src/commands/model.ts +298 -0
- package/src/commands/onboard.ts +205 -0
- package/src/commands/open.ts +169 -0
- package/src/commands/output/stream-json.ts +373 -0
- package/src/commands/parser/chain-parser.ts +370 -0
- package/src/commands/parser/index.ts +29 -0
- package/src/commands/parser/pipe-parser.ts +480 -0
- package/src/commands/parser.ts +588 -0
- package/src/commands/persistence.ts +355 -0
- package/src/commands/progress.ts +18 -0
- package/src/commands/prompt/index.ts +17 -0
- package/src/commands/prompt/validate.ts +621 -0
- package/src/commands/prompt-priority.ts +401 -0
- package/src/commands/registry.ts +374 -0
- package/src/commands/sandbox/index.ts +131 -0
- package/src/commands/security/index.ts +21 -0
- package/src/commands/security/input-sanitizer.ts +168 -0
- package/src/commands/security/permission-checker.ts +456 -0
- package/src/commands/security/sensitive-data.ts +350 -0
- package/src/commands/session/delete.ts +38 -0
- package/src/commands/session/export.ts +39 -0
- package/src/commands/session/index.ts +26 -0
- package/src/commands/session/list.ts +26 -0
- package/src/commands/session/resume.ts +562 -0
- package/src/commands/session/search.ts +434 -0
- package/src/commands/session/show.ts +26 -0
- package/src/commands/settings.ts +368 -0
- package/src/commands/setup.ts +23 -0
- package/src/commands/shell/index.ts +16 -0
- package/src/commands/shell/setup.ts +422 -0
- package/src/commands/shell-init.ts +50 -0
- package/src/commands/shell-integration/index.ts +194 -0
- package/src/commands/skill.ts +1220 -0
- package/src/commands/spec.ts +558 -0
- package/src/commands/status.ts +246 -0
- package/src/commands/theme.ts +211 -0
- package/src/commands/think.ts +551 -0
- package/src/commands/trust.ts +211 -0
- package/src/commands/tutorial.ts +522 -0
- package/src/commands/types.ts +512 -0
- package/src/commands/update.ts +274 -0
- package/src/commands/usage.ts +213 -0
- package/src/commands/user-commands.ts +630 -0
- package/src/commands/utils.ts +142 -0
- package/src/commands/vim.ts +152 -0
- package/src/commands/workflow.ts +257 -0
- package/src/components/header.tsx +25 -0
- package/src/components/input.tsx +25 -0
- package/src/components/message-list.tsx +32 -0
- package/src/components/status-bar.tsx +23 -0
- package/src/index.tsx +614 -0
- package/src/onboarding/__tests__/tutorial.test.ts +740 -0
- package/src/onboarding/index.ts +69 -0
- package/src/onboarding/tips/index.ts +9 -0
- package/src/onboarding/tips/tip-engine.ts +459 -0
- package/src/onboarding/tutorial/index.ts +88 -0
- package/src/onboarding/tutorial/lessons/basics.ts +151 -0
- package/src/onboarding/tutorial/lessons/index.ts +151 -0
- package/src/onboarding/tutorial/lessons/modes.ts +230 -0
- package/src/onboarding/tutorial/lessons/tools.ts +172 -0
- package/src/onboarding/tutorial/progress-tracker.ts +350 -0
- package/src/onboarding/tutorial/storage.ts +249 -0
- package/src/onboarding/tutorial/tutorial-system.ts +462 -0
- package/src/onboarding/tutorial/types.ts +310 -0
- package/src/orchestrator-singleton.ts +129 -0
- package/src/shutdown.ts +33 -0
- package/src/test/e2e/assertions.ts +267 -0
- package/src/test/e2e/fixtures.ts +204 -0
- package/src/test/e2e/harness.ts +575 -0
- package/src/test/e2e/index.ts +57 -0
- package/src/test/e2e/types.ts +228 -0
- package/src/test/fixtures/__tests__/fake-response-loader.test.ts +314 -0
- package/src/test/fixtures/fake-response-loader.ts +314 -0
- package/src/test/fixtures/index.ts +20 -0
- package/src/tui/__tests__/mcp-panel.test.tsx +82 -0
- package/src/tui/__tests__/mcp-wiring.test.tsx +78 -0
- package/src/tui/__tests__/mode-components.test.tsx +395 -0
- package/src/tui/__tests__/permission-ask-flow.test.tsx +138 -0
- package/src/tui/__tests__/sidebar-panel-data.test.tsx +148 -0
- package/src/tui/__tests__/tools-panel-hotkeys.test.tsx +41 -0
- package/src/tui/adapters/agent-adapter.ts +1008 -0
- package/src/tui/adapters/index.ts +48 -0
- package/src/tui/adapters/message-adapter.ts +315 -0
- package/src/tui/adapters/persistence-bridge.ts +331 -0
- package/src/tui/adapters/session-adapter.ts +419 -0
- package/src/tui/buffered-stdout.ts +223 -0
- package/src/tui/components/AgentProgress.tsx +424 -0
- package/src/tui/components/Banner/AsciiArt.ts +160 -0
- package/src/tui/components/Banner/Banner.tsx +355 -0
- package/src/tui/components/Banner/ShimmerContext.tsx +131 -0
- package/src/tui/components/Banner/ShimmerText.tsx +193 -0
- package/src/tui/components/Banner/TypeWriterGradient.tsx +321 -0
- package/src/tui/components/Banner/index.ts +61 -0
- package/src/tui/components/Banner/useShimmer.ts +241 -0
- package/src/tui/components/ChatView.tsx +11 -0
- package/src/tui/components/Checkpoint/CheckpointDiffView.tsx +371 -0
- package/src/tui/components/Checkpoint/SnapshotCheckpointPanel.tsx +440 -0
- package/src/tui/components/Checkpoint/index.ts +19 -0
- package/src/tui/components/CostDisplay.tsx +226 -0
- package/src/tui/components/InitErrorBanner.tsx +122 -0
- package/src/tui/components/Input/Autocomplete.tsx +603 -0
- package/src/tui/components/Input/EnhancedCommandInput.tsx +471 -0
- package/src/tui/components/Input/HighlightedText.tsx +236 -0
- package/src/tui/components/Input/MentionAutocomplete.tsx +375 -0
- package/src/tui/components/Input/TextInput.tsx +1002 -0
- package/src/tui/components/Input/__tests__/Autocomplete.test.tsx +374 -0
- package/src/tui/components/Input/__tests__/TextInput.test.tsx +241 -0
- package/src/tui/components/Input/__tests__/highlight.test.ts +219 -0
- package/src/tui/components/Input/__tests__/slash-command-utils.test.ts +104 -0
- package/src/tui/components/Input/highlight.ts +362 -0
- package/src/tui/components/Input/index.ts +36 -0
- package/src/tui/components/Input/slash-command-utils.ts +135 -0
- package/src/tui/components/Layout.tsx +432 -0
- package/src/tui/components/McpPanel.tsx +137 -0
- package/src/tui/components/MemoryPanel.tsx +448 -0
- package/src/tui/components/Messages/CodeBlock.tsx +527 -0
- package/src/tui/components/Messages/DiffView.tsx +679 -0
- package/src/tui/components/Messages/ImageReference.tsx +89 -0
- package/src/tui/components/Messages/MarkdownBlock.tsx +228 -0
- package/src/tui/components/Messages/MarkdownRenderer.tsx +498 -0
- package/src/tui/components/Messages/MessageBubble.tsx +270 -0
- package/src/tui/components/Messages/MessageList.tsx +1719 -0
- package/src/tui/components/Messages/StreamingText.tsx +216 -0
- package/src/tui/components/Messages/ThinkingBlock.tsx +408 -0
- package/src/tui/components/Messages/ToolResultPreview.tsx +243 -0
- package/src/tui/components/Messages/__tests__/CodeBlock.test.tsx +296 -0
- package/src/tui/components/Messages/__tests__/DiffView.test.tsx +239 -0
- package/src/tui/components/Messages/__tests__/MarkdownRenderer.test.tsx +303 -0
- package/src/tui/components/Messages/__tests__/MessageBubble.test.tsx +268 -0
- package/src/tui/components/Messages/__tests__/MessageList.test.tsx +324 -0
- package/src/tui/components/Messages/__tests__/StreamingText.test.tsx +215 -0
- package/src/tui/components/Messages/index.ts +25 -0
- package/src/tui/components/ModeIndicator.tsx +177 -0
- package/src/tui/components/ModeSelector.tsx +216 -0
- package/src/tui/components/ModelSelector.tsx +339 -0
- package/src/tui/components/OnboardingWizard.tsx +670 -0
- package/src/tui/components/PhaseProgressIndicator.tsx +270 -0
- package/src/tui/components/RateLimitIndicator.tsx +82 -0
- package/src/tui/components/ScreenReaderLayout.tsx +295 -0
- package/src/tui/components/SettingsPanel.tsx +643 -0
- package/src/tui/components/Sidebar/SystemStatusPanel.tsx +284 -0
- package/src/tui/components/Sidebar/index.ts +9 -0
- package/src/tui/components/Status/ModelStatusBar.tsx +270 -0
- package/src/tui/components/Status/index.ts +12 -0
- package/src/tui/components/StatusBar/AgentModeIndicator.tsx +257 -0
- package/src/tui/components/StatusBar/ContextProgress.tsx +167 -0
- package/src/tui/components/StatusBar/FileChangesIndicator.tsx +62 -0
- package/src/tui/components/StatusBar/GitIndicator.tsx +89 -0
- package/src/tui/components/StatusBar/HeaderBar.tsx +126 -0
- package/src/tui/components/StatusBar/ModelIndicator.tsx +157 -0
- package/src/tui/components/StatusBar/PersistenceStatusIndicator.tsx +210 -0
- package/src/tui/components/StatusBar/ResilienceIndicator.tsx +106 -0
- package/src/tui/components/StatusBar/SandboxIndicator.tsx +167 -0
- package/src/tui/components/StatusBar/StatusBar.tsx +368 -0
- package/src/tui/components/StatusBar/ThinkingModeIndicator.tsx +170 -0
- package/src/tui/components/StatusBar/TokenBreakdown.tsx +246 -0
- package/src/tui/components/StatusBar/TokenCounter.tsx +135 -0
- package/src/tui/components/StatusBar/TrustModeIndicator.tsx +130 -0
- package/src/tui/components/StatusBar/WorkspaceIndicator.tsx +86 -0
- package/src/tui/components/StatusBar/__tests__/AgentModeIndicator.test.tsx +193 -0
- package/src/tui/components/StatusBar/__tests__/StatusBar.test.tsx +729 -0
- package/src/tui/components/StatusBar/index.ts +60 -0
- package/src/tui/components/TipBanner.tsx +115 -0
- package/src/tui/components/TodoItem.tsx +208 -0
- package/src/tui/components/TodoPanel.tsx +455 -0
- package/src/tui/components/Tools/ApprovalQueue.tsx +407 -0
- package/src/tui/components/Tools/OptionSelector.tsx +160 -0
- package/src/tui/components/Tools/PermissionDialog.tsx +286 -0
- package/src/tui/components/Tools/ToolParams.tsx +483 -0
- package/src/tui/components/Tools/ToolsPanel.tsx +178 -0
- package/src/tui/components/Tools/__tests__/PermissionDialog.test.tsx +510 -0
- package/src/tui/components/Tools/__tests__/ToolParams.test.tsx +432 -0
- package/src/tui/components/Tools/index.ts +21 -0
- package/src/tui/components/TrustPrompt.tsx +279 -0
- package/src/tui/components/UpdateBanner.tsx +166 -0
- package/src/tui/components/VimModeIndicator.tsx +112 -0
- package/src/tui/components/backtrack/BacktrackControls.tsx +402 -0
- package/src/tui/components/backtrack/index.ts +13 -0
- package/src/tui/components/common/AutoApprovalStatus.tsx +251 -0
- package/src/tui/components/common/CostWarning.tsx +294 -0
- package/src/tui/components/common/DynamicShortcutHints.tsx +209 -0
- package/src/tui/components/common/EnhancedLoadingIndicator.tsx +305 -0
- package/src/tui/components/common/ErrorBoundary.tsx +140 -0
- package/src/tui/components/common/GradientText.tsx +224 -0
- package/src/tui/components/common/HotkeyHelpModal.tsx +193 -0
- package/src/tui/components/common/HotkeyHints.tsx +70 -0
- package/src/tui/components/common/MaxSizedBox.tsx +354 -0
- package/src/tui/components/common/NewMessagesBadge.tsx +65 -0
- package/src/tui/components/common/ProtectedFileLegend.tsx +89 -0
- package/src/tui/components/common/ScrollIndicator.tsx +160 -0
- package/src/tui/components/common/Spinner.tsx +342 -0
- package/src/tui/components/common/StreamingIndicator.tsx +316 -0
- package/src/tui/components/common/VirtualizedList/VirtualizedList.tsx +428 -0
- package/src/tui/components/common/VirtualizedList/hooks/index.ts +19 -0
- package/src/tui/components/common/VirtualizedList/hooks/useBatchedScroll.ts +64 -0
- package/src/tui/components/common/VirtualizedList/hooks/useScrollAnchor.ts +290 -0
- package/src/tui/components/common/VirtualizedList/hooks/useVirtualization.ts +340 -0
- package/src/tui/components/common/VirtualizedList/index.ts +30 -0
- package/src/tui/components/common/VirtualizedList/types.ts +107 -0
- package/src/tui/components/common/__tests__/NewMessagesBadge.test.tsx +74 -0
- package/src/tui/components/common/__tests__/ScrollIndicator.test.tsx +193 -0
- package/src/tui/components/common/index.ts +110 -0
- package/src/tui/components/index.ts +79 -0
- package/src/tui/components/session/CheckpointPanel.tsx +323 -0
- package/src/tui/components/session/RollbackDialog.tsx +169 -0
- package/src/tui/components/session/SessionItem.tsx +136 -0
- package/src/tui/components/session/SessionListPanel.tsx +252 -0
- package/src/tui/components/session/SessionPicker.tsx +449 -0
- package/src/tui/components/session/SessionPreview.tsx +240 -0
- package/src/tui/components/session/__tests__/session.test.tsx +408 -0
- package/src/tui/components/session/index.ts +28 -0
- package/src/tui/components/session/types.ts +116 -0
- package/src/tui/components/theme/__tests__/tokens.test.ts +471 -0
- package/src/tui/components/theme/index.ts +227 -0
- package/src/tui/components/theme/tokens.ts +484 -0
- package/src/tui/config/defaults.ts +134 -0
- package/src/tui/config/index.ts +17 -0
- package/src/tui/context/AnimationContext.tsx +284 -0
- package/src/tui/context/AppContext.tsx +349 -0
- package/src/tui/context/BracketedPasteContext.tsx +372 -0
- package/src/tui/context/LspContext.tsx +192 -0
- package/src/tui/context/McpContext.tsx +325 -0
- package/src/tui/context/MessagesContext.tsx +870 -0
- package/src/tui/context/OverflowContext.tsx +213 -0
- package/src/tui/context/RateLimitContext.tsx +108 -0
- package/src/tui/context/ResilienceContext.tsx +275 -0
- package/src/tui/context/RootProvider.tsx +136 -0
- package/src/tui/context/ScrollContext.tsx +331 -0
- package/src/tui/context/ToolsContext.tsx +702 -0
- package/src/tui/context/__tests__/BracketedPasteContext.test.tsx +416 -0
- package/src/tui/context/index.ts +140 -0
- package/src/tui/enterprise-integration.ts +282 -0
- package/src/tui/hooks/__tests__/useBacktrack.test.tsx +138 -0
- package/src/tui/hooks/__tests__/useBracketedPaste.test.tsx +222 -0
- package/src/tui/hooks/__tests__/useCopyMode.test.tsx +336 -0
- package/src/tui/hooks/__tests__/useHotkeys.ctrl-input.test.tsx +96 -0
- package/src/tui/hooks/__tests__/useHotkeys.test.tsx +454 -0
- package/src/tui/hooks/__tests__/useInputHistory.test.tsx +660 -0
- package/src/tui/hooks/__tests__/useLineBuffer.test.ts +295 -0
- package/src/tui/hooks/__tests__/useModeController.test.ts +137 -0
- package/src/tui/hooks/__tests__/useModeShortcuts.test.tsx +142 -0
- package/src/tui/hooks/__tests__/useScrollController.test.ts +464 -0
- package/src/tui/hooks/__tests__/useVim.test.tsx +531 -0
- package/src/tui/hooks/index.ts +252 -0
- package/src/tui/hooks/useAgentLoop.ts +712 -0
- package/src/tui/hooks/useAlternateBuffer.ts +398 -0
- package/src/tui/hooks/useAnimatedScrollbar.ts +241 -0
- package/src/tui/hooks/useBacktrack.ts +443 -0
- package/src/tui/hooks/useBracketedPaste.ts +104 -0
- package/src/tui/hooks/useCollapsible.ts +240 -0
- package/src/tui/hooks/useCopyMode.ts +382 -0
- package/src/tui/hooks/useCostSummary.ts +75 -0
- package/src/tui/hooks/useDesktopNotification.ts +414 -0
- package/src/tui/hooks/useDiffMode.ts +44 -0
- package/src/tui/hooks/useFileChangeStats.ts +110 -0
- package/src/tui/hooks/useFileSuggestions.ts +284 -0
- package/src/tui/hooks/useFlickerDetector.ts +250 -0
- package/src/tui/hooks/useGitStatus.ts +200 -0
- package/src/tui/hooks/useHotkeys.ts +579 -0
- package/src/tui/hooks/useImagePaste.ts +114 -0
- package/src/tui/hooks/useInputHighlight.ts +145 -0
- package/src/tui/hooks/useInputHistory.ts +246 -0
- package/src/tui/hooks/useKeyboardScroll.ts +209 -0
- package/src/tui/hooks/useLineBuffer.ts +356 -0
- package/src/tui/hooks/useMentionAutocomplete.ts +235 -0
- package/src/tui/hooks/useModeController.ts +167 -0
- package/src/tui/hooks/useModeShortcuts.ts +196 -0
- package/src/tui/hooks/usePermissionHandler.ts +146 -0
- package/src/tui/hooks/usePersistence.ts +480 -0
- package/src/tui/hooks/usePersistenceShortcuts.ts +225 -0
- package/src/tui/hooks/usePlaceholderRotation.ts +143 -0
- package/src/tui/hooks/useProviderStatus.ts +270 -0
- package/src/tui/hooks/useRateLimitStatus.ts +90 -0
- package/src/tui/hooks/useScreenReader.ts +315 -0
- package/src/tui/hooks/useScrollController.ts +450 -0
- package/src/tui/hooks/useScrollEventBatcher.ts +185 -0
- package/src/tui/hooks/useSidebarPanelData.ts +115 -0
- package/src/tui/hooks/useSmoothScroll.ts +202 -0
- package/src/tui/hooks/useSnapshots.ts +300 -0
- package/src/tui/hooks/useStateAndRef.ts +50 -0
- package/src/tui/hooks/useTerminalSize.ts +206 -0
- package/src/tui/hooks/useToolApprovalController.ts +91 -0
- package/src/tui/hooks/useVim.ts +334 -0
- package/src/tui/hooks/useWorkspace.ts +56 -0
- package/src/tui/i18n/__tests__/init.test.ts +278 -0
- package/src/tui/i18n/__tests__/language-config.test.ts +199 -0
- package/src/tui/i18n/__tests__/locale-detection.test.ts +250 -0
- package/src/tui/i18n/__tests__/settings-integration.test.ts +262 -0
- package/src/tui/i18n/index.ts +72 -0
- package/src/tui/i18n/init.ts +131 -0
- package/src/tui/i18n/language-config.ts +106 -0
- package/src/tui/i18n/locale-detection.ts +173 -0
- package/src/tui/i18n/settings-integration.ts +557 -0
- package/src/tui/i18n/tui-namespace.ts +538 -0
- package/src/tui/i18n/types.ts +312 -0
- package/src/tui/index.ts +43 -0
- package/src/tui/lsp-integration.ts +409 -0
- package/src/tui/metrics-integration.ts +366 -0
- package/src/tui/plugins.ts +383 -0
- package/src/tui/resilience.ts +342 -0
- package/src/tui/sandbox-integration.ts +317 -0
- package/src/tui/services/clipboard.ts +348 -0
- package/src/tui/services/fuzzy-search.ts +441 -0
- package/src/tui/services/index.ts +72 -0
- package/src/tui/services/markdown-renderer.ts +565 -0
- package/src/tui/services/open-external.ts +247 -0
- package/src/tui/services/syntax-highlighter.ts +483 -0
- package/src/tui/slash-commands.ts +12 -0
- package/src/tui/theme/index.ts +15 -0
- package/src/tui/theme/provider.tsx +206 -0
- package/src/tui/tip-integration.ts +300 -0
- package/src/tui/types/__tests__/ink-extended.test.ts +121 -0
- package/src/tui/types/ink-extended.ts +87 -0
- package/src/tui/utils/__tests__/bracketedPaste.test.ts +231 -0
- package/src/tui/utils/__tests__/heightEstimator.test.ts +157 -0
- package/src/tui/utils/__tests__/text-width.test.ts +158 -0
- package/src/tui/utils/__tests__/textSanitizer.test.ts +266 -0
- package/src/tui/utils/__tests__/ui-sizing.test.ts +169 -0
- package/src/tui/utils/bracketedPaste.ts +107 -0
- package/src/tui/utils/cursor-manager.ts +131 -0
- package/src/tui/utils/detectTerminal.ts +596 -0
- package/src/tui/utils/findLastSafeSplitPoint.ts +92 -0
- package/src/tui/utils/heightEstimator.ts +198 -0
- package/src/tui/utils/index.ts +91 -0
- package/src/tui/utils/isNarrowWidth.ts +52 -0
- package/src/tui/utils/stdoutGuard.ts +90 -0
- package/src/tui/utils/synchronized-update.ts +70 -0
- package/src/tui/utils/text-width.ts +225 -0
- package/src/tui/utils/textSanitizer.ts +225 -0
- package/src/tui/utils/textUtils.ts +114 -0
- package/src/tui/utils/ui-sizing.ts +192 -0
- package/src/tui-blessed/app.ts +160 -0
- package/src/tui-blessed/index.ts +2 -0
- package/src/tui-blessed/neo-blessed.d.ts +6 -0
- package/src/tui-blessed/test.ts +21 -0
- package/src/tui-blessed/types.ts +14 -0
- package/src/utils/icons.ts +130 -0
- package/src/utils/index.ts +33 -0
- package/src/utils/resume-hint.ts +86 -0
- package/src/version.ts +1 -0
- package/tsconfig.json +8 -0
- package/vitest.config.ts +35 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Autocomplete State Management
|
|
3
|
+
*
|
|
4
|
+
* Provides state management and fuzzy scoring for slash command autocomplete.
|
|
5
|
+
* Used by AutocompletePanel component for real-time command suggestions.
|
|
6
|
+
*
|
|
7
|
+
* @module cli/commands/autocomplete
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { CommandRegistry } from "./registry.js";
|
|
11
|
+
import type { SlashCommand } from "./types.js";
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// T022: State Types
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Autocomplete candidate with scoring information
|
|
19
|
+
*/
|
|
20
|
+
export interface AutocompleteCandidate {
|
|
21
|
+
/** The command being suggested */
|
|
22
|
+
readonly command: SlashCommand;
|
|
23
|
+
/** Match score (higher = better match) */
|
|
24
|
+
readonly score: number;
|
|
25
|
+
/** Character ranges that matched the query [start, end) */
|
|
26
|
+
readonly matchRanges: readonly [start: number, end: number][];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* State for autocomplete feature
|
|
31
|
+
*/
|
|
32
|
+
export interface AutocompleteState {
|
|
33
|
+
/** Whether autocomplete is currently active */
|
|
34
|
+
readonly active: boolean;
|
|
35
|
+
/** Current search query (text after /) */
|
|
36
|
+
readonly query: string;
|
|
37
|
+
/** Matched command candidates sorted by score */
|
|
38
|
+
readonly candidates: readonly AutocompleteCandidate[];
|
|
39
|
+
/** Currently selected candidate index */
|
|
40
|
+
readonly selectedIndex: number;
|
|
41
|
+
/** Precomputed highlight ranges for each command name */
|
|
42
|
+
readonly highlights: Map<string, readonly [start: number, end: number][]>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Initial autocomplete state
|
|
47
|
+
*/
|
|
48
|
+
export const initialAutocompleteState: AutocompleteState = {
|
|
49
|
+
active: false,
|
|
50
|
+
query: "",
|
|
51
|
+
candidates: [],
|
|
52
|
+
selectedIndex: 0,
|
|
53
|
+
highlights: new Map(),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// =============================================================================
|
|
57
|
+
// T024: Action Types
|
|
58
|
+
// =============================================================================
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Actions for autocomplete reducer
|
|
62
|
+
*/
|
|
63
|
+
export type AutocompleteAction =
|
|
64
|
+
| { readonly type: "INPUT_CHANGE"; readonly query: string; readonly registry: CommandRegistry }
|
|
65
|
+
| { readonly type: "SELECT_PREV" }
|
|
66
|
+
| { readonly type: "SELECT_NEXT" }
|
|
67
|
+
| { readonly type: "TAB_COMPLETE" }
|
|
68
|
+
| { readonly type: "CANCEL" };
|
|
69
|
+
|
|
70
|
+
// =============================================================================
|
|
71
|
+
// T023: Fuzzy Scoring Constants
|
|
72
|
+
// =============================================================================
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Scoring weights for fuzzy matching
|
|
76
|
+
*/
|
|
77
|
+
const SCORE_WEIGHTS = {
|
|
78
|
+
/** Bonus for exact match (query equals command name) */
|
|
79
|
+
EXACT_MATCH: 100,
|
|
80
|
+
/** Bonus for prefix match (query is prefix of command name) */
|
|
81
|
+
PREFIX_MATCH: 80,
|
|
82
|
+
/** Bonus for matching at word boundary (after - or _) */
|
|
83
|
+
WORD_BOUNDARY: 3,
|
|
84
|
+
/** Minimum bonus for consecutive character match */
|
|
85
|
+
CONSECUTIVE_MIN: 1,
|
|
86
|
+
/** Maximum bonus for consecutive character match */
|
|
87
|
+
CONSECUTIVE_MAX: 5,
|
|
88
|
+
/** Base score per matched character */
|
|
89
|
+
CHAR_MATCH: 10,
|
|
90
|
+
/** Penalty per unmatched character in target */
|
|
91
|
+
UNMATCHED_PENALTY: -1,
|
|
92
|
+
} as const;
|
|
93
|
+
|
|
94
|
+
// =============================================================================
|
|
95
|
+
// T023: Fuzzy Score Function
|
|
96
|
+
// =============================================================================
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Result of fuzzy scoring
|
|
100
|
+
*/
|
|
101
|
+
export interface FuzzyScoreResult {
|
|
102
|
+
/** Total match score (higher = better match) */
|
|
103
|
+
readonly score: number;
|
|
104
|
+
/** Character ranges that matched [start, end) */
|
|
105
|
+
readonly ranges: readonly [start: number, end: number][];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Calculate fuzzy match score between query and target
|
|
110
|
+
*
|
|
111
|
+
* Scoring rules:
|
|
112
|
+
* - Exact match: +100 (query equals command name)
|
|
113
|
+
* - Prefix match: +80 (query is prefix of command name)
|
|
114
|
+
* - Word boundary: +3 (match at start of word after - or _)
|
|
115
|
+
* - Consecutive chars: +1-5 (bonus for consecutive matches)
|
|
116
|
+
* - Base char match: +10 per matched character
|
|
117
|
+
* - Unmatched penalty: -1 per unmatched character in target
|
|
118
|
+
*
|
|
119
|
+
* @param query - Search query (lowercase comparison)
|
|
120
|
+
* @param target - Target string to match against
|
|
121
|
+
* @returns Score and matched character ranges, or null if no match
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```typescript
|
|
125
|
+
* fuzzyScore('h', 'help');
|
|
126
|
+
* // { score: 90, ranges: [[0, 1]] } // prefix match + char match
|
|
127
|
+
*
|
|
128
|
+
* fuzzyScore('help', 'help');
|
|
129
|
+
* // { score: 140, ranges: [[0, 4]] } // exact match + char matches
|
|
130
|
+
*
|
|
131
|
+
* fuzzyScore('gc', 'git-commit');
|
|
132
|
+
* // { score: 26, ranges: [[0, 1], [4, 5]] } // word boundary bonus
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export function fuzzyScore(query: string, target: string): FuzzyScoreResult | null {
|
|
136
|
+
if (!query) {
|
|
137
|
+
// Empty query matches everything with base score
|
|
138
|
+
return { score: 0, ranges: [] };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const lowerQuery = query.toLowerCase();
|
|
142
|
+
const lowerTarget = target.toLowerCase();
|
|
143
|
+
|
|
144
|
+
// Check for exact match first
|
|
145
|
+
if (lowerQuery === lowerTarget) {
|
|
146
|
+
return {
|
|
147
|
+
score: SCORE_WEIGHTS.EXACT_MATCH + SCORE_WEIGHTS.CHAR_MATCH * query.length,
|
|
148
|
+
ranges: [[0, target.length]],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Check for prefix match
|
|
153
|
+
if (lowerTarget.startsWith(lowerQuery)) {
|
|
154
|
+
return {
|
|
155
|
+
score:
|
|
156
|
+
SCORE_WEIGHTS.PREFIX_MATCH +
|
|
157
|
+
SCORE_WEIGHTS.CHAR_MATCH * query.length +
|
|
158
|
+
SCORE_WEIGHTS.UNMATCHED_PENALTY * (target.length - query.length),
|
|
159
|
+
ranges: [[0, query.length]],
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Fuzzy matching
|
|
164
|
+
return fuzzyMatchCharacters(lowerQuery, lowerTarget, target);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Internal fuzzy matching for non-exact, non-prefix matches
|
|
169
|
+
*/
|
|
170
|
+
function fuzzyMatchCharacters(
|
|
171
|
+
lowerQuery: string,
|
|
172
|
+
lowerTarget: string,
|
|
173
|
+
target: string
|
|
174
|
+
): FuzzyScoreResult | null {
|
|
175
|
+
const ranges: [number, number][] = [];
|
|
176
|
+
let score = 0;
|
|
177
|
+
let queryIndex = 0;
|
|
178
|
+
let consecutiveCount = 0;
|
|
179
|
+
let lastMatchIndex = -2; // -2 so first match isn't considered consecutive
|
|
180
|
+
|
|
181
|
+
for (let i = 0; i < target.length && queryIndex < lowerQuery.length; i++) {
|
|
182
|
+
if (lowerTarget[i] === lowerQuery[queryIndex]) {
|
|
183
|
+
score += scoreMatchedChar(i, lastMatchIndex, consecutiveCount, target);
|
|
184
|
+
consecutiveCount = i === lastMatchIndex + 1 ? consecutiveCount + 1 : 1;
|
|
185
|
+
updateRanges(ranges, i);
|
|
186
|
+
lastMatchIndex = i;
|
|
187
|
+
queryIndex++;
|
|
188
|
+
} else if (queryIndex > 0) {
|
|
189
|
+
score += SCORE_WEIGHTS.UNMATCHED_PENALTY;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// All query characters must be found
|
|
194
|
+
if (queryIndex < lowerQuery.length) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return { score, ranges };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Calculate score for a matched character
|
|
203
|
+
*/
|
|
204
|
+
function scoreMatchedChar(
|
|
205
|
+
i: number,
|
|
206
|
+
lastMatchIndex: number,
|
|
207
|
+
consecutiveCount: number,
|
|
208
|
+
target: string
|
|
209
|
+
): number {
|
|
210
|
+
let charScore = SCORE_WEIGHTS.CHAR_MATCH;
|
|
211
|
+
|
|
212
|
+
// Word boundary bonus
|
|
213
|
+
if (i === 0 || target[i - 1] === "-" || target[i - 1] === "_") {
|
|
214
|
+
charScore += SCORE_WEIGHTS.WORD_BOUNDARY;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Consecutive bonus
|
|
218
|
+
if (i === lastMatchIndex + 1) {
|
|
219
|
+
charScore += Math.min(consecutiveCount + 1, SCORE_WEIGHTS.CONSECUTIVE_MAX);
|
|
220
|
+
} else {
|
|
221
|
+
charScore += SCORE_WEIGHTS.CONSECUTIVE_MIN;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return charScore;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Update match ranges with new match at index i
|
|
229
|
+
*/
|
|
230
|
+
function updateRanges(ranges: [number, number][], i: number): void {
|
|
231
|
+
const lastRange = ranges.length > 0 ? ranges[ranges.length - 1] : undefined;
|
|
232
|
+
if (lastRange && lastRange[1] === i) {
|
|
233
|
+
lastRange[1] = i + 1;
|
|
234
|
+
} else {
|
|
235
|
+
ranges.push([i, i + 1]);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// =============================================================================
|
|
240
|
+
// T025: Highlight Computation
|
|
241
|
+
// =============================================================================
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Compute highlight ranges for all candidates
|
|
245
|
+
*
|
|
246
|
+
* Creates a Map of command names to their highlight ranges for
|
|
247
|
+
* efficient lookup during rendering.
|
|
248
|
+
*
|
|
249
|
+
* @param candidates - List of autocomplete candidates
|
|
250
|
+
* @returns Map of command name to highlight ranges
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* ```typescript
|
|
254
|
+
* const highlights = computeHighlights(candidates);
|
|
255
|
+
* const ranges = highlights.get('help'); // [[0, 1]] for query 'h'
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
export function computeHighlights(
|
|
259
|
+
candidates: readonly AutocompleteCandidate[]
|
|
260
|
+
): Map<string, readonly [start: number, end: number][]> {
|
|
261
|
+
const highlights = new Map<string, readonly [start: number, end: number][]>();
|
|
262
|
+
|
|
263
|
+
for (const candidate of candidates) {
|
|
264
|
+
highlights.set(candidate.command.name, candidate.matchRanges);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return highlights;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// =============================================================================
|
|
271
|
+
// Helper Functions
|
|
272
|
+
// =============================================================================
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Compute candidates from registry for a given query
|
|
276
|
+
*/
|
|
277
|
+
function computeCandidates(
|
|
278
|
+
query: string,
|
|
279
|
+
registry: CommandRegistry
|
|
280
|
+
): readonly AutocompleteCandidate[] {
|
|
281
|
+
const candidates: AutocompleteCandidate[] = [];
|
|
282
|
+
|
|
283
|
+
// Get all commands from registry
|
|
284
|
+
for (const command of registry.list()) {
|
|
285
|
+
// Score against command name
|
|
286
|
+
const result = fuzzyScore(query, command.name);
|
|
287
|
+
|
|
288
|
+
if (result) {
|
|
289
|
+
candidates.push({
|
|
290
|
+
command,
|
|
291
|
+
score: result.score,
|
|
292
|
+
matchRanges: result.ranges,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Also check aliases for better matching
|
|
297
|
+
if (command.aliases) {
|
|
298
|
+
let bestAliasResult: FuzzyScoreResult | null = null;
|
|
299
|
+
|
|
300
|
+
for (const alias of command.aliases) {
|
|
301
|
+
const aliasResult = fuzzyScore(query, alias);
|
|
302
|
+
if (aliasResult && aliasResult.score > (bestAliasResult?.score ?? result?.score ?? 0)) {
|
|
303
|
+
bestAliasResult = aliasResult;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (bestAliasResult && bestAliasResult.score > (result?.score ?? 0)) {
|
|
308
|
+
const existing = candidates.find((c) => c.command === command);
|
|
309
|
+
if (existing) {
|
|
310
|
+
const index = candidates.indexOf(existing);
|
|
311
|
+
candidates.splice(index, 1);
|
|
312
|
+
}
|
|
313
|
+
candidates.push({
|
|
314
|
+
command,
|
|
315
|
+
score: bestAliasResult.score,
|
|
316
|
+
matchRanges: bestAliasResult.ranges,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Sort by score descending, then alphabetically for ties
|
|
323
|
+
candidates.sort((a, b) => {
|
|
324
|
+
if (b.score !== a.score) {
|
|
325
|
+
return b.score - a.score;
|
|
326
|
+
}
|
|
327
|
+
return a.command.name.localeCompare(b.command.name);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
return candidates;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// =============================================================================
|
|
334
|
+
// T024: Autocomplete Reducer
|
|
335
|
+
// =============================================================================
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Reducer for autocomplete state management
|
|
339
|
+
*
|
|
340
|
+
* Handles:
|
|
341
|
+
* - INPUT_CHANGE: Update query and recompute candidates
|
|
342
|
+
* - SELECT_PREV: Move selection up (wraps to bottom)
|
|
343
|
+
* - SELECT_NEXT: Move selection down (wraps to top)
|
|
344
|
+
* - TAB_COMPLETE: Returns state with selected candidate (for completion)
|
|
345
|
+
* - CANCEL: Reset state and deactivate
|
|
346
|
+
*
|
|
347
|
+
* @param state - Current autocomplete state
|
|
348
|
+
* @param action - Action to perform
|
|
349
|
+
* @returns New autocomplete state
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* ```typescript
|
|
353
|
+
* let state = initialAutocompleteState;
|
|
354
|
+
*
|
|
355
|
+
* // User types '/h'
|
|
356
|
+
* state = autocompleteReducer(state, {
|
|
357
|
+
* type: 'INPUT_CHANGE',
|
|
358
|
+
* query: 'h',
|
|
359
|
+
* registry,
|
|
360
|
+
* });
|
|
361
|
+
* // state.candidates contains matching commands
|
|
362
|
+
*
|
|
363
|
+
* // User presses down arrow
|
|
364
|
+
* state = autocompleteReducer(state, { type: 'SELECT_NEXT' });
|
|
365
|
+
* // state.selectedIndex is 1
|
|
366
|
+
*
|
|
367
|
+
* // User presses Tab
|
|
368
|
+
* state = autocompleteReducer(state, { type: 'TAB_COMPLETE' });
|
|
369
|
+
* // Use state.candidates[state.selectedIndex] for completion
|
|
370
|
+
* ```
|
|
371
|
+
*/
|
|
372
|
+
export function autocompleteReducer(
|
|
373
|
+
state: AutocompleteState,
|
|
374
|
+
action: AutocompleteAction
|
|
375
|
+
): AutocompleteState {
|
|
376
|
+
switch (action.type) {
|
|
377
|
+
case "INPUT_CHANGE": {
|
|
378
|
+
const { query, registry } = action;
|
|
379
|
+
|
|
380
|
+
// Empty query deactivates autocomplete
|
|
381
|
+
if (!query) {
|
|
382
|
+
return {
|
|
383
|
+
...initialAutocompleteState,
|
|
384
|
+
active: false,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Compute candidates from registry
|
|
389
|
+
const candidates = computeCandidates(query, registry);
|
|
390
|
+
|
|
391
|
+
// Compute highlights for all candidates
|
|
392
|
+
const highlights = computeHighlights(candidates);
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
active: candidates.length > 0,
|
|
396
|
+
query,
|
|
397
|
+
candidates,
|
|
398
|
+
selectedIndex: 0, // Reset selection on new query
|
|
399
|
+
highlights,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
case "SELECT_PREV": {
|
|
404
|
+
if (!state.active || state.candidates.length === 0) {
|
|
405
|
+
return state;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Wrap to bottom if at top
|
|
409
|
+
const newIndex =
|
|
410
|
+
state.selectedIndex <= 0 ? state.candidates.length - 1 : state.selectedIndex - 1;
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
...state,
|
|
414
|
+
selectedIndex: newIndex,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
case "SELECT_NEXT": {
|
|
419
|
+
if (!state.active || state.candidates.length === 0) {
|
|
420
|
+
return state;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Wrap to top if at bottom
|
|
424
|
+
const newIndex =
|
|
425
|
+
state.selectedIndex >= state.candidates.length - 1 ? 0 : state.selectedIndex + 1;
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
...state,
|
|
429
|
+
selectedIndex: newIndex,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
case "TAB_COMPLETE": {
|
|
434
|
+
// State is returned as-is for the caller to read selectedIndex
|
|
435
|
+
// The caller will use candidates[selectedIndex] for completion
|
|
436
|
+
// Then dispatch CANCEL or INPUT_CHANGE with completed text
|
|
437
|
+
return state;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
case "CANCEL": {
|
|
441
|
+
return {
|
|
442
|
+
...initialAutocompleteState,
|
|
443
|
+
active: false,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
default: {
|
|
448
|
+
// Type guard for exhaustiveness checking
|
|
449
|
+
const exhaustiveCheck: never = action;
|
|
450
|
+
throw new Error(`Unknown action type: ${JSON.stringify(exhaustiveCheck)}`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// =============================================================================
|
|
456
|
+
// Utility Functions
|
|
457
|
+
// =============================================================================
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Get the currently selected candidate (if any)
|
|
461
|
+
*
|
|
462
|
+
* @param state - Current autocomplete state
|
|
463
|
+
* @returns Selected candidate or undefined
|
|
464
|
+
*/
|
|
465
|
+
export function getSelectedCandidate(state: AutocompleteState): AutocompleteCandidate | undefined {
|
|
466
|
+
if (!state.active || state.candidates.length === 0) {
|
|
467
|
+
return undefined;
|
|
468
|
+
}
|
|
469
|
+
return state.candidates[state.selectedIndex];
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Check if autocomplete should be shown
|
|
474
|
+
*
|
|
475
|
+
* @param state - Current autocomplete state
|
|
476
|
+
* @returns True if autocomplete panel should be visible
|
|
477
|
+
*/
|
|
478
|
+
export function shouldShowAutocomplete(state: AutocompleteState): boolean {
|
|
479
|
+
return state.active && state.candidates.length > 0;
|
|
480
|
+
}
|