Download apps/vscode/webview-ui/src/components/inputarea/hooks/useSlashMenu.ts from SaylorTwift/kimi-code: direct link, hf CLI and curl.
- Browser
- Download file 3.29 kB
-
https://huggingface.co/SaylorTwift/kimi-code/resolve/main/apps/vscode/webview-ui/src/components/inputarea/hooks/useSlashMenu.ts
- Command line
-
hf download hf://SaylorTwift/kimi-code/apps/vscode/webview-ui/src/components/inputarea/hooks/useSlashMenu.ts
-
curl -L -o useSlashMenu.ts https://huggingface.co/SaylorTwift/kimi-code/resolve/main/apps/vscode/webview-ui/src/components/inputarea/hooks/useSlashMenu.ts
3.29 kB
| import { useMemo, useState, useCallback } from "react"; | |
| import { useSettingsStore } from "@/stores"; | |
| import type { SlashCommandInfo } from "shared/legacy-sdk"; | |
| interface ActiveToken { | |
| trigger: "/" | "@"; | |
| start: number; | |
| query: string; | |
| } | |
| function fuzzyMatch(text: string, query: string): boolean { | |
| if (!query) { | |
| return true; | |
| } | |
| const lowerText = text.toLowerCase(); | |
| const lowerQuery = query.toLowerCase(); | |
| let qi = 0; | |
| for (let i = 0; i < lowerText.length && qi < lowerQuery.length; i++) { | |
| if (lowerText[i] === lowerQuery[qi]) { | |
| qi++; | |
| } | |
| } | |
| return qi === lowerQuery.length; | |
| } | |
| export function findActiveToken(text: string, cursorPos: number): ActiveToken | null { | |
| const beforeCursor = text.slice(0, cursorPos); | |
| const lastSpace = Math.max(beforeCursor.lastIndexOf(" "), beforeCursor.lastIndexOf("\n"), beforeCursor.lastIndexOf("\t"), -1); | |
| const currentWord = beforeCursor.slice(lastSpace + 1); | |
| if (currentWord.startsWith("@")) { | |
| return { trigger: "@", start: lastSpace + 1, query: currentWord.slice(1) }; | |
| } | |
| if (currentWord.startsWith("/")) { | |
| return { trigger: "/", start: lastSpace + 1, query: currentWord.slice(1) }; | |
| } | |
| return null; | |
| } | |
| interface UseSlashMenuResult { | |
| showSlashMenu: boolean; | |
| filteredCommands: SlashCommandInfo[]; | |
| selectedIndex: number; | |
| setSelectedIndex: (index: number) => void; | |
| handleSlashMenuKey: (e: React.KeyboardEvent) => boolean; | |
| resetSlashMenu: () => void; | |
| } | |
| export function useSlashMenu(activeToken: ActiveToken | null, onSelectCommand: (name: string) => void, onCancel: () => void): UseSlashMenuResult { | |
| const [selectedIndex, setSelectedIndex] = useState(0); | |
| const { slashCommands } = useSettingsStore(); | |
| const showSlashMenu = activeToken?.trigger === "/"; | |
| const filteredCommands = useMemo(() => { | |
| if (!showSlashMenu) { | |
| return []; | |
| } | |
| const q = activeToken.query; | |
| if (!q) { | |
| return slashCommands; | |
| } | |
| return slashCommands.filter((cmd) => fuzzyMatch(cmd.name, q) || fuzzyMatch(cmd.description, q)); | |
| }, [showSlashMenu, activeToken?.query, slashCommands]); | |
| const resetSlashMenu = useCallback(() => { | |
| setSelectedIndex(0); | |
| }, []); | |
| const handleSlashMenuKey = useCallback( | |
| (e: React.KeyboardEvent): boolean => { | |
| if (!showSlashMenu || filteredCommands.length === 0) { | |
| return false; | |
| } | |
| switch (e.key) { | |
| case "ArrowDown": | |
| e.preventDefault(); | |
| setSelectedIndex((i) => Math.min(i + 1, filteredCommands.length - 1)); | |
| return true; | |
| case "ArrowUp": | |
| e.preventDefault(); | |
| setSelectedIndex((i) => Math.max(i - 1, 0)); | |
| return true; | |
| case "Tab": | |
| case "Enter": { | |
| e.preventDefault(); | |
| const cmd = filteredCommands[selectedIndex]; | |
| if (cmd) { | |
| onSelectCommand(cmd.name); | |
| } | |
| return true; | |
| } | |
| case "Escape": | |
| e.preventDefault(); | |
| onCancel(); | |
| return true; | |
| default: | |
| return false; | |
| } | |
| }, | |
| [showSlashMenu, filteredCommands, selectedIndex, onSelectCommand, onCancel], | |
| ); | |
| return { | |
| showSlashMenu, | |
| filteredCommands, | |
| selectedIndex, | |
| setSelectedIndex, | |
| handleSlashMenuKey, | |
| resetSlashMenu, | |
| }; | |
| } | |