on main help sign in
fix(tools/view): fix view paging, test for edge cases
commit058322169d3c1b3e1ffad43da8bb7c00c94e706d authorChristian Rocha <christian@rocha.is> dateThu 26 Feb 2026, 14:38 (8 months ago) parentd98c854
2 files changed+90−3
internal/agent/tools/view.go+3−3 internal/agent/tools/view_test.go+87
internal/agent/tools/view.go+3−3
@@ -272,7 +272,7 @@ func readTextFile(filePath string, offset, limit int) (string, bool, error) {
272272 // Pre-allocate slice with expected capacity.
273273 lines := make([]string, 0, limit)
274274
275− for scanner.Scan() && len(lines) < limit {
275+ for len(lines) < limit && scanner.Scan() {
276276 lineText := scanner.Text()
277277 if len(lineText) > MaxLineLength {
278278 lineText = lineText[:MaxLineLength] + "..."
@@ -280,8 +280,8 @@ func readTextFile(filePath string, offset, limit int) (string, bool, error) {
280280 lines = append(lines, lineText)
281281 }
282282
283− // Peek one more line to determine if the file has more content.
284− hasMore := scanner.Scan()
283+ // Peek one more line only when we filled the limit.
284+ hasMore := len(lines) == limit && scanner.Scan()
285285
286286 if err := scanner.Err(); err != nil {
287287 return "", false, err
internal/agent/tools/view_test.gonew file+87
@@ -0,0 +1,87 @@
1+package tools
2+
3+import (
4+ "fmt"
5+ "os"
6+ "path/filepath"
7+ "strings"
8+ "testing"
9+
10+ "github.com/stretchr/testify/require"
11+)
12+
13+func TestReadTextFileBoundaryCases(t *testing.T) {
14+ t.Parallel()
15+
16+ tmpDir := t.TempDir()
17+ filePath := filepath.Join(tmpDir, "sample.txt")
18+
19+ var allLines []string
20+ for i := range 5 {
21+ allLines = append(allLines, fmt.Sprintf("line %d", i+1))
22+ }
23+ require.NoError(t, os.WriteFile(filePath, []byte(strings.Join(allLines, "\n")), 0o644))
24+
25+ tests := []struct {
26+ name string
27+ offset int
28+ limit int
29+ wantContent string
30+ wantHasMore bool
31+ }{
32+ {
33+ name: "exactly limit lines remaining",
34+ offset: 0,
35+ limit: 5,
36+ wantContent: "line 1\nline 2\nline 3\nline 4\nline 5",
37+ wantHasMore: false,
38+ },
39+ {
40+ name: "limit plus one line remaining",
41+ offset: 0,
42+ limit: 4,
43+ wantContent: "line 1\nline 2\nline 3\nline 4",
44+ wantHasMore: true,
45+ },
46+ {
47+ name: "offset at last line",
48+ offset: 4,
49+ limit: 3,
50+ wantContent: "line 5",
51+ wantHasMore: false,
52+ },
53+ {
54+ name: "offset beyond eof",
55+ offset: 10,
56+ limit: 3,
57+ wantContent: "",
58+ wantHasMore: false,
59+ },
60+ }
61+
62+ for _, tt := range tests {
63+ t.Run(tt.name, func(t *testing.T) {
64+ t.Parallel()
65+
66+ gotContent, gotHasMore, err := readTextFile(filePath, tt.offset, tt.limit)
67+ require.NoError(t, err)
68+ require.Equal(t, tt.wantContent, gotContent)
69+ require.Equal(t, tt.wantHasMore, gotHasMore)
70+ })
71+ }
72+}
73+
74+func TestReadTextFileTruncatesLongLines(t *testing.T) {
75+ t.Parallel()
76+
77+ tmpDir := t.TempDir()
78+ filePath := filepath.Join(tmpDir, "longline.txt")
79+
80+ longLine := strings.Repeat("a", MaxLineLength+10)
81+ require.NoError(t, os.WriteFile(filePath, []byte(longLine), 0o644))
82+
83+ content, hasMore, err := readTextFile(filePath, 0, 1)
84+ require.NoError(t, err)
85+ require.False(t, hasMore)
86+ require.Equal(t, strings.Repeat("a", MaxLineLength)+"...", content)
87+}