# All files / src/components/tools SearchCard.tsx

82.35% Statements 14/17
76.92% Branches 30/39
60% Functions 3/5
86.66% Lines 13/15

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102

    15x

    15x
    15x
    15x
    15x

    44x

    15x
    15x
    15x

    35x
    15x
    15x

    15x

     import { useState, type JSX } from "react";
    import { ChevronRight, Search } from "lucide-react";
    import type { ToolCardProps } from "./ToolCardProps.js";
    import { ICON_SM, ICON_MD } from "../../utils/iconSize.js";
    import styles from "./toolCards.module.scss";

    /** Extracts search-relevant fields from tool args. */
    function getSearchInfo(args: unknown): { pattern: string; path: string } {
      Iif (args === null || args === undefined || typeof args !== "object") {
        return { pattern: "", path: "" };
      }
      const a = args as Record<string, unknown>;
      const pattern = typeof a.pattern === "string" ? a.pattern : "";
      const path = typeof a.path === "string" ? a.path : "";
      return { pattern, path };
    }

    /** Number of result lines shown when collapsed. */
    const PREVIEW_LINES: number = 5;

    /** Renders a search tool call (Grep, Glob) with pattern and match results. */
    export function SearchCard({ tool, args, result, isError }: ToolCardProps): JSX.Element {
      const [expanded, setExpanded] = useState(false);
      const { pattern, path } = getSearchInfo(args);
      const inProgress = result === undefined;

      const lines = result?.split("\n").filter((l) => l.length > 0) ?? [];
      const hasMore = lines.length > PREVIEW_LINES;
      const displayLines = expanded ? lines : lines.slice(0, PREVIEW_LINES);

      return (
        <div
          className={`${styles.card} ${isError ? styles.cardRed : styles.cardPurple} ${inProgress ? styles.inProgress : ""}`}
          data-testid="tool-card-search"
        >
          <div className={styles.header}>
            <span className={styles.icon}>
              <Search size={ICON_MD} />
            </span>
            <span className={styles.toolName} style={{ color: "var(--accent-purple, #a78bfa)" }}>
              {tool}
            </span>
            {pattern && (
              <span className={styles.fileName} data-testid="tool-card-pattern">
                &quot;{pattern}&quot;
              </span>
            )}
            {path && (
              <span
                className={styles.fileName}
                style={{ flexShrink: 1 }}
                data-testid="tool-card-search-path"
              >
                in {path}
              </span>
            )}
            {!inProgress && !isError && lines.length > 0 && (
              <>
                <span className={styles.spacer} />
                <span className={styles.badge} data-testid="tool-card-match-count">
                  {lines.length} {lines.length === 1 ? "match" : "matches"}
                </span>
              </>
            )}
          </div>

          {isError && result && (
            <pre className={styles.pre} data-testid="tool-card-error">
              {result}
            </pre>
          )}

          {!isError && !inProgress && displayLines.length > 0 && (
            <>
              <pre className={styles.pre} data-testid="tool-card-results">
                {displayLines.join("\n")}
              </pre>
              {hasMore && (
                <button
                  type="button"
                  className={styles.bodyToggle}
                  onClick={() => {
                    setExpanded((v) => !v);
                  }}
                  aria-expanded={expanded}
                  data-testid="tool-card-toggle"
                >
                  <span
                    className={`${styles.chevron} ${expanded ? styles.chevronExpanded : ""}`}
                    aria-hidden="true"
                  >
                    <ChevronRight size={ICON_SM} />
                  </span>
                  {expanded ? "collapse" : `${lines.length - PREVIEW_LINES} more matches`}
                </button>
              )}
            </>
          )}
        </div>
      );
    }