mirror of
https://github.com/lukaszraczylo/filepuff-mcp.git
synced 2026-10-12 04:54:27 +00:00
refactor(ast_query): emit cursor footer from formatter, not string-replace
handleASTQuery rebuilt the formatter's '[remaining: N]' placeholder string and strings.ReplaceAll'd it with the cursor line, plus a fallback append — fragile coupling to the formatter's exact output. New FormatResultsWithCursor appends the prebuilt '[cursor: ..., remaining: N]' footer in place of the bare hint when results are truncated; the handler just passes it through. FormatResultsWithOptions is unchanged (delegates with empty cursor). Adds TestFormatResultsWithCursor.
This commit is contained in:
@@ -128,6 +128,33 @@ func TestFormatResultsPaginationCursor(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// FormatResultsWithCursor must emit the supplied cursor footer verbatim in place
|
||||
// of the bare "[remaining: N]" hint when results are truncated.
|
||||
func TestFormatResultsWithCursor(t *testing.T) {
|
||||
results := makeResults(5)
|
||||
cursor := "[cursor: abc123, remaining: 3]"
|
||||
|
||||
out := FormatResultsWithCursor(results, 2, "compact", 0, false, cursor)
|
||||
if !strings.Contains(out, cursor) {
|
||||
t.Errorf("expected cursor footer %q, got:\n%s", cursor, out)
|
||||
}
|
||||
if strings.Contains(out, "[remaining: 3]\n") {
|
||||
t.Errorf("bare remaining hint should be replaced by cursor footer, got:\n%s", out)
|
||||
}
|
||||
|
||||
// Empty cursorLine falls back to the bare hint (parity with FormatResultsWithOptions).
|
||||
fallback := FormatResultsWithCursor(results, 2, "compact", 0, false, "")
|
||||
if !strings.Contains(fallback, "[remaining: 3]") {
|
||||
t.Errorf("empty cursorLine should fall back to [remaining: 3], got:\n%s", fallback)
|
||||
}
|
||||
|
||||
// No truncation → no footer at all.
|
||||
none := FormatResultsWithCursor(makeResults(2), 0, "compact", 0, false, cursor)
|
||||
if strings.Contains(none, cursor) || strings.Contains(none, "[remaining:") {
|
||||
t.Errorf("no truncation should emit no footer, got:\n%s", none)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatResultsEmpty(t *testing.T) {
|
||||
out := FormatResultsWithOptions(nil, 0, "verbose", 0)
|
||||
if out != "No matches found." {
|
||||
|
||||
+25
-5
@@ -460,7 +460,24 @@ func FormatResults(results []MatchResult, maxResults int) string {
|
||||
// format: "verbose" (default) | "compact" | "location"
|
||||
// offset: skip this many results before rendering (used for cursor pagination).
|
||||
// verbose: opt-in variadic — pass true to restore "Found N match(es):" preamble (v1 behaviour).
|
||||
// When results are truncated it emits a bare "[remaining: N]" hint; callers that
|
||||
// have a pagination cursor should use FormatResultsWithCursor to emit the full line.
|
||||
func FormatResultsWithOptions(results []MatchResult, maxResults int, format string, offset int, verbose ...bool) string {
|
||||
return formatResults(results, maxResults, format, offset, len(verbose) > 0 && verbose[0], "")
|
||||
}
|
||||
|
||||
// FormatResultsWithCursor is like FormatResultsWithOptions but, when results are
|
||||
// truncated, appends cursorLine verbatim instead of the bare "[remaining: N]"
|
||||
// hint. This lets the caller pass a fully-formed "[cursor: ..., remaining: N]"
|
||||
// footer without string-replacing a placeholder. If cursorLine is empty it
|
||||
// behaves like FormatResultsWithOptions.
|
||||
func FormatResultsWithCursor(results []MatchResult, maxResults int, format string, offset int, verbose bool, cursorLine string) string {
|
||||
return formatResults(results, maxResults, format, offset, verbose, cursorLine)
|
||||
}
|
||||
|
||||
// formatResults is the shared implementation. cursorLine, when non-empty, is
|
||||
// emitted in place of the "[remaining: N]" hint on truncation.
|
||||
func formatResults(results []MatchResult, maxResults int, format string, offset int, verbose bool, cursorLine string) string {
|
||||
if len(results) == 0 {
|
||||
return "No matches found."
|
||||
}
|
||||
@@ -484,8 +501,7 @@ func FormatResultsWithOptions(results []MatchResult, maxResults int, format stri
|
||||
var sb strings.Builder
|
||||
|
||||
// Emit preamble only when verbose=true is explicitly passed (opt-in, default off).
|
||||
wantVerbose := len(verbose) > 0 && verbose[0]
|
||||
if wantVerbose {
|
||||
if verbose {
|
||||
fmt.Fprintf(&sb, "Found %d match(es):\n", renderCount)
|
||||
}
|
||||
|
||||
@@ -547,9 +563,13 @@ func FormatResultsWithOptions(results []MatchResult, maxResults int, format stri
|
||||
}
|
||||
|
||||
if remaining > 0 {
|
||||
// Caller must embed the cursor token; we just append the remaining count hint.
|
||||
// The actual [cursor: ...] line is written by the handler after calling MakeCursor.
|
||||
fmt.Fprintf(&sb, "[remaining: %d]\n", remaining)
|
||||
if cursorLine != "" {
|
||||
// Caller supplied a fully-formed "[cursor: ..., remaining: N]" footer.
|
||||
sb.WriteString(strings.TrimRight(cursorLine, "\n"))
|
||||
sb.WriteByte('\n')
|
||||
} else {
|
||||
fmt.Fprintf(&sb, "[remaining: %d]\n", remaining)
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
|
||||
@@ -224,16 +224,9 @@ func (s *Server) handleASTQuery(ctx context.Context, request mcp.CallToolRequest
|
||||
allResults := s.runASTQueryWalk(ctx, p, exts)
|
||||
cursorFooter := buildASTCursorFooter(len(allResults), p.offset, p.maxResults, p.queryHash)
|
||||
|
||||
output := query.FormatResultsWithOptions(allResults, p.maxResults, p.format, p.offset, p.verbose)
|
||||
if cursorFooter != "" {
|
||||
// Replace the [remaining: N] placeholder emitted by FormatResultsWithOptions
|
||||
// with the full [cursor: ..., remaining: N] line.
|
||||
output = strings.ReplaceAll(output, fmt.Sprintf("[remaining: %d]\n", len(allResults)-p.offset-p.maxResults), cursorFooter+"\n")
|
||||
// Fallback: if placeholder wasn't present (e.g. format=location), append footer.
|
||||
if !strings.Contains(output, cursorFooter) {
|
||||
output = strings.TrimRight(output, "\n") + "\n" + cursorFooter + "\n"
|
||||
}
|
||||
}
|
||||
// The formatter emits cursorFooter in place of its "[remaining: N]" hint when
|
||||
// results are truncated; no fragile placeholder string-replacement needed.
|
||||
output := query.FormatResultsWithCursor(allResults, p.maxResults, p.format, p.offset, p.verbose, cursorFooter)
|
||||
return mcp.NewToolResultText(output), nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user