mirror of
https://github.com/lukaszraczylo/filepuff-mcp.git
synced 2026-10-12 04:54:27 +00:00
fix(search): truncate match lines on a rune boundary
truncateLine byte-sliced s[:maxLen-3], which can split a multibyte UTF-8 character and emit an invalid sequence (mojibake) on long non-ASCII match lines. Back off to the nearest rune boundary via utf8.RuneStart. Adds TestTruncateLineRuneSafe.
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
json "github.com/goccy/go-json"
|
json "github.com/goccy/go-json"
|
||||||
"github.com/lukaszraczylo/mcp-filepuff/internal/config"
|
"github.com/lukaszraczylo/mcp-filepuff/internal/config"
|
||||||
@@ -488,10 +489,15 @@ func writeClusteredResults(sb *strings.Builder, results []Result) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// truncateLine truncates a line if it exceeds maxLen.
|
// truncateLine truncates a line if it exceeds maxLen bytes, backing off to a
|
||||||
|
// rune boundary so a multibyte character is never split into invalid UTF-8.
|
||||||
func truncateLine(s string, maxLen int) string {
|
func truncateLine(s string, maxLen int) string {
|
||||||
if len(s) <= maxLen {
|
if len(s) <= maxLen {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
return s[:maxLen-3] + "..."
|
end := max(maxLen-3, 0)
|
||||||
|
for end > 0 && !utf8.RuneStart(s[end]) {
|
||||||
|
end--
|
||||||
|
}
|
||||||
|
return s[:end] + "..."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,27 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/lukaszraczylo/mcp-filepuff/internal/config"
|
"github.com/lukaszraczylo/mcp-filepuff/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// truncateLine must back off to a rune boundary so multibyte characters are
|
||||||
|
// never split into invalid UTF-8.
|
||||||
|
func TestTruncateLineRuneSafe(t *testing.T) {
|
||||||
|
s := strings.Repeat("é", 200) // 2 bytes each → 400 bytes
|
||||||
|
out := truncateLine(s, 50)
|
||||||
|
if !utf8.ValidString(out) {
|
||||||
|
t.Errorf("truncated output is not valid UTF-8: %q", out)
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(out, "...") {
|
||||||
|
t.Errorf("expected ellipsis suffix, got %q", out)
|
||||||
|
}
|
||||||
|
if got := truncateLine("hi", 50); got != "hi" {
|
||||||
|
t.Errorf("short string should be unchanged, got %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNew(t *testing.T) {
|
func TestNew(t *testing.T) {
|
||||||
cfg := config.Default()
|
cfg := config.Default()
|
||||||
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
|
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
|
||||||
|
|||||||
Reference in New Issue
Block a user