search.go

  1package chat
  2
  3import (
  4	"encoding/json"
  5
  6	"github.com/charmbracelet/crush/internal/agent/tools"
  7	"github.com/charmbracelet/crush/internal/fsext"
  8	"github.com/charmbracelet/crush/internal/message"
  9	"github.com/charmbracelet/crush/internal/ui/styles"
 10)
 11
 12// -----------------------------------------------------------------------------
 13// Glob Tool
 14// -----------------------------------------------------------------------------
 15
 16// GlobToolMessageItem is a message item that represents a glob tool call.
 17type GlobToolMessageItem struct {
 18	*baseToolMessageItem
 19}
 20
 21var _ ToolMessageItem = (*GlobToolMessageItem)(nil)
 22
 23// NewGlobToolMessageItem creates a new [GlobToolMessageItem].
 24func NewGlobToolMessageItem(
 25	sty *styles.Styles,
 26	toolCall message.ToolCall,
 27	result *message.ToolResult,
 28	canceled bool,
 29) ToolMessageItem {
 30	return newBaseToolMessageItem(sty, toolCall, result, &GlobToolRenderContext{}, canceled)
 31}
 32
 33// GlobToolRenderContext renders glob tool messages.
 34type GlobToolRenderContext struct{}
 35
 36// RenderTool implements the [ToolRenderer] interface.
 37func (g *GlobToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
 38	cappedWidth := cappedMessageWidth(width)
 39	if !opts.ToolCall.Finished && !opts.Canceled {
 40		return pendingTool(sty, "Glob", opts.Anim)
 41	}
 42
 43	var params tools.GlobParams
 44	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
 45		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
 46	}
 47
 48	toolParams := []string{params.Pattern}
 49	if params.Path != "" {
 50		toolParams = append(toolParams, "path", params.Path)
 51	}
 52
 53	header := toolHeader(sty, opts.Status(), "Glob", cappedWidth, opts.Nested, toolParams...)
 54	if opts.Nested {
 55		return header
 56	}
 57
 58	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
 59		return joinToolParts(header, earlyState)
 60	}
 61
 62	if opts.Result == nil || opts.Result.Content == "" {
 63		return header
 64	}
 65
 66	bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
 67	body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.Expanded))
 68	return joinToolParts(header, body)
 69}
 70
 71// -----------------------------------------------------------------------------
 72// Grep Tool
 73// -----------------------------------------------------------------------------
 74
 75// GrepToolMessageItem is a message item that represents a grep tool call.
 76type GrepToolMessageItem struct {
 77	*baseToolMessageItem
 78}
 79
 80var _ ToolMessageItem = (*GrepToolMessageItem)(nil)
 81
 82// NewGrepToolMessageItem creates a new [GrepToolMessageItem].
 83func NewGrepToolMessageItem(
 84	sty *styles.Styles,
 85	toolCall message.ToolCall,
 86	result *message.ToolResult,
 87	canceled bool,
 88) ToolMessageItem {
 89	return newBaseToolMessageItem(sty, toolCall, result, &GrepToolRenderContext{}, canceled)
 90}
 91
 92// GrepToolRenderContext renders grep tool messages.
 93type GrepToolRenderContext struct{}
 94
 95// RenderTool implements the [ToolRenderer] interface.
 96func (g *GrepToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
 97	cappedWidth := cappedMessageWidth(width)
 98	if !opts.ToolCall.Finished && !opts.Canceled {
 99		return pendingTool(sty, "Grep", opts.Anim)
100	}
101
102	var params tools.GrepParams
103	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
104		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
105	}
106
107	toolParams := []string{params.Pattern}
108	if params.Path != "" {
109		toolParams = append(toolParams, "path", params.Path)
110	}
111	if params.Include != "" {
112		toolParams = append(toolParams, "include", params.Include)
113	}
114	if params.LiteralText {
115		toolParams = append(toolParams, "literal", "true")
116	}
117
118	header := toolHeader(sty, opts.Status(), "Grep", cappedWidth, opts.Nested, toolParams...)
119	if opts.Nested {
120		return header
121	}
122
123	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
124		return joinToolParts(header, earlyState)
125	}
126
127	if opts.Result == nil || opts.Result.Content == "" {
128		return header
129	}
130
131	bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
132	body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.Expanded))
133	return joinToolParts(header, body)
134}
135
136// -----------------------------------------------------------------------------
137// LS Tool
138// -----------------------------------------------------------------------------
139
140// LSToolMessageItem is a message item that represents an ls tool call.
141type LSToolMessageItem struct {
142	*baseToolMessageItem
143}
144
145var _ ToolMessageItem = (*LSToolMessageItem)(nil)
146
147// NewLSToolMessageItem creates a new [LSToolMessageItem].
148func NewLSToolMessageItem(
149	sty *styles.Styles,
150	toolCall message.ToolCall,
151	result *message.ToolResult,
152	canceled bool,
153) ToolMessageItem {
154	return newBaseToolMessageItem(sty, toolCall, result, &LSToolRenderContext{}, canceled)
155}
156
157// LSToolRenderContext renders ls tool messages.
158type LSToolRenderContext struct{}
159
160// RenderTool implements the [ToolRenderer] interface.
161func (l *LSToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
162	cappedWidth := cappedMessageWidth(width)
163	if !opts.ToolCall.Finished && !opts.Canceled {
164		return pendingTool(sty, "List", opts.Anim)
165	}
166
167	var params tools.LSParams
168	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
169		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
170	}
171
172	path := params.Path
173	if path == "" {
174		path = "."
175	}
176	path = fsext.PrettyPath(path)
177
178	header := toolHeader(sty, opts.Status(), "List", cappedWidth, opts.Nested, path)
179	if opts.Nested {
180		return header
181	}
182
183	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
184		return joinToolParts(header, earlyState)
185	}
186
187	if opts.Result == nil || opts.Result.Content == "" {
188		return header
189	}
190
191	bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
192	body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.Expanded))
193	return joinToolParts(header, body)
194}