Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
4.3 kB · 150 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151import { useEffect, useRef } from 'react'import { logError } from 'src/utils/log.js'import { z } from 'zod/v4'import type { ConnectedMCPServer, MCPServerConnection,} from '../services/mcp/types.js'import { getConnectedIdeClient } from '../utils/ide.js'import { lazySchema } from '../utils/lazySchema.js'export type SelectionPoint = { line: number character: number}
export type SelectionData = { selection: { start: SelectionPoint end: SelectionPoint } | null text?: string filePath?: string}
export type IDESelection = { lineCount: number lineStart?: number text?: string filePath?: string}
// Define the selection changed notification schemaconst SelectionChangedSchema = lazySchema(() => z.object({ method: z.literal('selection_changed'), params: z.object({ selection: z .object({ start: z.object({ line: z.number(), character: z.number(), }), end: z.object({ line: z.number(), character: z.number(), }), }) .nullable() .optional(), text: z.string().optional(), filePath: z.string().optional(), }), }),)
/** * A hook that tracks IDE text selection information by directly registering * with MCP client notification handlers */export function useIdeSelection( mcpClients: MCPServerConnection[], onSelect: (selection: IDESelection) => void,): void { const handlersRegistered = useRef(false) const currentIDERef = useRef<ConnectedMCPServer | null>(null)
useEffect(() => { // Find the IDE client from the MCP clients list const ideClient = getConnectedIdeClient(mcpClients)
// If the IDE client changed, we need to re-register handlers. // Normalize undefined to null so the initial ref value (null) matches // "no IDE found" (undefined), avoiding spurious resets on every MCP update. if (currentIDERef.current !== (ideClient ?? null)) { handlersRegistered.current = false currentIDERef.current = ideClient || null // Reset the selection when the IDE client changes. onSelect({ lineCount: 0, lineStart: undefined, text: undefined, filePath: undefined, }) }
// Skip if we've already registered handlers for the current IDE or if there's no IDE client if (handlersRegistered.current || !ideClient) { return }
// Handler function for selection changes const selectionChangeHandler = (data: SelectionData) => { if (data.selection?.start && data.selection?.end) { const { start, end } = data.selection let lineCount = end.line - start.line + 1 // If on the first character of the line, do not count the line // as being selected. if (end.character === 0) { lineCount-- } const selection = { lineCount, lineStart: start.line, text: data.text, filePath: data.filePath, }
onSelect(selection) } }
// Register notification handler for selection_changed events ideClient.client.setNotificationHandler( SelectionChangedSchema(), notification => { if (currentIDERef.current !== ideClient) { return }
try { // Get the selection data from the notification params const selectionData = notification.params
// Process selection data - validate it has required properties if ( selectionData.selection && selectionData.selection.start && selectionData.selection.end ) { // Handle selection changes selectionChangeHandler(selectionData as SelectionData) } else if (selectionData.text !== undefined) { // Handle empty selection (when text is empty string) selectionChangeHandler({ selection: null, text: selectionData.text, filePath: selectionData.filePath, }) } } catch (error) { logError(error as Error) } }, )
// Mark that we've registered handlers handlersRegistered.current = true
// No cleanup needed as MCP clients manage their own lifecycle }, [mcpClients, onSelect])}