SearchTool.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import { Box, Text } from "ink"
  2. import * as theme from "../../theme.js"
  3. import { Icon } from "../Icon.js"
  4. import type { ToolRendererProps } from "./types.js"
  5. import { truncateText, sanitizeContent, getToolDisplayName, getToolIconName } from "./utils.js"
  6. const MAX_RESULT_LINES = 15
  7. export function SearchTool({ toolData }: ToolRendererProps) {
  8. const iconName = getToolIconName(toolData.tool)
  9. const displayName = getToolDisplayName(toolData.tool)
  10. const regex = toolData.regex || ""
  11. const query = toolData.query || ""
  12. const filePattern = toolData.filePattern || ""
  13. const path = toolData.path || ""
  14. const content = toolData.content ? sanitizeContent(toolData.content) : ""
  15. // Parse search results if content looks like results.
  16. const resultLines = content.split("\n").filter((line) => line.trim())
  17. const matchCount = resultLines.length
  18. const { text: previewContent, truncated, hiddenLines } = truncateText(content, MAX_RESULT_LINES)
  19. return (
  20. <Box flexDirection="column" paddingX={1}>
  21. {/* Header */}
  22. <Box>
  23. <Icon name={iconName} color={theme.toolHeader} />
  24. <Text bold color={theme.toolHeader}>
  25. {" "}
  26. {displayName}
  27. </Text>
  28. {matchCount > 0 && <Text color={theme.dimText}> ({matchCount} matches)</Text>}
  29. </Box>
  30. {/* Search parameters */}
  31. <Box flexDirection="column" marginLeft={2}>
  32. {/* Regex/Query */}
  33. {regex && (
  34. <Box>
  35. <Text color={theme.dimText}>regex: </Text>
  36. <Text color={theme.warningColor} bold>
  37. {regex}
  38. </Text>
  39. </Box>
  40. )}
  41. {query && (
  42. <Box>
  43. <Text color={theme.dimText}>query: </Text>
  44. <Text color={theme.warningColor} bold>
  45. {query}
  46. </Text>
  47. </Box>
  48. )}
  49. {/* Search scope */}
  50. <Box>
  51. {path && (
  52. <>
  53. <Text color={theme.dimText}>path: </Text>
  54. <Text color={theme.text}>{path}</Text>
  55. </>
  56. )}
  57. {filePattern && (
  58. <>
  59. <Text color={theme.dimText}> pattern: </Text>
  60. <Text color={theme.text}>{filePattern}</Text>
  61. </>
  62. )}
  63. </Box>
  64. </Box>
  65. {/* Results */}
  66. {previewContent && (
  67. <Box flexDirection="column" marginLeft={2} marginTop={1}>
  68. <Text color={theme.dimText} bold>
  69. Results:
  70. </Text>
  71. <Box flexDirection="column" marginTop={0}>
  72. {previewContent.split("\n").map((line, i) => {
  73. // Try to highlight file:line patterns
  74. const match = line.match(/^([^:]+):(\d+):(.*)$/)
  75. if (match) {
  76. const [, file, lineNum, context] = match
  77. return (
  78. <Box key={i}>
  79. <Text color={theme.focusColor}>{file}</Text>
  80. <Text color={theme.dimText}>:</Text>
  81. <Text color={theme.warningColor}>{lineNum}</Text>
  82. <Text color={theme.dimText}>:</Text>
  83. <Text color={theme.toolText}>{context}</Text>
  84. </Box>
  85. )
  86. }
  87. return (
  88. <Text key={i} color={theme.toolText}>
  89. {line}
  90. </Text>
  91. )
  92. })}
  93. </Box>
  94. {truncated && (
  95. <Text color={theme.dimText} dimColor>
  96. ... ({hiddenLines} more results)
  97. </Text>
  98. )}
  99. </Box>
  100. )}
  101. </Box>
  102. )
  103. }