init claude-code
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import { logForDebugging } from '../../utils/debug.js'
|
||||
import { truncate } from '../../utils/format.js'
|
||||
import { getFsImplementation } from '../../utils/fsOperations.js'
|
||||
import { expandPath } from '../../utils/path.js'
|
||||
|
||||
const MAX_READ_BYTES = 64 * 1024
|
||||
|
||||
/**
|
||||
* Extracts the symbol/word at a specific position in a file.
|
||||
* Used to show context in tool use messages.
|
||||
*
|
||||
* @param filePath - The file path (absolute or relative)
|
||||
* @param line - 0-indexed line number
|
||||
* @param character - 0-indexed character position on the line
|
||||
*
|
||||
* Note: This uses synchronous file I/O because it is called from
|
||||
* renderToolUseMessage (a synchronous React render function). The read is
|
||||
* wrapped in try/catch so ENOENT and other errors fall back gracefully.
|
||||
* @returns The symbol at that position, or null if extraction fails
|
||||
*/
|
||||
export function getSymbolAtPosition(
|
||||
filePath: string,
|
||||
line: number,
|
||||
character: number,
|
||||
): string | null {
|
||||
try {
|
||||
const fs = getFsImplementation()
|
||||
const absolutePath = expandPath(filePath)
|
||||
|
||||
// Read only the first 64KB instead of the whole file. Most LSP hover/goto
|
||||
// targets are near recent edits; 64KB covers ~1000 lines of typical code.
|
||||
// If the target line is past this window we fall back to null (the UI
|
||||
// already handles that by showing `position: line:char`).
|
||||
// eslint-disable-next-line custom-rules/no-sync-fs -- called from sync React render (renderToolUseMessage)
|
||||
const { buffer, bytesRead } = fs.readSync(absolutePath, {
|
||||
length: MAX_READ_BYTES,
|
||||
})
|
||||
const content = buffer.toString('utf-8', 0, bytesRead)
|
||||
const lines = content.split('\n')
|
||||
|
||||
if (line < 0 || line >= lines.length) {
|
||||
return null
|
||||
}
|
||||
// If we filled the full buffer the file continues past our window,
|
||||
// so the last split element may be truncated mid-line.
|
||||
if (bytesRead === MAX_READ_BYTES && line === lines.length - 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
const lineContent = lines[line]
|
||||
if (!lineContent || character < 0 || character >= lineContent.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Extract the word/symbol at the character position
|
||||
// Pattern matches:
|
||||
// - Standard identifiers: alphanumeric + underscore + dollar
|
||||
// - Rust lifetimes: 'a, 'static
|
||||
// - Rust macros: macro_name!
|
||||
// - Operators and special symbols: +, -, *, etc.
|
||||
// This is more inclusive to handle various programming languages
|
||||
const symbolPattern = /[\w$'!]+|[+\-*/%&|^~<>=]+/g
|
||||
let match: RegExpExecArray | null
|
||||
|
||||
while ((match = symbolPattern.exec(lineContent)) !== null) {
|
||||
const start = match.index
|
||||
const end = start + match[0].length
|
||||
|
||||
// Check if the character position falls within this match
|
||||
if (character >= start && character < end) {
|
||||
const symbol = match[0]
|
||||
// Limit length to 30 characters to avoid overly long symbols
|
||||
return truncate(symbol, 30)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
} catch (error) {
|
||||
// Log unexpected errors for debugging (permission issues, encoding problems, etc.)
|
||||
// Use logForDebugging since this is a display enhancement, not a critical error
|
||||
if (error instanceof Error) {
|
||||
logForDebugging(
|
||||
`Symbol extraction failed for ${filePath}:${line}:${character}: ${error.message}`,
|
||||
{ level: 'warn' },
|
||||
)
|
||||
}
|
||||
// Still return null for graceful fallback to position display
|
||||
return null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user