gofmt: format all remaining files with the go1.27 toolchain

The tree was formatted with an older gofmt; go1.27's gofmt additionally
wants: EOF exactly one newline (no trailing blank lines), imports sorted
alphabetically within a block, mixed-precedence binary expressions
re-spaced for grouping ((a+b)/c), single-field composite literals
un-aligned, adjacent one-line method signatures aligned, and one-line
bodies containing a compound statement expanded. Applied repo-wide
(31 files under internal/); pure formatting, no semantic changes —
build and the full test suite pass.
This commit is contained in:
Greg Pomerantz 2026-08-23 10:03:27 -04:00
parent 180fa966c8
commit 06b1444207
31 changed files with 279 additions and 247 deletions

View File

@ -66,7 +66,7 @@ func computeVisiblePageRange(s *BrowserState) (minPage, maxPage int) {
if minPage < 0 {
minPage = 0
}
maxPage = (s.GetScrollIndex() + s.VisibleCount)/PageSize + PrefetchDist
maxPage = (s.GetScrollIndex()+s.VisibleCount)/PageSize + PrefetchDist
return minPage, maxPage
}
@ -164,4 +164,3 @@ func filterEntriesByQuery(entries []Entry, query string) []int {
return results
}

View File

@ -28,7 +28,7 @@ func TestLoadPage_FromIndexAtOffset(t *testing.T) {
// Build position maps for sort modes
s.SortIndex = &DirectoryIndex{
Entries: allEntries,
Entries: allEntries,
SortOrders: map[string][]int{
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),
@ -76,7 +76,7 @@ func TestLoadPage_LastPagePartialFromIndex(t *testing.T) {
// Build position maps for sort modes
s.SortIndex = &DirectoryIndex{
Entries: allEntries,
Entries: allEntries,
SortOrders: map[string][]int{
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),
@ -116,7 +116,7 @@ func TestLoadPage_OutOfBoundsFromIndex(t *testing.T) {
// Build position maps for sort modes
s.SortIndex = &DirectoryIndex{
Entries: allEntries,
Entries: allEntries,
SortOrders: map[string][]int{
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),
@ -195,7 +195,7 @@ func TestGetEntryByIndex(t *testing.T) {
// Build position maps for sort modes
s.SortIndex = &DirectoryIndex{
Entries: allEntries,
Entries: allEntries,
SortOrders: map[string][]int{
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),
@ -236,7 +236,7 @@ func TestGetEntryByIndex_UnloadedPage(t *testing.T) {
// Build position maps for sort modes
s.SortIndex = &DirectoryIndex{
Entries: allEntries,
Entries: allEntries,
SortOrders: map[string][]int{
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),

View File

@ -13,11 +13,11 @@ import (
// DirectoryIndex holds a cached index of a directory's contents.
// Per design: index stores raw metadata; sorting is pre-computed via position maps.
type DirectoryIndex struct {
Path string `json:"path"`
Mtime time.Time `json:"mtime"`
EntryCount int `json:"entry_count"`
Entries []Entry `json:"entries"`
SortOrders map[string][]int `json:"sort_orders,omitempty"` // key: "name_asc", "name_desc", etc.
Path string `json:"path"`
Mtime time.Time `json:"mtime"`
EntryCount int `json:"entry_count"`
Entries []Entry `json:"entries"`
SortOrders map[string][]int `json:"sort_orders,omitempty"` // key: "name_asc", "name_desc", etc.
}
// getCachePath returns the path to the cached index file for a directory.

View File

@ -224,5 +224,3 @@ func TestBuildIndex_LargeDirectory(t *testing.T) {
t.Errorf("buildIndex took %v, expected < 5s", elapsed)
}
}

View File

@ -160,8 +160,6 @@ func recomputeSearchResults(s *BrowserState) {
}
}
// ComputeVisibleEntriesForTest is exported for testing purposes.
// It builds the list of ui.ListItem entries that should be rendered.
func ComputeVisibleEntriesForTest(state *BrowserState) []ui.ListItem {
@ -224,7 +222,7 @@ func computeSearchResults(state *BrowserState) []ui.ListItem {
var items []ui.ListItem
for idx := startIndex; idx < endIndex; idx++ {
rawIdx := state.SearchResults[idx]
entry, ok := getEntryByIndex(state, rawIdx)
if !ok {
// Page not loaded yet; add placeholder

View File

@ -32,7 +32,7 @@ func makeTestState(t *testing.T, entryCount int, scrollIndex int, visibleCount i
// Build position maps for sort modes
s.SortIndex = &DirectoryIndex{
Entries: entries,
Entries: entries,
SortOrders: map[string][]int{
"name_asc": buildPositionMap(entries, SortModeNameAsc),
"date_desc": buildPositionMap(entries, SortModeDateDesc),

View File

@ -67,7 +67,7 @@ func TestLazyLoadingLargeDirectory(t *testing.T) {
// 3. Scroll down to page 50
state.ScrollOffset = 50 * 100 * state.EntryHeight // Scroll to middle
bm.OnScroll()
// Wait for load pages result for scroll
res, ok = getResult(15 * time.Second)
if !ok {

View File

@ -145,7 +145,7 @@ func TestPixelScroll_EvictionTriggered(t *testing.T) {
entries[i] = NewEntry("/test/file.txt", "file.txt", 100, s.TapTimestamp, false)
}
s.SortIndex = &DirectoryIndex{
Entries: entries,
Entries: entries,
SortOrders: map[string][]int{
"name_asc": buildPositionMap(entries, SortModeNameAsc),
"date_desc": buildPositionMap(entries, SortModeDateDesc),
@ -184,7 +184,7 @@ func TestPixelScroll_PrefetchTriggered(t *testing.T) {
entries[i] = NewEntry("/test/file.txt", "file.txt", 100, s.TapTimestamp, false)
}
s.SortIndex = &DirectoryIndex{
Entries: entries,
Entries: entries,
SortOrders: map[string][]int{
"name_asc": buildPositionMap(entries, SortModeNameAsc),
"date_desc": buildPositionMap(entries, SortModeDateDesc),

View File

@ -83,7 +83,7 @@ func TestScroll_PrefetchTriggered(t *testing.T) {
}
// Build position maps for sort modes
s.SortIndex = &DirectoryIndex{
Entries: entries,
Entries: entries,
SortOrders: map[string][]int{
"name_asc": buildPositionMap(entries, SortModeNameAsc),
"date_desc": buildPositionMap(entries, SortModeDateDesc),
@ -131,7 +131,7 @@ func TestScroll_EvictionTriggered(t *testing.T) {
}
// Build position maps for sort modes
s.SortIndex = &DirectoryIndex{
Entries: entries,
Entries: entries,
SortOrders: map[string][]int{
"name_asc": buildPositionMap(entries, SortModeNameAsc),
"date_desc": buildPositionMap(entries, SortModeDateDesc),
@ -246,7 +246,7 @@ func TestScroll_PrefetchTriggeredOnPageBoundary(t *testing.T) {
}
// Build position maps for sort modes
s.SortIndex = &DirectoryIndex{
Entries: entries,
Entries: entries,
SortOrders: map[string][]int{
"name_asc": buildPositionMap(entries, SortModeNameAsc),
"date_desc": buildPositionMap(entries, SortModeDateDesc),

View File

@ -171,7 +171,7 @@ func TestSearch_JumpToFirst(t *testing.T) {
if len(s.SearchResults) == 0 {
t.Fatalf("expected results for 'file_3', got 0")
}
// The first result should be index 3 (file_3).
// With 12 results, it fits in 20 visible. ScrollIndex should be 0.
if s.GetScrollIndex() != 0 {

View File

@ -69,4 +69,3 @@ func comparator(mode SortMode) func(a, b Entry) int {
return func(a, b Entry) int { return 0 }
}
}

View File

@ -19,20 +19,20 @@ type Entry struct {
// Page represents a chunk of directory entries loaded from disk.
type Page struct {
Index int // Page number (0-based)
Entries []Entry // Entries in this page
Loaded bool // True if page data is in memory
Dirty bool // True if page needs refresh (external change detected)
Index int // Page number (0-based)
Entries []Entry // Entries in this page
Loaded bool // True if page data is in memory
Dirty bool // True if page needs refresh (external change detected)
}
// BrowserState holds all mutable browser state owned by the logic goroutine.
type BrowserState struct {
// Navigation
CurrentPath string // Currently browsed directory (relative to root)
History []string // Path history for navigation
ScrollOffset float64 // Vertical scroll offset in pixels (per-pixel scrolling)
EntryHeight float64 // Height of a single entry in pixels
VisibleCount int // Number of entries currently visible
CurrentPath string // Currently browsed directory (relative to root)
History []string // Path history for navigation
ScrollOffset float64 // Vertical scroll offset in pixels (per-pixel scrolling)
EntryHeight float64 // Height of a single entry in pixels
VisibleCount int // Number of entries currently visible
// Lazy loading
Pages map[int]*Page // Loaded pages by page index
@ -44,8 +44,8 @@ type BrowserState struct {
SortMode SortMode // Current sort mode
// Search
Query string // Current search query (forwarded from the main-owned search widget)
SearchResults []int // Indices of matching entries (empty = no filter)
Query string // Current search query (forwarded from the main-owned search widget)
SearchResults []int // Indices of matching entries (empty = no filter)
// NOTE: the search bar's widget.Editor is intentionally NOT part of this
// state: Gio mutates widget state on the main goroutine during draw, and
// this state is owned by the logic goroutine (architecture.md §1). The
@ -62,8 +62,8 @@ type BrowserState struct {
}
const (
PageSize = 100 // Entries per page (tunable; ~5KB per page in memory)
PrefetchDist = 2 // Pages to prefetch beyond visible region
PageSize = 100 // Entries per page (tunable; ~5KB per page in memory)
PrefetchDist = 2 // Pages to prefetch beyond visible region
)
// NewEntry creates a new Entry from the given parameters.
@ -127,10 +127,10 @@ func formatSize(bytes int64) string {
// EvictPages removes pages that are far from the current scroll position.
func (s *BrowserState) EvictPages() {
minPage := s.GetScrollIndex()/PageSize - PrefetchDist
maxPage := (s.GetScrollIndex() + s.VisibleCount)/PageSize + PrefetchDist
maxPage := (s.GetScrollIndex()+s.VisibleCount)/PageSize + PrefetchDist
for idx, page := range s.Pages {
if idx < minPage || idx > maxPage {
page.Entries = nil // Release memory
page.Entries = nil // Release memory
page.Loaded = false
}
}

View File

@ -103,7 +103,7 @@ func TestFormatSize(t *testing.T) {
{0, "0 B"},
{500, "500 B"},
{1024, "1.0 KB"},
{1024*1024, "1.0 MB"},
{1024 * 1024, "1.0 MB"},
{1024 * 1024 * 1024, "1.0 GB"},
}
@ -118,7 +118,7 @@ func TestFormatSize(t *testing.T) {
func TestBrowserStateEvictPages(t *testing.T) {
s := NewBrowserState()
s.EntryHeight = 48.0
s.ScrollOffset = 500 * 48 // Page 5
s.ScrollOffset = 500 * 48 // Page 5
s.VisibleCount = 10
// With ScrollIndex=500, PageSize=100:
@ -127,10 +127,10 @@ func TestBrowserStateEvictPages(t *testing.T) {
// Pages 3-7 should be kept, others evicted
// Add pages at various positions
s.Pages[0] = NewPage(0, nil) // Should be evicted (far)
s.Pages[3] = NewPage(3, nil) // Should be kept (at min boundary)
s.Pages[5] = NewPage(5, nil) // Should be kept (visible)
s.Pages[7] = NewPage(7, nil) // Should be kept (at max boundary)
s.Pages[0] = NewPage(0, nil) // Should be evicted (far)
s.Pages[3] = NewPage(3, nil) // Should be kept (at min boundary)
s.Pages[5] = NewPage(5, nil) // Should be kept (visible)
s.Pages[7] = NewPage(7, nil) // Should be kept (at max boundary)
s.Pages[10] = NewPage(10, nil) // Should be evicted (far)
s.EvictPages()

View File

@ -14,10 +14,10 @@ func TestWriteFailureTracking(t *testing.T) {
state := NewState()
filename := "test.txt"
state.Editor.Filename = filename
// Simulate a write failure
state.Editor.SetWriteFailed(filename, true)
if !state.Editor.WriteFailed() {
t.Errorf("Expected WriteFailed() to be true")
}
@ -34,7 +34,7 @@ func TestWriteFailureTracking(t *testing.T) {
func TestAutoSave_RetryFails(t *testing.T) {
// Setup: New Logic, set mock FS to fail
l := NewLogic(nil, "/", func(string) {})
// Start a goroutine to drain frameChan to prevent deadlocks
go func() {
for range l.frameChan {
@ -50,16 +50,16 @@ func TestAutoSave_RetryFails(t *testing.T) {
if !ok {
t.Fatal("mockFS is not a *mock.FileSystem")
}
mfs.SetWriteError(true)
mfs.SetWriteError(true)
// 1. Manually dispatch a write task
task := pool.NewWriteFileTask(filename, []byte("hello"), l.mockFS)
l.workerPool.Dispatch(task)
// 2. Manually process the result to trigger the failure state
res := <-l.workerPool.ResultChan()
l.handleWorkerResult(res)
// 3. Assert failure
if !l.state.Editor.WriteFailed() {
t.Errorf("Expected WriteFailed() to be true")

View File

@ -25,7 +25,7 @@ func TestBackspace(t *testing.T) {
state := NewState()
TheState = state
state.Editor.Buffer = "Hello"
state.Editor.CursorPosition = 5
state.Editor.CursorPosition = 5
HandleBackspace() // Assuming we create this function

View File

@ -189,9 +189,9 @@ func TestLargeFileChunkBoundary(t *testing.T) {
// (The old fixed-slot model treated each chunk as an independent buffer and
// did NOT shift later chunks; that was the bug this design replaces.)
expectedStr := string(initialContent)
expectedStr = expectedStr[:65000] + "CHUNK1" + expectedStr[65000:] // insert 1 @65000
expectedStr = expectedStr[:65535] + "BOUNDARY" + expectedStr[65535:] // insert 2 @65535
expectedStr = expectedStr[:262000] + "CHUNK3" + expectedStr[262000:] // insert 3 @262000
expectedStr = expectedStr[:65000] + "CHUNK1" + expectedStr[65000:] // insert 1 @65000
expectedStr = expectedStr[:65535] + "BOUNDARY" + expectedStr[65535:] // insert 2 @65535
expectedStr = expectedStr[:262000] + "CHUNK3" + expectedStr[262000:] // insert 3 @262000
expected := []byte(expectedStr)
if len(savedContent) != len(expected) {

View File

@ -1,26 +1,26 @@
package editor
import (
"testing"
"pad/internal/ui"
"testing"
)
func TestEditorLayout_Filename(t *testing.T) {
// Setup: Reset state
TheState = NewState()
// Set a filename
expectedFilename := "test.txt"
TheState.Editor.Filename = expectedFilename
// Run layout
screenWidth := ui.Dp(400)
screenHeight := ui.Dp(800)
elements := EditorLayout(screenWidth, screenHeight, false)
// Find top bar (assuming it's the first container element)
topBar := elements[0].(ui.Container)
// Find the filename label in the top bar (the back icon precedes it).
var filenameLabel ui.Label
for _, c := range topBar.Children {
@ -37,18 +37,18 @@ func TestEditorLayout_Filename(t *testing.T) {
func TestEditorLayout_DefaultFilename(t *testing.T) {
// Setup: Reset state
TheState = NewState()
// Filename is empty
TheState.Editor.Filename = ""
// Run layout
screenWidth := ui.Dp(400)
screenHeight := ui.Dp(800)
elements := EditorLayout(screenWidth, screenHeight, false)
// Find top bar (assuming it's the first container element)
topBar := elements[0].(ui.Container)
// Find the filename label in the top bar (the back icon precedes it).
var filenameLabel ui.Label
for _, c := range topBar.Children {

View File

@ -40,8 +40,8 @@ func newChunkedState(t *testing.T, content string, chunkSize int) *State {
func TestRuneIndexToByteStr(t *testing.T) {
cases := []struct {
s string
n int
s string
n int
want int
}{
{"", 0, 0},

View File

@ -20,45 +20,45 @@ func absFloat(x ui.Dp) float64 {
func TestFragmentStartYCalculation(t *testing.T) {
// Setup test scenario
lineHeight := ui.Dp(16.8) // 14 * 1.2
// Test case 1: Scroll to line 0 (top)
scrollOffset := ui.Dp(0)
expectedVisualLine := 0
expectedFragmentStartY := ui.Dp(0)
visualLine := int(scrollOffset / lineHeight)
fragmentStartY := ui.Dp(visualLine) * lineHeight
if visualLine != expectedVisualLine {
t.Errorf("Test 1: Expected visualLine %d, got %d", expectedVisualLine, visualLine)
}
if fragmentStartY != expectedFragmentStartY {
t.Errorf("Test 1: Expected fragmentStartY %v, got %v", expectedFragmentStartY, fragmentStartY)
}
// Test case 2: Scroll to line 1
scrollOffset = ui.Dp(16.8)
expectedVisualLine = 1
expectedFragmentStartY = ui.Dp(16.8)
visualLine = int(scrollOffset / lineHeight)
fragmentStartY = ui.Dp(visualLine) * lineHeight
if visualLine != expectedVisualLine {
t.Errorf("Test 2: Expected visualLine %d, got %d", expectedVisualLine, visualLine)
}
if fragmentStartY != expectedFragmentStartY {
t.Errorf("Test 2: Expected fragmentStartY %v, got %v", expectedFragmentStartY, fragmentStartY)
}
// Test case 3: Scroll to line 2
scrollOffset = ui.Dp(33.6)
expectedVisualLine = 2
expectedFragmentStartY = ui.Dp(33.6)
visualLine = int(scrollOffset / lineHeight)
fragmentStartY = ui.Dp(visualLine) * lineHeight
if visualLine != expectedVisualLine {
t.Errorf("Test 3: Expected visualLine %d, got %d", expectedVisualLine, visualLine)
}
@ -77,24 +77,24 @@ func TestVisualLineIndexCreation(t *testing.T) {
Advance: []ui.Dp{10, 10, 10, 10},
LineHeight: ui.Dp(16.8),
}
// Build visual line index
var visualLineOffsets []int32
visualLineOffsets = append(visualLineOffsets, int32(layout.ByteOffsets[0]))
for i := 1; i < len(layout.Y); i++ {
if layout.Y[i] != layout.Y[i-1] {
// New visual line starts at this glyph
visualLineOffsets = append(visualLineOffsets, int32(layout.ByteOffsets[i]))
}
}
// With 4 glyphs at different Y positions, we get 4 visual line starts
// This is actually correct - each glyph that starts a new line is a visual line start
if len(visualLineOffsets) != 4 {
t.Errorf("Expected 4 visual line starts, got %d", len(visualLineOffsets))
}
// Check byte offsets - should match all the byte offsets where Y changes
expectedOffsets := []int32{0, 7, 14, 21}
for i := 0; i < len(visualLineOffsets) && i < len(expectedOffsets); i++ {
@ -107,14 +107,14 @@ func TestVisualLineIndexCreation(t *testing.T) {
// TestWordWrapFragmentStartY tests fragmentStartY calculation with word wrap enabled
func TestWordWrapFragmentStartY(t *testing.T) {
lineHeight := ui.Dp(16.8) // 14 * 1.2
// Test case: Word wrap enabled, scroll to visual line 5
// With word wrap, visual lines don't correspond 1:1 with logical lines
wordWrap := true
_ = wordWrap // Mark as used for this test
_ = wordWrap // Mark as used for this test
scrollOffset := ui.Dp(5 * float64(lineHeight)) // Scroll to visual line 5
visualLine := int(scrollOffset / lineHeight)
// With word wrap, we should use a different estimation
// because one logical line can span multiple visual lines
if wordWrap {
@ -122,7 +122,7 @@ func TestWordWrapFragmentStartY(t *testing.T) {
// to avoid skipping wrapped lines
estimatedBytesPerVisualLine := 10 // Very small step
expectedStartByteOffset := visualLine * estimatedBytesPerVisualLine
if expectedStartByteOffset != 50 { // 5 * 10
t.Errorf("Word wrap case: expected start byte offset %d, got %d", 50, expectedStartByteOffset)
}
@ -130,12 +130,12 @@ func TestWordWrapFragmentStartY(t *testing.T) {
// Without word wrap, use bytes per logical line estimate
estimatedBytesPerLogicalLine := 50
expectedStartByteOffset := visualLine * estimatedBytesPerLogicalLine
if expectedStartByteOffset != 250 { // 5 * 50
t.Errorf("No word wrap case: expected start byte offset %d, got %d", 250, expectedStartByteOffset)
}
}
// fragmentStartY should always be visualLine * lineHeight
expectedFragmentStartY := ui.Dp(visualLine) * lineHeight
if expectedFragmentStartY != ui.Dp(5*16.8) {
@ -150,31 +150,31 @@ func TestByteOffsetAndYFromScroll(t *testing.T) {
visualLineIndex := &types.VisualLineIndex{
Offsets: []int32{0, 7, 14, 21}, // 4 lines
}
layout := ui.GlyphLayout{
ByteOffsets: []int{0, 7, 14, 21},
X: []ui.Dp{0, 0, 0, 0},
Y: []ui.Dp{0, 16.8, 33.6, 50.4},
Advance: []ui.Dp{10, 10, 10, 10},
LineHeight: lineHeight,
ByteOffsets: []int{0, 7, 14, 21},
X: []ui.Dp{0, 0, 0, 0},
Y: []ui.Dp{0, 16.8, 33.6, 50.4},
Advance: []ui.Dp{10, 10, 10, 10},
LineHeight: lineHeight,
VisualLineIndex: visualLineIndex,
}
// Test scrolling to different positions
testCases := []struct {
scrollOffset ui.Dp
expectedByte int
expectedY ui.Dp
scrollOffset ui.Dp
expectedByte int
expectedY ui.Dp
}{
{ui.Dp(0), 0, ui.Dp(0)}, // Top of document
{ui.Dp(16.8), 7, ui.Dp(16.8)}, // Start of line 1
{ui.Dp(33.6), 14, ui.Dp(33.6)}, // Start of line 2
{ui.Dp(50.4), 21, ui.Dp(50.4)}, // Start of line 3
{ui.Dp(0), 0, ui.Dp(0)}, // Top of document
{ui.Dp(16.8), 7, ui.Dp(16.8)}, // Start of line 1
{ui.Dp(33.6), 14, ui.Dp(33.6)}, // Start of line 2
{ui.Dp(50.4), 21, ui.Dp(50.4)}, // Start of line 3
}
for _, tc := range testCases {
byteOffset, y := ByteOffsetAndYFromScrollWithLayout(tc.scrollOffset, layout)
if byteOffset != tc.expectedByte {
t.Errorf("Scroll %v: expected byte offset %d, got %d", tc.scrollOffset, tc.expectedByte, byteOffset)
}
@ -191,17 +191,17 @@ func ByteOffsetAndYFromScrollWithLayout(scrollOffset ui.Dp, layout ui.GlyphLayou
if lineHeight == 0 {
lineHeight = editor.EditorLineHeight()
}
// Calculate which visual line should be at the given scroll offset
visualLine := int(scrollOffset / lineHeight)
// If we have a visual line index, use it for accurate byte offset
if layout.VisualLineIndex != nil && visualLine < len(layout.VisualLineIndex.Offsets) {
byteOffset := int(layout.VisualLineIndex.Offsets[visualLine])
lineTop := ui.Dp(visualLine) * lineHeight
return byteOffset, lineTop
}
// Fallback to the original logic using layout.Y values
// 1. Find the index of the line whose top is <= scrollOffset
idx := sort.Search(len(layout.Y), func(i int) bool {
@ -226,4 +226,4 @@ func ByteOffsetAndYFromScrollWithLayout(scrollOffset ui.Dp, layout ui.GlyphLayou
lineTop := layout.Y[lineStartIdx] - lineHeight
return layout.ByteOffsets[lineStartIdx], lineTop
}
}

View File

@ -202,4 +202,3 @@ func byteOffsetOfLine(content string, i int) int {
}
return o
}

View File

@ -53,13 +53,13 @@ func (t *cancelTask) Execute() Result {
}
}
func (t *cancelTask) Priority() Priority { return t.priority }
func (t *cancelTask) TaskID() string { return t.id }
func (t *cancelTask) TaskType() TaskType { return t.taskType }
func (t *cancelTask) DirPath() string { return "/test" }
func (t *cancelTask) Priority() Priority { return t.priority }
func (t *cancelTask) TaskID() string { return t.id }
func (t *cancelTask) TaskType() TaskType { return t.taskType }
func (t *cancelTask) DirPath() string { return "/test" }
func (t *cancelTask) Context() context.Context { return t.ctx }
func (t *cancelTask) Cancel() { t.cancel() }
func (t *cancelTask) Timeout() time.Duration { return 0 }
func (t *cancelTask) Cancel() { t.cancel() }
func (t *cancelTask) Timeout() time.Duration { return 0 }
// timeoutTask is a task that respects timeout.
type timeoutTask struct {
@ -79,13 +79,13 @@ func (t *timeoutTask) Execute() Result {
}
}
func (t *timeoutTask) Priority() Priority { return t.priority }
func (t *timeoutTask) TaskID() string { return t.id }
func (t *timeoutTask) TaskType() TaskType { return t.taskType }
func (t *timeoutTask) DirPath() string { return "/test" }
func (t *timeoutTask) Priority() Priority { return t.priority }
func (t *timeoutTask) TaskID() string { return t.id }
func (t *timeoutTask) TaskType() TaskType { return t.taskType }
func (t *timeoutTask) DirPath() string { return "/test" }
func (t *timeoutTask) Context() context.Context { return context.Background() }
func (t *timeoutTask) Cancel() {}
func (t *timeoutTask) Timeout() time.Duration { return 50 * time.Millisecond }
func (t *timeoutTask) Cancel() {}
func (t *timeoutTask) Timeout() time.Duration { return 50 * time.Millisecond }
func TestTaskContextCancellation(t *testing.T) {
task := newCancelTask("test-cancel", 1*time.Second)

View File

@ -68,10 +68,10 @@ type notifyEvent struct {
// FileSystem is a thread-safe in-memory filesystem for testing.
type FileSystem struct {
mu sync.RWMutex
files map[string]*File // path -> file
delay time.Duration // uniform delay applied to all operations
changes chan FileChangeEvent // async change notifications (nil = no notifications)
writeError bool // Added to simulate write errors
files map[string]*File // path -> file
delay time.Duration // uniform delay applied to all operations
changes chan FileChangeEvent // async change notifications (nil = no notifications)
writeError bool // Added to simulate write errors
}
// NewFileSystem creates an empty mock filesystem.

View File

@ -115,8 +115,6 @@ func (t TaskType) String() string {
}
}
// --- Specific Task Implementations ---
// ReadChunkTask reads a specific chunk of a file.
@ -163,13 +161,17 @@ func (t *ReadChunkTask) Execute() Result {
}
}
func (t *ReadChunkTask) Priority() Priority { return HighPriority }
func (t *ReadChunkTask) TaskType() TaskType { return TypeReadChunk }
func (t *ReadChunkTask) TaskID() string { return t.taskID }
func (t *ReadChunkTask) DirPath() string { return filepath.Dir(t.Path) }
func (t *ReadChunkTask) Priority() Priority { return HighPriority }
func (t *ReadChunkTask) TaskType() TaskType { return TypeReadChunk }
func (t *ReadChunkTask) TaskID() string { return t.taskID }
func (t *ReadChunkTask) DirPath() string { return filepath.Dir(t.Path) }
func (t *ReadChunkTask) Context() context.Context { return t.ctx }
func (t *ReadChunkTask) Timeout() time.Duration { return 5 * time.Second }
func (t *ReadChunkTask) Cancel() { if t.cancel != nil { t.cancel() } }
func (t *ReadChunkTask) Timeout() time.Duration { return 5 * time.Second }
func (t *ReadChunkTask) Cancel() {
if t.cancel != nil {
t.cancel()
}
}
// StatFileTask retrieves file metadata (like size and modification time).
// The plan indicates this replaces the full-file ReadFileTask for opening.
@ -210,13 +212,17 @@ func (t *StatFileTask) Execute() Result {
}
}
func (t *StatFileTask) Priority() Priority { return MediumPriority }
func (t *StatFileTask) TaskType() TaskType { return TypeStatFile }
func (t *StatFileTask) TaskID() string { return t.taskID }
func (t *StatFileTask) DirPath() string { return filepath.Dir(t.Path) }
func (t *StatFileTask) Priority() Priority { return MediumPriority }
func (t *StatFileTask) TaskType() TaskType { return TypeStatFile }
func (t *StatFileTask) TaskID() string { return t.taskID }
func (t *StatFileTask) DirPath() string { return filepath.Dir(t.Path) }
func (t *StatFileTask) Context() context.Context { return t.ctx }
func (t *StatFileTask) Timeout() time.Duration { return 5 * time.Second }
func (t *StatFileTask) Cancel() { if t.cancel != nil { t.cancel() } }
func (t *StatFileTask) Timeout() time.Duration { return 5 * time.Second }
func (t *StatFileTask) Cancel() {
if t.cancel != nil {
t.cancel()
}
}
// FileStat holds file metadata.
type FileStat struct {
@ -267,13 +273,17 @@ func (t *BuildLineIndexTask) Execute() Result {
}
}
func (t *BuildLineIndexTask) Priority() Priority { return LowPriority }
func (t *BuildLineIndexTask) TaskType() TaskType { return TypeBuildLineIndex }
func (t *BuildLineIndexTask) TaskID() string { return t.taskID }
func (t *BuildLineIndexTask) DirPath() string { return filepath.Dir(t.Path) }
func (t *BuildLineIndexTask) Priority() Priority { return LowPriority }
func (t *BuildLineIndexTask) TaskType() TaskType { return TypeBuildLineIndex }
func (t *BuildLineIndexTask) TaskID() string { return t.taskID }
func (t *BuildLineIndexTask) DirPath() string { return filepath.Dir(t.Path) }
func (t *BuildLineIndexTask) Context() context.Context { return t.ctx }
func (t *BuildLineIndexTask) Timeout() time.Duration { return 30 * time.Second }
func (t *BuildLineIndexTask) Cancel() { if t.cancel != nil { t.cancel() } }
func (t *BuildLineIndexTask) Timeout() time.Duration { return 30 * time.Second }
func (t *BuildLineIndexTask) Cancel() {
if t.cancel != nil {
t.cancel()
}
}
// BuildIndexTask builds the directory index for the browser.
type BuildIndexTask struct {
@ -304,13 +314,17 @@ func (t *BuildIndexTask) Execute() Result {
return Result{TaskID: t.taskID, TaskType: TypeBuildIndex, Success: true, Data: entries}
}
func (t *BuildIndexTask) Priority() Priority { return HighPriority }
func (t *BuildIndexTask) TaskType() TaskType { return TypeBuildIndex }
func (t *BuildIndexTask) TaskID() string { return t.taskID }
func (t *BuildIndexTask) DirPath() string { return t.Dir }
func (t *BuildIndexTask) Priority() Priority { return HighPriority }
func (t *BuildIndexTask) TaskType() TaskType { return TypeBuildIndex }
func (t *BuildIndexTask) TaskID() string { return t.taskID }
func (t *BuildIndexTask) DirPath() string { return t.Dir }
func (t *BuildIndexTask) Context() context.Context { return t.ctx }
func (t *BuildIndexTask) Timeout() time.Duration { return 5 * time.Second }
func (t *BuildIndexTask) Cancel() { if t.cancel != nil { t.cancel() } }
func (t *BuildIndexTask) Timeout() time.Duration { return 5 * time.Second }
func (t *BuildIndexTask) Cancel() {
if t.cancel != nil {
t.cancel()
}
}
// LoadPagesTask loads specific pages from a directory index.
type LoadPagesTask struct {
@ -343,13 +357,17 @@ func (t *LoadPagesTask) Execute() Result {
return Result{TaskID: t.taskID, TaskType: TypeLoadPages, Success: true, Data: t.PageIdxs}
}
func (t *LoadPagesTask) Priority() Priority { return HighPriority }
func (t *LoadPagesTask) TaskType() TaskType { return TypeLoadPages }
func (t *LoadPagesTask) TaskID() string { return t.taskID }
func (t *LoadPagesTask) DirPath() string { return t.Dir }
func (t *LoadPagesTask) Priority() Priority { return HighPriority }
func (t *LoadPagesTask) TaskType() TaskType { return TypeLoadPages }
func (t *LoadPagesTask) TaskID() string { return t.taskID }
func (t *LoadPagesTask) DirPath() string { return t.Dir }
func (t *LoadPagesTask) Context() context.Context { return t.ctx }
func (t *LoadPagesTask) Timeout() time.Duration { return 5 * time.Second }
func (t *LoadPagesTask) Cancel() { if t.cancel != nil { t.cancel() } }
func (t *LoadPagesTask) Timeout() time.Duration { return 5 * time.Second }
func (t *LoadPagesTask) Cancel() {
if t.cancel != nil {
t.cancel()
}
}
// ReadDirTask reads directory entries.
type ReadDirTask struct {
@ -381,13 +399,17 @@ func (t *ReadDirTask) Execute() Result {
return Result{TaskID: t.taskID, TaskType: TypeReadDir, Success: true, Data: entries}
}
func (t *ReadDirTask) Priority() Priority { return HighPriority }
func (t *ReadDirTask) TaskType() TaskType { return TypeReadDir }
func (t *ReadDirTask) TaskID() string { return t.taskID }
func (t *ReadDirTask) DirPath() string { return t.Dir }
func (t *ReadDirTask) Priority() Priority { return HighPriority }
func (t *ReadDirTask) TaskType() TaskType { return TypeReadDir }
func (t *ReadDirTask) TaskID() string { return t.taskID }
func (t *ReadDirTask) DirPath() string { return t.Dir }
func (t *ReadDirTask) Context() context.Context { return t.ctx }
func (t *ReadDirTask) Timeout() time.Duration { return 5 * time.Second }
func (t *ReadDirTask) Cancel() { if t.cancel != nil { t.cancel() } }
func (t *ReadDirTask) Timeout() time.Duration { return 5 * time.Second }
func (t *ReadDirTask) Cancel() {
if t.cancel != nil {
t.cancel()
}
}
// SaveStateTask persists application state.
type SaveStateTask struct {
@ -421,13 +443,17 @@ func (t *SaveStateTask) Execute() Result {
return Result{TaskID: t.taskID, TaskType: TypeSaveState, Success: true}
}
func (t *SaveStateTask) Priority() Priority { return LowPriority }
func (t *SaveStateTask) TaskType() TaskType { return TypeSaveState }
func (t *SaveStateTask) TaskID() string { return t.taskID }
func (t *SaveStateTask) DirPath() string { return filepath.Dir(t.Path) }
func (t *SaveStateTask) Priority() Priority { return LowPriority }
func (t *SaveStateTask) TaskType() TaskType { return TypeSaveState }
func (t *SaveStateTask) TaskID() string { return t.taskID }
func (t *SaveStateTask) DirPath() string { return filepath.Dir(t.Path) }
func (t *SaveStateTask) Context() context.Context { return t.ctx }
func (t *SaveStateTask) Timeout() time.Duration { return 5 * time.Second }
func (t *SaveStateTask) Cancel() { if t.cancel != nil { t.cancel() } }
func (t *SaveStateTask) Timeout() time.Duration { return 5 * time.Second }
func (t *SaveStateTask) Cancel() {
if t.cancel != nil {
t.cancel()
}
}
// SaveUndoTask persists undo stack.
type SaveUndoTask struct {
@ -461,13 +487,17 @@ func (t *SaveUndoTask) Execute() Result {
return Result{TaskID: t.taskID, TaskType: TypeSaveUndo, Success: true}
}
func (t *SaveUndoTask) Priority() Priority { return LowPriority }
func (t *SaveUndoTask) TaskType() TaskType { return TypeSaveUndo }
func (t *SaveUndoTask) TaskID() string { return t.taskID }
func (t *SaveUndoTask) DirPath() string { return filepath.Dir(t.Path) }
func (t *SaveUndoTask) Priority() Priority { return LowPriority }
func (t *SaveUndoTask) TaskType() TaskType { return TypeSaveUndo }
func (t *SaveUndoTask) TaskID() string { return t.taskID }
func (t *SaveUndoTask) DirPath() string { return filepath.Dir(t.Path) }
func (t *SaveUndoTask) Context() context.Context { return t.ctx }
func (t *SaveUndoTask) Timeout() time.Duration { return 5 * time.Second }
func (t *SaveUndoTask) Cancel() { if t.cancel != nil { t.cancel() } }
func (t *SaveUndoTask) Timeout() time.Duration { return 5 * time.Second }
func (t *SaveUndoTask) Cancel() {
if t.cancel != nil {
t.cancel()
}
}
// ReadFileTask reads the full content of a file.
// Used as a fallback for small files or initial load before chunking is set up.
@ -499,13 +529,17 @@ func (t *ReadFileTask) Execute() Result {
return Result{TaskID: t.taskID, TaskType: TypeReadFile, FilePath: t.Path, Success: true, Data: content}
}
func (t *ReadFileTask) Priority() Priority { return HighPriority }
func (t *ReadFileTask) TaskType() TaskType { return TypeReadFile }
func (t *ReadFileTask) TaskID() string { return t.taskID }
func (t *ReadFileTask) DirPath() string { return filepath.Dir(t.Path) }
func (t *ReadFileTask) Priority() Priority { return HighPriority }
func (t *ReadFileTask) TaskType() TaskType { return TypeReadFile }
func (t *ReadFileTask) TaskID() string { return t.taskID }
func (t *ReadFileTask) DirPath() string { return filepath.Dir(t.Path) }
func (t *ReadFileTask) Context() context.Context { return t.ctx }
func (t *ReadFileTask) Timeout() time.Duration { return 5 * time.Second }
func (t *ReadFileTask) Cancel() { if t.cancel != nil { t.cancel() } }
func (t *ReadFileTask) Timeout() time.Duration { return 5 * time.Second }
func (t *ReadFileTask) Cancel() {
if t.cancel != nil {
t.cancel()
}
}
// SearchData is the payload of a completed SearchTask: the byte ranges
// [start, end) of every match of Query in the scanned Text, ascending and
@ -587,13 +621,17 @@ func toLowerFold(s string) string {
// unit; it is the standard library's two-way search.
func indexOf(s, sub string) int { return strings.Index(s, sub) }
func (t *SearchTask) Priority() Priority { return MediumPriority }
func (t *SearchTask) TaskType() TaskType { return TypeSearch }
func (t *SearchTask) TaskID() string { return t.taskID }
func (t *SearchTask) DirPath() string { return "" }
func (t *SearchTask) Context() context.Context { return t.ctx }
func (t *SearchTask) Timeout() time.Duration { return 10 * time.Second }
func (t *SearchTask) Cancel() { if t.cancel != nil { t.cancel() } }
func (t *SearchTask) Priority() Priority { return MediumPriority }
func (t *SearchTask) TaskType() TaskType { return TypeSearch }
func (t *SearchTask) TaskID() string { return t.taskID }
func (t *SearchTask) DirPath() string { return "" }
func (t *SearchTask) Context() context.Context { return t.ctx }
func (t *SearchTask) Timeout() time.Duration { return 10 * time.Second }
func (t *SearchTask) Cancel() {
if t.cancel != nil {
t.cancel()
}
}
// WriteFileTask writes content to a file.
type WriteFileTask struct {
@ -626,13 +664,17 @@ func (t *WriteFileTask) Execute() Result {
return Result{TaskID: t.taskID, TaskType: TypeWriteFile, FilePath: t.Path, Success: true}
}
func (t *WriteFileTask) Priority() Priority { return LowPriority }
func (t *WriteFileTask) TaskType() TaskType { return TypeWriteFile }
func (t *WriteFileTask) TaskID() string { return t.taskID }
func (t *WriteFileTask) DirPath() string { return filepath.Dir(t.Path) }
func (t *WriteFileTask) Priority() Priority { return LowPriority }
func (t *WriteFileTask) TaskType() TaskType { return TypeWriteFile }
func (t *WriteFileTask) TaskID() string { return t.taskID }
func (t *WriteFileTask) DirPath() string { return filepath.Dir(t.Path) }
func (t *WriteFileTask) Context() context.Context { return t.ctx }
func (t *WriteFileTask) Timeout() time.Duration { return 5 * time.Second }
func (t *WriteFileTask) Cancel() { if t.cancel != nil { t.cancel() } }
func (t *WriteFileTask) Timeout() time.Duration { return 5 * time.Second }
func (t *WriteFileTask) Cancel() {
if t.cancel != nil {
t.cancel()
}
}
// --- Mock File System (for testing/development) ---
// MockFS implements the pool.FileSystem interface for testing.
@ -709,12 +751,12 @@ type mockFileInfo struct {
modTime time.Time
}
func (f *mockFileInfo) Name() string { return f.name }
func (f *mockFileInfo) Size() int64 { return f.size }
func (f *mockFileInfo) Mode() os.FileMode { return os.FileMode(f.mode) }
func (f *mockFileInfo) ModTime() time.Time { return f.modTime }
func (f *mockFileInfo) IsDir() bool { return false }
func (f *mockFileInfo) Sys() any { return nil }
func (f *mockFileInfo) Name() string { return f.name }
func (f *mockFileInfo) Size() int64 { return f.size }
func (f *mockFileInfo) Mode() os.FileMode { return os.FileMode(f.mode) }
func (f *mockFileInfo) ModTime() time.Time { return f.modTime }
func (f *mockFileInfo) IsDir() bool { return false }
func (f *mockFileInfo) Sys() any { return nil }
// mockDirEntry implements types.DirEntry for MockFS ReadDir.
type mockDirEntry struct {
@ -722,8 +764,8 @@ type mockDirEntry struct {
isDir bool
}
func (e *mockDirEntry) Name() string { return e.name }
func (e *mockDirEntry) IsDir() bool { return e.isDir }
func (e *mockDirEntry) Name() string { return e.name }
func (e *mockDirEntry) IsDir() bool { return e.isDir }
func (e *mockDirEntry) Info() (os.FileInfo, error) {
return &mockFileInfo{name: e.name, size: 0}, nil
}
@ -739,6 +781,3 @@ func (m *MockFS) ReadDir(path string) ([]types.DirEntry, error) {
}
var _ FileSystem = (*MockFS)(nil) // Compile-time interface check

View File

@ -68,25 +68,25 @@ func (li *LineIndex) FindLogicalLineForByteOffset(byteOffset int) int {
if len(li.Offsets) == 0 {
return -1
}
// Binary search to find the line that contains this byte offset
low, high := 0, len(li.Offsets)-1
for low <= high {
mid := (low + high) / 2
midOffset := int(li.Offsets[mid])
if byteOffset >= midOffset {
// This line starts at or before our byte offset
// Check if the next line starts after our byte offset
if mid == len(li.Offsets)-1 || int(li.Offsets[mid+1]) > byteOffset {
return mid // Found the line
}
low = mid + 1
} else {
high = mid - 1
midOffset := int(li.Offsets[mid])
if byteOffset >= midOffset {
// This line starts at or before our byte offset
// Check if the next line starts after our byte offset
if mid == len(li.Offsets)-1 || int(li.Offsets[mid+1]) > byteOffset {
return mid // Found the line
}
low = mid + 1
} else {
high = mid - 1
}
}
return -1 // Not found
}

View File

@ -13,15 +13,15 @@ import (
// - Workers execute tasks and post results on resultChan
// - Workers never access state directly
type WorkerPool struct {
highWorkChan chan Task
lowWorkChan chan Task
resultChan chan Result
workerCount int
stopOnce sync.Once
stopChan chan struct{}
wg sync.WaitGroup
taskCounter atomic.Int64
running atomic.Bool
highWorkChan chan Task
lowWorkChan chan Task
resultChan chan Result
workerCount int
stopOnce sync.Once
stopChan chan struct{}
wg sync.WaitGroup
taskCounter atomic.Int64
running atomic.Bool
// pendingTasks tracks in-flight tasks for cancellation by directory.
pendingTasks map[string][]Task

View File

@ -42,13 +42,13 @@ func (t *stubTask) Execute() Result {
}
}
func (t *stubTask) Priority() Priority { return t.priority }
func (t *stubTask) TaskID() string { return t.id }
func (t *stubTask) TaskType() TaskType { return t.taskType }
func (t *stubTask) DirPath() string { return "" }
func (t *stubTask) Priority() Priority { return t.priority }
func (t *stubTask) TaskID() string { return t.id }
func (t *stubTask) TaskType() TaskType { return t.taskType }
func (t *stubTask) DirPath() string { return "" }
func (t *stubTask) Context() context.Context { return context.Background() }
func (t *stubTask) Cancel() {}
func (t *stubTask) Timeout() time.Duration { return 5 * time.Second }
func (t *stubTask) Cancel() {}
func (t *stubTask) Timeout() time.Duration { return 5 * time.Second }
func TestWorkerPool_StartStop(t *testing.T) {
pool := NewWorkerPool(4)
@ -672,9 +672,9 @@ func TestWorkerPool_ResultIsSuccess(t *testing.T) {
}
result2 := Result{
TaskID: "test",
TaskID: "test",
Success: false,
Error: fmt.Errorf("error"),
Error: fmt.Errorf("error"),
}
if result2.IsSuccess() {
t.Error("Result should not be successful")
@ -683,7 +683,7 @@ func TestWorkerPool_ResultIsSuccess(t *testing.T) {
func TestWorkerPool_ResultIsError(t *testing.T) {
result := Result{
TaskID: "test",
TaskID: "test",
Success: false,
Error: fmt.Errorf("error"),
}

View File

@ -20,7 +20,7 @@ func TestEditorInitialLayout(t *testing.T) {
t.Fatalf("GoToEditor: %v", err)
}
h.SendConfig(780, 1688)
// Wait for a new frame after switching to editor page
_, err := e2e.WaitForNewFrame(h, h.FrameCount(), 5*time.Second)
if err != nil {

View File

@ -14,7 +14,7 @@ type TestScenario struct {
Setup func(*Harness)
Actions []HarnessAction
Assertions []FrameAssertion
FrameIndex int // which frame to assert on (-1 for last)
FrameIndex int // which frame to assert on (-1 for last)
Timeout time.Duration
}

View File

@ -646,7 +646,7 @@ func TestRestore_LinePinHoldsAcrossLateWrapFeedback(t *testing.T) {
File: "/wrap.txt",
Cursor: 0,
Scroll: float64(ui.Dp(500 * lh)), // the saving session's visual-space offset
ScrollLine: 200, // the logical line at the viewport top
ScrollLine: 200, // the logical line at the viewport top
ScrollSub: 0,
}

View File

@ -248,7 +248,7 @@ func TestSortModeChangePreservesSearchFilter(t *testing.T) {
// When the sort mode changes, the position map changes,
// so the *index* of the entry "Documents" (which matches "doc")
// will change!
// Let's print the entries to see if they are still correct,
// ignoring the index values themselves.
// (Owner-side snapshot: all reads happen on the logic goroutine.)

View File

@ -2,14 +2,14 @@ package ui
const (
// Standard dimensions in Dp
StatusBarLineHeight = Dp(24)
StatusBarLineHeight = Dp(24)
StatusBarFilenameLine = Dp(24)
StatusBarIconsLine = Dp(24)
BottomBarHeight = Dp(24)
IconSize = Dp(24)
IconGap = Dp(36)
Padding = Dp(8)
ButtonPadding = Dp(8)
StatusBarIconsLine = Dp(24)
BottomBarHeight = Dp(24)
IconSize = Dp(24)
IconGap = Dp(36)
Padding = Dp(8)
ButtonPadding = Dp(8)
)
// LineHeightScale is the baseline-to-baseline spacing multiplier for editor text.