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:
parent
180fa966c8
commit
06b1444207
|
|
@ -66,7 +66,7 @@ func computeVisiblePageRange(s *BrowserState) (minPage, maxPage int) {
|
||||||
if minPage < 0 {
|
if minPage < 0 {
|
||||||
minPage = 0
|
minPage = 0
|
||||||
}
|
}
|
||||||
maxPage = (s.GetScrollIndex() + s.VisibleCount)/PageSize + PrefetchDist
|
maxPage = (s.GetScrollIndex()+s.VisibleCount)/PageSize + PrefetchDist
|
||||||
return minPage, maxPage
|
return minPage, maxPage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,4 +164,3 @@ func filterEntriesByQuery(entries []Entry, query string) []int {
|
||||||
|
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ func TestLoadPage_FromIndexAtOffset(t *testing.T) {
|
||||||
|
|
||||||
// Build position maps for sort modes
|
// Build position maps for sort modes
|
||||||
s.SortIndex = &DirectoryIndex{
|
s.SortIndex = &DirectoryIndex{
|
||||||
Entries: allEntries,
|
Entries: allEntries,
|
||||||
SortOrders: map[string][]int{
|
SortOrders: map[string][]int{
|
||||||
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
|
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
|
||||||
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),
|
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),
|
||||||
|
|
@ -76,7 +76,7 @@ func TestLoadPage_LastPagePartialFromIndex(t *testing.T) {
|
||||||
|
|
||||||
// Build position maps for sort modes
|
// Build position maps for sort modes
|
||||||
s.SortIndex = &DirectoryIndex{
|
s.SortIndex = &DirectoryIndex{
|
||||||
Entries: allEntries,
|
Entries: allEntries,
|
||||||
SortOrders: map[string][]int{
|
SortOrders: map[string][]int{
|
||||||
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
|
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
|
||||||
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),
|
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),
|
||||||
|
|
@ -116,7 +116,7 @@ func TestLoadPage_OutOfBoundsFromIndex(t *testing.T) {
|
||||||
|
|
||||||
// Build position maps for sort modes
|
// Build position maps for sort modes
|
||||||
s.SortIndex = &DirectoryIndex{
|
s.SortIndex = &DirectoryIndex{
|
||||||
Entries: allEntries,
|
Entries: allEntries,
|
||||||
SortOrders: map[string][]int{
|
SortOrders: map[string][]int{
|
||||||
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
|
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
|
||||||
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),
|
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),
|
||||||
|
|
@ -195,7 +195,7 @@ func TestGetEntryByIndex(t *testing.T) {
|
||||||
|
|
||||||
// Build position maps for sort modes
|
// Build position maps for sort modes
|
||||||
s.SortIndex = &DirectoryIndex{
|
s.SortIndex = &DirectoryIndex{
|
||||||
Entries: allEntries,
|
Entries: allEntries,
|
||||||
SortOrders: map[string][]int{
|
SortOrders: map[string][]int{
|
||||||
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
|
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
|
||||||
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),
|
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),
|
||||||
|
|
@ -236,7 +236,7 @@ func TestGetEntryByIndex_UnloadedPage(t *testing.T) {
|
||||||
|
|
||||||
// Build position maps for sort modes
|
// Build position maps for sort modes
|
||||||
s.SortIndex = &DirectoryIndex{
|
s.SortIndex = &DirectoryIndex{
|
||||||
Entries: allEntries,
|
Entries: allEntries,
|
||||||
SortOrders: map[string][]int{
|
SortOrders: map[string][]int{
|
||||||
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
|
"name_asc": buildPositionMap(allEntries, SortModeNameAsc),
|
||||||
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),
|
"date_desc": buildPositionMap(allEntries, SortModeDateDesc),
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ import (
|
||||||
// DirectoryIndex holds a cached index of a directory's contents.
|
// DirectoryIndex holds a cached index of a directory's contents.
|
||||||
// Per design: index stores raw metadata; sorting is pre-computed via position maps.
|
// Per design: index stores raw metadata; sorting is pre-computed via position maps.
|
||||||
type DirectoryIndex struct {
|
type DirectoryIndex struct {
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Mtime time.Time `json:"mtime"`
|
Mtime time.Time `json:"mtime"`
|
||||||
EntryCount int `json:"entry_count"`
|
EntryCount int `json:"entry_count"`
|
||||||
Entries []Entry `json:"entries"`
|
Entries []Entry `json:"entries"`
|
||||||
SortOrders map[string][]int `json:"sort_orders,omitempty"` // key: "name_asc", "name_desc", etc.
|
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.
|
// getCachePath returns the path to the cached index file for a directory.
|
||||||
|
|
|
||||||
|
|
@ -224,5 +224,3 @@ func TestBuildIndex_LargeDirectory(t *testing.T) {
|
||||||
t.Errorf("buildIndex took %v, expected < 5s", elapsed)
|
t.Errorf("buildIndex took %v, expected < 5s", elapsed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -160,8 +160,6 @@ func recomputeSearchResults(s *BrowserState) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ComputeVisibleEntriesForTest is exported for testing purposes.
|
// ComputeVisibleEntriesForTest is exported for testing purposes.
|
||||||
// It builds the list of ui.ListItem entries that should be rendered.
|
// It builds the list of ui.ListItem entries that should be rendered.
|
||||||
func ComputeVisibleEntriesForTest(state *BrowserState) []ui.ListItem {
|
func ComputeVisibleEntriesForTest(state *BrowserState) []ui.ListItem {
|
||||||
|
|
@ -224,7 +222,7 @@ func computeSearchResults(state *BrowserState) []ui.ListItem {
|
||||||
var items []ui.ListItem
|
var items []ui.ListItem
|
||||||
for idx := startIndex; idx < endIndex; idx++ {
|
for idx := startIndex; idx < endIndex; idx++ {
|
||||||
rawIdx := state.SearchResults[idx]
|
rawIdx := state.SearchResults[idx]
|
||||||
|
|
||||||
entry, ok := getEntryByIndex(state, rawIdx)
|
entry, ok := getEntryByIndex(state, rawIdx)
|
||||||
if !ok {
|
if !ok {
|
||||||
// Page not loaded yet; add placeholder
|
// Page not loaded yet; add placeholder
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ func makeTestState(t *testing.T, entryCount int, scrollIndex int, visibleCount i
|
||||||
|
|
||||||
// Build position maps for sort modes
|
// Build position maps for sort modes
|
||||||
s.SortIndex = &DirectoryIndex{
|
s.SortIndex = &DirectoryIndex{
|
||||||
Entries: entries,
|
Entries: entries,
|
||||||
SortOrders: map[string][]int{
|
SortOrders: map[string][]int{
|
||||||
"name_asc": buildPositionMap(entries, SortModeNameAsc),
|
"name_asc": buildPositionMap(entries, SortModeNameAsc),
|
||||||
"date_desc": buildPositionMap(entries, SortModeDateDesc),
|
"date_desc": buildPositionMap(entries, SortModeDateDesc),
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ func TestLazyLoadingLargeDirectory(t *testing.T) {
|
||||||
// 3. Scroll down to page 50
|
// 3. Scroll down to page 50
|
||||||
state.ScrollOffset = 50 * 100 * state.EntryHeight // Scroll to middle
|
state.ScrollOffset = 50 * 100 * state.EntryHeight // Scroll to middle
|
||||||
bm.OnScroll()
|
bm.OnScroll()
|
||||||
|
|
||||||
// Wait for load pages result for scroll
|
// Wait for load pages result for scroll
|
||||||
res, ok = getResult(15 * time.Second)
|
res, ok = getResult(15 * time.Second)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ func TestPixelScroll_EvictionTriggered(t *testing.T) {
|
||||||
entries[i] = NewEntry("/test/file.txt", "file.txt", 100, s.TapTimestamp, false)
|
entries[i] = NewEntry("/test/file.txt", "file.txt", 100, s.TapTimestamp, false)
|
||||||
}
|
}
|
||||||
s.SortIndex = &DirectoryIndex{
|
s.SortIndex = &DirectoryIndex{
|
||||||
Entries: entries,
|
Entries: entries,
|
||||||
SortOrders: map[string][]int{
|
SortOrders: map[string][]int{
|
||||||
"name_asc": buildPositionMap(entries, SortModeNameAsc),
|
"name_asc": buildPositionMap(entries, SortModeNameAsc),
|
||||||
"date_desc": buildPositionMap(entries, SortModeDateDesc),
|
"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)
|
entries[i] = NewEntry("/test/file.txt", "file.txt", 100, s.TapTimestamp, false)
|
||||||
}
|
}
|
||||||
s.SortIndex = &DirectoryIndex{
|
s.SortIndex = &DirectoryIndex{
|
||||||
Entries: entries,
|
Entries: entries,
|
||||||
SortOrders: map[string][]int{
|
SortOrders: map[string][]int{
|
||||||
"name_asc": buildPositionMap(entries, SortModeNameAsc),
|
"name_asc": buildPositionMap(entries, SortModeNameAsc),
|
||||||
"date_desc": buildPositionMap(entries, SortModeDateDesc),
|
"date_desc": buildPositionMap(entries, SortModeDateDesc),
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ func TestScroll_PrefetchTriggered(t *testing.T) {
|
||||||
}
|
}
|
||||||
// Build position maps for sort modes
|
// Build position maps for sort modes
|
||||||
s.SortIndex = &DirectoryIndex{
|
s.SortIndex = &DirectoryIndex{
|
||||||
Entries: entries,
|
Entries: entries,
|
||||||
SortOrders: map[string][]int{
|
SortOrders: map[string][]int{
|
||||||
"name_asc": buildPositionMap(entries, SortModeNameAsc),
|
"name_asc": buildPositionMap(entries, SortModeNameAsc),
|
||||||
"date_desc": buildPositionMap(entries, SortModeDateDesc),
|
"date_desc": buildPositionMap(entries, SortModeDateDesc),
|
||||||
|
|
@ -131,7 +131,7 @@ func TestScroll_EvictionTriggered(t *testing.T) {
|
||||||
}
|
}
|
||||||
// Build position maps for sort modes
|
// Build position maps for sort modes
|
||||||
s.SortIndex = &DirectoryIndex{
|
s.SortIndex = &DirectoryIndex{
|
||||||
Entries: entries,
|
Entries: entries,
|
||||||
SortOrders: map[string][]int{
|
SortOrders: map[string][]int{
|
||||||
"name_asc": buildPositionMap(entries, SortModeNameAsc),
|
"name_asc": buildPositionMap(entries, SortModeNameAsc),
|
||||||
"date_desc": buildPositionMap(entries, SortModeDateDesc),
|
"date_desc": buildPositionMap(entries, SortModeDateDesc),
|
||||||
|
|
@ -246,7 +246,7 @@ func TestScroll_PrefetchTriggeredOnPageBoundary(t *testing.T) {
|
||||||
}
|
}
|
||||||
// Build position maps for sort modes
|
// Build position maps for sort modes
|
||||||
s.SortIndex = &DirectoryIndex{
|
s.SortIndex = &DirectoryIndex{
|
||||||
Entries: entries,
|
Entries: entries,
|
||||||
SortOrders: map[string][]int{
|
SortOrders: map[string][]int{
|
||||||
"name_asc": buildPositionMap(entries, SortModeNameAsc),
|
"name_asc": buildPositionMap(entries, SortModeNameAsc),
|
||||||
"date_desc": buildPositionMap(entries, SortModeDateDesc),
|
"date_desc": buildPositionMap(entries, SortModeDateDesc),
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,7 @@ func TestSearch_JumpToFirst(t *testing.T) {
|
||||||
if len(s.SearchResults) == 0 {
|
if len(s.SearchResults) == 0 {
|
||||||
t.Fatalf("expected results for 'file_3', got 0")
|
t.Fatalf("expected results for 'file_3', got 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
// The first result should be index 3 (file_3).
|
// The first result should be index 3 (file_3).
|
||||||
// With 12 results, it fits in 20 visible. ScrollIndex should be 0.
|
// With 12 results, it fits in 20 visible. ScrollIndex should be 0.
|
||||||
if s.GetScrollIndex() != 0 {
|
if s.GetScrollIndex() != 0 {
|
||||||
|
|
|
||||||
|
|
@ -69,4 +69,3 @@ func comparator(mode SortMode) func(a, b Entry) int {
|
||||||
return func(a, b Entry) int { return 0 }
|
return func(a, b Entry) int { return 0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,20 +19,20 @@ type Entry struct {
|
||||||
|
|
||||||
// Page represents a chunk of directory entries loaded from disk.
|
// Page represents a chunk of directory entries loaded from disk.
|
||||||
type Page struct {
|
type Page struct {
|
||||||
Index int // Page number (0-based)
|
Index int // Page number (0-based)
|
||||||
Entries []Entry // Entries in this page
|
Entries []Entry // Entries in this page
|
||||||
Loaded bool // True if page data is in memory
|
Loaded bool // True if page data is in memory
|
||||||
Dirty bool // True if page needs refresh (external change detected)
|
Dirty bool // True if page needs refresh (external change detected)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BrowserState holds all mutable browser state owned by the logic goroutine.
|
// BrowserState holds all mutable browser state owned by the logic goroutine.
|
||||||
type BrowserState struct {
|
type BrowserState struct {
|
||||||
// Navigation
|
// Navigation
|
||||||
CurrentPath string // Currently browsed directory (relative to root)
|
CurrentPath string // Currently browsed directory (relative to root)
|
||||||
History []string // Path history for navigation
|
History []string // Path history for navigation
|
||||||
ScrollOffset float64 // Vertical scroll offset in pixels (per-pixel scrolling)
|
ScrollOffset float64 // Vertical scroll offset in pixels (per-pixel scrolling)
|
||||||
EntryHeight float64 // Height of a single entry in pixels
|
EntryHeight float64 // Height of a single entry in pixels
|
||||||
VisibleCount int // Number of entries currently visible
|
VisibleCount int // Number of entries currently visible
|
||||||
|
|
||||||
// Lazy loading
|
// Lazy loading
|
||||||
Pages map[int]*Page // Loaded pages by page index
|
Pages map[int]*Page // Loaded pages by page index
|
||||||
|
|
@ -44,8 +44,8 @@ type BrowserState struct {
|
||||||
SortMode SortMode // Current sort mode
|
SortMode SortMode // Current sort mode
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
Query string // Current search query (forwarded from the main-owned search widget)
|
Query string // Current search query (forwarded from the main-owned search widget)
|
||||||
SearchResults []int // Indices of matching entries (empty = no filter)
|
SearchResults []int // Indices of matching entries (empty = no filter)
|
||||||
// NOTE: the search bar's widget.Editor is intentionally NOT part of this
|
// 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
|
// state: Gio mutates widget state on the main goroutine during draw, and
|
||||||
// this state is owned by the logic goroutine (architecture.md §1). The
|
// this state is owned by the logic goroutine (architecture.md §1). The
|
||||||
|
|
@ -62,8 +62,8 @@ type BrowserState struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PageSize = 100 // Entries per page (tunable; ~5KB per page in memory)
|
PageSize = 100 // Entries per page (tunable; ~5KB per page in memory)
|
||||||
PrefetchDist = 2 // Pages to prefetch beyond visible region
|
PrefetchDist = 2 // Pages to prefetch beyond visible region
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewEntry creates a new Entry from the given parameters.
|
// 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.
|
// EvictPages removes pages that are far from the current scroll position.
|
||||||
func (s *BrowserState) EvictPages() {
|
func (s *BrowserState) EvictPages() {
|
||||||
minPage := s.GetScrollIndex()/PageSize - PrefetchDist
|
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 {
|
for idx, page := range s.Pages {
|
||||||
if idx < minPage || idx > maxPage {
|
if idx < minPage || idx > maxPage {
|
||||||
page.Entries = nil // Release memory
|
page.Entries = nil // Release memory
|
||||||
page.Loaded = false
|
page.Loaded = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ func TestFormatSize(t *testing.T) {
|
||||||
{0, "0 B"},
|
{0, "0 B"},
|
||||||
{500, "500 B"},
|
{500, "500 B"},
|
||||||
{1024, "1.0 KB"},
|
{1024, "1.0 KB"},
|
||||||
{1024*1024, "1.0 MB"},
|
{1024 * 1024, "1.0 MB"},
|
||||||
{1024 * 1024 * 1024, "1.0 GB"},
|
{1024 * 1024 * 1024, "1.0 GB"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,7 +118,7 @@ func TestFormatSize(t *testing.T) {
|
||||||
func TestBrowserStateEvictPages(t *testing.T) {
|
func TestBrowserStateEvictPages(t *testing.T) {
|
||||||
s := NewBrowserState()
|
s := NewBrowserState()
|
||||||
s.EntryHeight = 48.0
|
s.EntryHeight = 48.0
|
||||||
s.ScrollOffset = 500 * 48 // Page 5
|
s.ScrollOffset = 500 * 48 // Page 5
|
||||||
s.VisibleCount = 10
|
s.VisibleCount = 10
|
||||||
|
|
||||||
// With ScrollIndex=500, PageSize=100:
|
// With ScrollIndex=500, PageSize=100:
|
||||||
|
|
@ -127,10 +127,10 @@ func TestBrowserStateEvictPages(t *testing.T) {
|
||||||
// Pages 3-7 should be kept, others evicted
|
// Pages 3-7 should be kept, others evicted
|
||||||
|
|
||||||
// Add pages at various positions
|
// Add pages at various positions
|
||||||
s.Pages[0] = NewPage(0, nil) // Should be evicted (far)
|
s.Pages[0] = NewPage(0, nil) // Should be evicted (far)
|
||||||
s.Pages[3] = NewPage(3, nil) // Should be kept (at min boundary)
|
s.Pages[3] = NewPage(3, nil) // Should be kept (at min boundary)
|
||||||
s.Pages[5] = NewPage(5, nil) // Should be kept (visible)
|
s.Pages[5] = NewPage(5, nil) // Should be kept (visible)
|
||||||
s.Pages[7] = NewPage(7, nil) // Should be kept (at max boundary)
|
s.Pages[7] = NewPage(7, nil) // Should be kept (at max boundary)
|
||||||
s.Pages[10] = NewPage(10, nil) // Should be evicted (far)
|
s.Pages[10] = NewPage(10, nil) // Should be evicted (far)
|
||||||
|
|
||||||
s.EvictPages()
|
s.EvictPages()
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,10 @@ func TestWriteFailureTracking(t *testing.T) {
|
||||||
state := NewState()
|
state := NewState()
|
||||||
filename := "test.txt"
|
filename := "test.txt"
|
||||||
state.Editor.Filename = filename
|
state.Editor.Filename = filename
|
||||||
|
|
||||||
// Simulate a write failure
|
// Simulate a write failure
|
||||||
state.Editor.SetWriteFailed(filename, true)
|
state.Editor.SetWriteFailed(filename, true)
|
||||||
|
|
||||||
if !state.Editor.WriteFailed() {
|
if !state.Editor.WriteFailed() {
|
||||||
t.Errorf("Expected WriteFailed() to be true")
|
t.Errorf("Expected WriteFailed() to be true")
|
||||||
}
|
}
|
||||||
|
|
@ -34,7 +34,7 @@ func TestWriteFailureTracking(t *testing.T) {
|
||||||
func TestAutoSave_RetryFails(t *testing.T) {
|
func TestAutoSave_RetryFails(t *testing.T) {
|
||||||
// Setup: New Logic, set mock FS to fail
|
// Setup: New Logic, set mock FS to fail
|
||||||
l := NewLogic(nil, "/", func(string) {})
|
l := NewLogic(nil, "/", func(string) {})
|
||||||
|
|
||||||
// Start a goroutine to drain frameChan to prevent deadlocks
|
// Start a goroutine to drain frameChan to prevent deadlocks
|
||||||
go func() {
|
go func() {
|
||||||
for range l.frameChan {
|
for range l.frameChan {
|
||||||
|
|
@ -50,16 +50,16 @@ func TestAutoSave_RetryFails(t *testing.T) {
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("mockFS is not a *mock.FileSystem")
|
t.Fatal("mockFS is not a *mock.FileSystem")
|
||||||
}
|
}
|
||||||
mfs.SetWriteError(true)
|
mfs.SetWriteError(true)
|
||||||
|
|
||||||
// 1. Manually dispatch a write task
|
// 1. Manually dispatch a write task
|
||||||
task := pool.NewWriteFileTask(filename, []byte("hello"), l.mockFS)
|
task := pool.NewWriteFileTask(filename, []byte("hello"), l.mockFS)
|
||||||
l.workerPool.Dispatch(task)
|
l.workerPool.Dispatch(task)
|
||||||
|
|
||||||
// 2. Manually process the result to trigger the failure state
|
// 2. Manually process the result to trigger the failure state
|
||||||
res := <-l.workerPool.ResultChan()
|
res := <-l.workerPool.ResultChan()
|
||||||
l.handleWorkerResult(res)
|
l.handleWorkerResult(res)
|
||||||
|
|
||||||
// 3. Assert failure
|
// 3. Assert failure
|
||||||
if !l.state.Editor.WriteFailed() {
|
if !l.state.Editor.WriteFailed() {
|
||||||
t.Errorf("Expected WriteFailed() to be true")
|
t.Errorf("Expected WriteFailed() to be true")
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ func TestBackspace(t *testing.T) {
|
||||||
state := NewState()
|
state := NewState()
|
||||||
TheState = state
|
TheState = state
|
||||||
state.Editor.Buffer = "Hello"
|
state.Editor.Buffer = "Hello"
|
||||||
state.Editor.CursorPosition = 5
|
state.Editor.CursorPosition = 5
|
||||||
|
|
||||||
HandleBackspace() // Assuming we create this function
|
HandleBackspace() // Assuming we create this function
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -189,9 +189,9 @@ func TestLargeFileChunkBoundary(t *testing.T) {
|
||||||
// (The old fixed-slot model treated each chunk as an independent buffer and
|
// (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.)
|
// did NOT shift later chunks; that was the bug this design replaces.)
|
||||||
expectedStr := string(initialContent)
|
expectedStr := string(initialContent)
|
||||||
expectedStr = expectedStr[:65000] + "CHUNK1" + expectedStr[65000:] // insert 1 @65000
|
expectedStr = expectedStr[:65000] + "CHUNK1" + expectedStr[65000:] // insert 1 @65000
|
||||||
expectedStr = expectedStr[:65535] + "BOUNDARY" + expectedStr[65535:] // insert 2 @65535
|
expectedStr = expectedStr[:65535] + "BOUNDARY" + expectedStr[65535:] // insert 2 @65535
|
||||||
expectedStr = expectedStr[:262000] + "CHUNK3" + expectedStr[262000:] // insert 3 @262000
|
expectedStr = expectedStr[:262000] + "CHUNK3" + expectedStr[262000:] // insert 3 @262000
|
||||||
expected := []byte(expectedStr)
|
expected := []byte(expectedStr)
|
||||||
|
|
||||||
if len(savedContent) != len(expected) {
|
if len(savedContent) != len(expected) {
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,26 @@
|
||||||
package editor
|
package editor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
|
||||||
"pad/internal/ui"
|
"pad/internal/ui"
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEditorLayout_Filename(t *testing.T) {
|
func TestEditorLayout_Filename(t *testing.T) {
|
||||||
// Setup: Reset state
|
// Setup: Reset state
|
||||||
TheState = NewState()
|
TheState = NewState()
|
||||||
|
|
||||||
// Set a filename
|
// Set a filename
|
||||||
expectedFilename := "test.txt"
|
expectedFilename := "test.txt"
|
||||||
TheState.Editor.Filename = expectedFilename
|
TheState.Editor.Filename = expectedFilename
|
||||||
|
|
||||||
// Run layout
|
// Run layout
|
||||||
screenWidth := ui.Dp(400)
|
screenWidth := ui.Dp(400)
|
||||||
screenHeight := ui.Dp(800)
|
screenHeight := ui.Dp(800)
|
||||||
elements := EditorLayout(screenWidth, screenHeight, false)
|
elements := EditorLayout(screenWidth, screenHeight, false)
|
||||||
|
|
||||||
// Find top bar (assuming it's the first container element)
|
// Find top bar (assuming it's the first container element)
|
||||||
topBar := elements[0].(ui.Container)
|
topBar := elements[0].(ui.Container)
|
||||||
|
|
||||||
// Find the filename label in the top bar (the back icon precedes it).
|
// Find the filename label in the top bar (the back icon precedes it).
|
||||||
var filenameLabel ui.Label
|
var filenameLabel ui.Label
|
||||||
for _, c := range topBar.Children {
|
for _, c := range topBar.Children {
|
||||||
|
|
@ -37,18 +37,18 @@ func TestEditorLayout_Filename(t *testing.T) {
|
||||||
func TestEditorLayout_DefaultFilename(t *testing.T) {
|
func TestEditorLayout_DefaultFilename(t *testing.T) {
|
||||||
// Setup: Reset state
|
// Setup: Reset state
|
||||||
TheState = NewState()
|
TheState = NewState()
|
||||||
|
|
||||||
// Filename is empty
|
// Filename is empty
|
||||||
TheState.Editor.Filename = ""
|
TheState.Editor.Filename = ""
|
||||||
|
|
||||||
// Run layout
|
// Run layout
|
||||||
screenWidth := ui.Dp(400)
|
screenWidth := ui.Dp(400)
|
||||||
screenHeight := ui.Dp(800)
|
screenHeight := ui.Dp(800)
|
||||||
elements := EditorLayout(screenWidth, screenHeight, false)
|
elements := EditorLayout(screenWidth, screenHeight, false)
|
||||||
|
|
||||||
// Find top bar (assuming it's the first container element)
|
// Find top bar (assuming it's the first container element)
|
||||||
topBar := elements[0].(ui.Container)
|
topBar := elements[0].(ui.Container)
|
||||||
|
|
||||||
// Find the filename label in the top bar (the back icon precedes it).
|
// Find the filename label in the top bar (the back icon precedes it).
|
||||||
var filenameLabel ui.Label
|
var filenameLabel ui.Label
|
||||||
for _, c := range topBar.Children {
|
for _, c := range topBar.Children {
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,8 @@ func newChunkedState(t *testing.T, content string, chunkSize int) *State {
|
||||||
|
|
||||||
func TestRuneIndexToByteStr(t *testing.T) {
|
func TestRuneIndexToByteStr(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
s string
|
s string
|
||||||
n int
|
n int
|
||||||
want int
|
want int
|
||||||
}{
|
}{
|
||||||
{"", 0, 0},
|
{"", 0, 0},
|
||||||
|
|
|
||||||
|
|
@ -20,45 +20,45 @@ func absFloat(x ui.Dp) float64 {
|
||||||
func TestFragmentStartYCalculation(t *testing.T) {
|
func TestFragmentStartYCalculation(t *testing.T) {
|
||||||
// Setup test scenario
|
// Setup test scenario
|
||||||
lineHeight := ui.Dp(16.8) // 14 * 1.2
|
lineHeight := ui.Dp(16.8) // 14 * 1.2
|
||||||
|
|
||||||
// Test case 1: Scroll to line 0 (top)
|
// Test case 1: Scroll to line 0 (top)
|
||||||
scrollOffset := ui.Dp(0)
|
scrollOffset := ui.Dp(0)
|
||||||
expectedVisualLine := 0
|
expectedVisualLine := 0
|
||||||
expectedFragmentStartY := ui.Dp(0)
|
expectedFragmentStartY := ui.Dp(0)
|
||||||
|
|
||||||
visualLine := int(scrollOffset / lineHeight)
|
visualLine := int(scrollOffset / lineHeight)
|
||||||
fragmentStartY := ui.Dp(visualLine) * lineHeight
|
fragmentStartY := ui.Dp(visualLine) * lineHeight
|
||||||
|
|
||||||
if visualLine != expectedVisualLine {
|
if visualLine != expectedVisualLine {
|
||||||
t.Errorf("Test 1: Expected visualLine %d, got %d", expectedVisualLine, visualLine)
|
t.Errorf("Test 1: Expected visualLine %d, got %d", expectedVisualLine, visualLine)
|
||||||
}
|
}
|
||||||
if fragmentStartY != expectedFragmentStartY {
|
if fragmentStartY != expectedFragmentStartY {
|
||||||
t.Errorf("Test 1: Expected fragmentStartY %v, got %v", expectedFragmentStartY, fragmentStartY)
|
t.Errorf("Test 1: Expected fragmentStartY %v, got %v", expectedFragmentStartY, fragmentStartY)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test case 2: Scroll to line 1
|
// Test case 2: Scroll to line 1
|
||||||
scrollOffset = ui.Dp(16.8)
|
scrollOffset = ui.Dp(16.8)
|
||||||
expectedVisualLine = 1
|
expectedVisualLine = 1
|
||||||
expectedFragmentStartY = ui.Dp(16.8)
|
expectedFragmentStartY = ui.Dp(16.8)
|
||||||
|
|
||||||
visualLine = int(scrollOffset / lineHeight)
|
visualLine = int(scrollOffset / lineHeight)
|
||||||
fragmentStartY = ui.Dp(visualLine) * lineHeight
|
fragmentStartY = ui.Dp(visualLine) * lineHeight
|
||||||
|
|
||||||
if visualLine != expectedVisualLine {
|
if visualLine != expectedVisualLine {
|
||||||
t.Errorf("Test 2: Expected visualLine %d, got %d", expectedVisualLine, visualLine)
|
t.Errorf("Test 2: Expected visualLine %d, got %d", expectedVisualLine, visualLine)
|
||||||
}
|
}
|
||||||
if fragmentStartY != expectedFragmentStartY {
|
if fragmentStartY != expectedFragmentStartY {
|
||||||
t.Errorf("Test 2: Expected fragmentStartY %v, got %v", expectedFragmentStartY, fragmentStartY)
|
t.Errorf("Test 2: Expected fragmentStartY %v, got %v", expectedFragmentStartY, fragmentStartY)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test case 3: Scroll to line 2
|
// Test case 3: Scroll to line 2
|
||||||
scrollOffset = ui.Dp(33.6)
|
scrollOffset = ui.Dp(33.6)
|
||||||
expectedVisualLine = 2
|
expectedVisualLine = 2
|
||||||
expectedFragmentStartY = ui.Dp(33.6)
|
expectedFragmentStartY = ui.Dp(33.6)
|
||||||
|
|
||||||
visualLine = int(scrollOffset / lineHeight)
|
visualLine = int(scrollOffset / lineHeight)
|
||||||
fragmentStartY = ui.Dp(visualLine) * lineHeight
|
fragmentStartY = ui.Dp(visualLine) * lineHeight
|
||||||
|
|
||||||
if visualLine != expectedVisualLine {
|
if visualLine != expectedVisualLine {
|
||||||
t.Errorf("Test 3: Expected visualLine %d, got %d", expectedVisualLine, visualLine)
|
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},
|
Advance: []ui.Dp{10, 10, 10, 10},
|
||||||
LineHeight: ui.Dp(16.8),
|
LineHeight: ui.Dp(16.8),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build visual line index
|
// Build visual line index
|
||||||
var visualLineOffsets []int32
|
var visualLineOffsets []int32
|
||||||
visualLineOffsets = append(visualLineOffsets, int32(layout.ByteOffsets[0]))
|
visualLineOffsets = append(visualLineOffsets, int32(layout.ByteOffsets[0]))
|
||||||
|
|
||||||
for i := 1; i < len(layout.Y); i++ {
|
for i := 1; i < len(layout.Y); i++ {
|
||||||
if layout.Y[i] != layout.Y[i-1] {
|
if layout.Y[i] != layout.Y[i-1] {
|
||||||
// New visual line starts at this glyph
|
// New visual line starts at this glyph
|
||||||
visualLineOffsets = append(visualLineOffsets, int32(layout.ByteOffsets[i]))
|
visualLineOffsets = append(visualLineOffsets, int32(layout.ByteOffsets[i]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// With 4 glyphs at different Y positions, we get 4 visual line starts
|
// 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
|
// This is actually correct - each glyph that starts a new line is a visual line start
|
||||||
if len(visualLineOffsets) != 4 {
|
if len(visualLineOffsets) != 4 {
|
||||||
t.Errorf("Expected 4 visual line starts, got %d", len(visualLineOffsets))
|
t.Errorf("Expected 4 visual line starts, got %d", len(visualLineOffsets))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check byte offsets - should match all the byte offsets where Y changes
|
// Check byte offsets - should match all the byte offsets where Y changes
|
||||||
expectedOffsets := []int32{0, 7, 14, 21}
|
expectedOffsets := []int32{0, 7, 14, 21}
|
||||||
for i := 0; i < len(visualLineOffsets) && i < len(expectedOffsets); i++ {
|
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
|
// TestWordWrapFragmentStartY tests fragmentStartY calculation with word wrap enabled
|
||||||
func TestWordWrapFragmentStartY(t *testing.T) {
|
func TestWordWrapFragmentStartY(t *testing.T) {
|
||||||
lineHeight := ui.Dp(16.8) // 14 * 1.2
|
lineHeight := ui.Dp(16.8) // 14 * 1.2
|
||||||
|
|
||||||
// Test case: Word wrap enabled, scroll to visual line 5
|
// Test case: Word wrap enabled, scroll to visual line 5
|
||||||
// With word wrap, visual lines don't correspond 1:1 with logical lines
|
// With word wrap, visual lines don't correspond 1:1 with logical lines
|
||||||
wordWrap := true
|
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
|
scrollOffset := ui.Dp(5 * float64(lineHeight)) // Scroll to visual line 5
|
||||||
visualLine := int(scrollOffset / lineHeight)
|
visualLine := int(scrollOffset / lineHeight)
|
||||||
|
|
||||||
// With word wrap, we should use a different estimation
|
// With word wrap, we should use a different estimation
|
||||||
// because one logical line can span multiple visual lines
|
// because one logical line can span multiple visual lines
|
||||||
if wordWrap {
|
if wordWrap {
|
||||||
|
|
@ -122,7 +122,7 @@ func TestWordWrapFragmentStartY(t *testing.T) {
|
||||||
// to avoid skipping wrapped lines
|
// to avoid skipping wrapped lines
|
||||||
estimatedBytesPerVisualLine := 10 // Very small step
|
estimatedBytesPerVisualLine := 10 // Very small step
|
||||||
expectedStartByteOffset := visualLine * estimatedBytesPerVisualLine
|
expectedStartByteOffset := visualLine * estimatedBytesPerVisualLine
|
||||||
|
|
||||||
if expectedStartByteOffset != 50 { // 5 * 10
|
if expectedStartByteOffset != 50 { // 5 * 10
|
||||||
t.Errorf("Word wrap case: expected start byte offset %d, got %d", 50, expectedStartByteOffset)
|
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
|
// Without word wrap, use bytes per logical line estimate
|
||||||
estimatedBytesPerLogicalLine := 50
|
estimatedBytesPerLogicalLine := 50
|
||||||
expectedStartByteOffset := visualLine * estimatedBytesPerLogicalLine
|
expectedStartByteOffset := visualLine * estimatedBytesPerLogicalLine
|
||||||
|
|
||||||
if expectedStartByteOffset != 250 { // 5 * 50
|
if expectedStartByteOffset != 250 { // 5 * 50
|
||||||
t.Errorf("No word wrap case: expected start byte offset %d, got %d", 250, expectedStartByteOffset)
|
t.Errorf("No word wrap case: expected start byte offset %d, got %d", 250, expectedStartByteOffset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fragmentStartY should always be visualLine * lineHeight
|
// fragmentStartY should always be visualLine * lineHeight
|
||||||
expectedFragmentStartY := ui.Dp(visualLine) * lineHeight
|
expectedFragmentStartY := ui.Dp(visualLine) * lineHeight
|
||||||
if expectedFragmentStartY != ui.Dp(5*16.8) {
|
if expectedFragmentStartY != ui.Dp(5*16.8) {
|
||||||
|
|
@ -150,31 +150,31 @@ func TestByteOffsetAndYFromScroll(t *testing.T) {
|
||||||
visualLineIndex := &types.VisualLineIndex{
|
visualLineIndex := &types.VisualLineIndex{
|
||||||
Offsets: []int32{0, 7, 14, 21}, // 4 lines
|
Offsets: []int32{0, 7, 14, 21}, // 4 lines
|
||||||
}
|
}
|
||||||
|
|
||||||
layout := ui.GlyphLayout{
|
layout := ui.GlyphLayout{
|
||||||
ByteOffsets: []int{0, 7, 14, 21},
|
ByteOffsets: []int{0, 7, 14, 21},
|
||||||
X: []ui.Dp{0, 0, 0, 0},
|
X: []ui.Dp{0, 0, 0, 0},
|
||||||
Y: []ui.Dp{0, 16.8, 33.6, 50.4},
|
Y: []ui.Dp{0, 16.8, 33.6, 50.4},
|
||||||
Advance: []ui.Dp{10, 10, 10, 10},
|
Advance: []ui.Dp{10, 10, 10, 10},
|
||||||
LineHeight: lineHeight,
|
LineHeight: lineHeight,
|
||||||
VisualLineIndex: visualLineIndex,
|
VisualLineIndex: visualLineIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test scrolling to different positions
|
// Test scrolling to different positions
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
scrollOffset ui.Dp
|
scrollOffset ui.Dp
|
||||||
expectedByte int
|
expectedByte int
|
||||||
expectedY ui.Dp
|
expectedY ui.Dp
|
||||||
}{
|
}{
|
||||||
{ui.Dp(0), 0, ui.Dp(0)}, // Top of document
|
{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(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(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(50.4), 21, ui.Dp(50.4)}, // Start of line 3
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
byteOffset, y := ByteOffsetAndYFromScrollWithLayout(tc.scrollOffset, layout)
|
byteOffset, y := ByteOffsetAndYFromScrollWithLayout(tc.scrollOffset, layout)
|
||||||
|
|
||||||
if byteOffset != tc.expectedByte {
|
if byteOffset != tc.expectedByte {
|
||||||
t.Errorf("Scroll %v: expected byte offset %d, got %d", tc.scrollOffset, tc.expectedByte, byteOffset)
|
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 {
|
if lineHeight == 0 {
|
||||||
lineHeight = editor.EditorLineHeight()
|
lineHeight = editor.EditorLineHeight()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate which visual line should be at the given scroll offset
|
// Calculate which visual line should be at the given scroll offset
|
||||||
visualLine := int(scrollOffset / lineHeight)
|
visualLine := int(scrollOffset / lineHeight)
|
||||||
|
|
||||||
// If we have a visual line index, use it for accurate byte offset
|
// If we have a visual line index, use it for accurate byte offset
|
||||||
if layout.VisualLineIndex != nil && visualLine < len(layout.VisualLineIndex.Offsets) {
|
if layout.VisualLineIndex != nil && visualLine < len(layout.VisualLineIndex.Offsets) {
|
||||||
byteOffset := int(layout.VisualLineIndex.Offsets[visualLine])
|
byteOffset := int(layout.VisualLineIndex.Offsets[visualLine])
|
||||||
lineTop := ui.Dp(visualLine) * lineHeight
|
lineTop := ui.Dp(visualLine) * lineHeight
|
||||||
return byteOffset, lineTop
|
return byteOffset, lineTop
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to the original logic using layout.Y values
|
// Fallback to the original logic using layout.Y values
|
||||||
// 1. Find the index of the line whose top is <= scrollOffset
|
// 1. Find the index of the line whose top is <= scrollOffset
|
||||||
idx := sort.Search(len(layout.Y), func(i int) bool {
|
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
|
lineTop := layout.Y[lineStartIdx] - lineHeight
|
||||||
|
|
||||||
return layout.ByteOffsets[lineStartIdx], lineTop
|
return layout.ByteOffsets[lineStartIdx], lineTop
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -202,4 +202,3 @@ func byteOffsetOfLine(content string, i int) int {
|
||||||
}
|
}
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,13 +53,13 @@ func (t *cancelTask) Execute() Result {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *cancelTask) Priority() Priority { return t.priority }
|
func (t *cancelTask) Priority() Priority { return t.priority }
|
||||||
func (t *cancelTask) TaskID() string { return t.id }
|
func (t *cancelTask) TaskID() string { return t.id }
|
||||||
func (t *cancelTask) TaskType() TaskType { return t.taskType }
|
func (t *cancelTask) TaskType() TaskType { return t.taskType }
|
||||||
func (t *cancelTask) DirPath() string { return "/test" }
|
func (t *cancelTask) DirPath() string { return "/test" }
|
||||||
func (t *cancelTask) Context() context.Context { return t.ctx }
|
func (t *cancelTask) Context() context.Context { return t.ctx }
|
||||||
func (t *cancelTask) Cancel() { t.cancel() }
|
func (t *cancelTask) Cancel() { t.cancel() }
|
||||||
func (t *cancelTask) Timeout() time.Duration { return 0 }
|
func (t *cancelTask) Timeout() time.Duration { return 0 }
|
||||||
|
|
||||||
// timeoutTask is a task that respects timeout.
|
// timeoutTask is a task that respects timeout.
|
||||||
type timeoutTask struct {
|
type timeoutTask struct {
|
||||||
|
|
@ -79,13 +79,13 @@ func (t *timeoutTask) Execute() Result {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *timeoutTask) Priority() Priority { return t.priority }
|
func (t *timeoutTask) Priority() Priority { return t.priority }
|
||||||
func (t *timeoutTask) TaskID() string { return t.id }
|
func (t *timeoutTask) TaskID() string { return t.id }
|
||||||
func (t *timeoutTask) TaskType() TaskType { return t.taskType }
|
func (t *timeoutTask) TaskType() TaskType { return t.taskType }
|
||||||
func (t *timeoutTask) DirPath() string { return "/test" }
|
func (t *timeoutTask) DirPath() string { return "/test" }
|
||||||
func (t *timeoutTask) Context() context.Context { return context.Background() }
|
func (t *timeoutTask) Context() context.Context { return context.Background() }
|
||||||
func (t *timeoutTask) Cancel() {}
|
func (t *timeoutTask) Cancel() {}
|
||||||
func (t *timeoutTask) Timeout() time.Duration { return 50 * time.Millisecond }
|
func (t *timeoutTask) Timeout() time.Duration { return 50 * time.Millisecond }
|
||||||
|
|
||||||
func TestTaskContextCancellation(t *testing.T) {
|
func TestTaskContextCancellation(t *testing.T) {
|
||||||
task := newCancelTask("test-cancel", 1*time.Second)
|
task := newCancelTask("test-cancel", 1*time.Second)
|
||||||
|
|
|
||||||
|
|
@ -68,10 +68,10 @@ type notifyEvent struct {
|
||||||
// FileSystem is a thread-safe in-memory filesystem for testing.
|
// FileSystem is a thread-safe in-memory filesystem for testing.
|
||||||
type FileSystem struct {
|
type FileSystem struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
files map[string]*File // path -> file
|
files map[string]*File // path -> file
|
||||||
delay time.Duration // uniform delay applied to all operations
|
delay time.Duration // uniform delay applied to all operations
|
||||||
changes chan FileChangeEvent // async change notifications (nil = no notifications)
|
changes chan FileChangeEvent // async change notifications (nil = no notifications)
|
||||||
writeError bool // Added to simulate write errors
|
writeError bool // Added to simulate write errors
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFileSystem creates an empty mock filesystem.
|
// NewFileSystem creates an empty mock filesystem.
|
||||||
|
|
|
||||||
|
|
@ -115,8 +115,6 @@ func (t TaskType) String() string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// --- Specific Task Implementations ---
|
// --- Specific Task Implementations ---
|
||||||
|
|
||||||
// ReadChunkTask reads a specific chunk of a file.
|
// 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) Priority() Priority { return HighPriority }
|
||||||
func (t *ReadChunkTask) TaskType() TaskType { return TypeReadChunk }
|
func (t *ReadChunkTask) TaskType() TaskType { return TypeReadChunk }
|
||||||
func (t *ReadChunkTask) TaskID() string { return t.taskID }
|
func (t *ReadChunkTask) TaskID() string { return t.taskID }
|
||||||
func (t *ReadChunkTask) DirPath() string { return filepath.Dir(t.Path) }
|
func (t *ReadChunkTask) DirPath() string { return filepath.Dir(t.Path) }
|
||||||
func (t *ReadChunkTask) Context() context.Context { return t.ctx }
|
func (t *ReadChunkTask) Context() context.Context { return t.ctx }
|
||||||
func (t *ReadChunkTask) Timeout() time.Duration { return 5 * time.Second }
|
func (t *ReadChunkTask) Timeout() time.Duration { return 5 * time.Second }
|
||||||
func (t *ReadChunkTask) Cancel() { if t.cancel != nil { t.cancel() } }
|
func (t *ReadChunkTask) Cancel() {
|
||||||
|
if t.cancel != nil {
|
||||||
|
t.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// StatFileTask retrieves file metadata (like size and modification time).
|
// StatFileTask retrieves file metadata (like size and modification time).
|
||||||
// The plan indicates this replaces the full-file ReadFileTask for opening.
|
// 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) Priority() Priority { return MediumPriority }
|
||||||
func (t *StatFileTask) TaskType() TaskType { return TypeStatFile }
|
func (t *StatFileTask) TaskType() TaskType { return TypeStatFile }
|
||||||
func (t *StatFileTask) TaskID() string { return t.taskID }
|
func (t *StatFileTask) TaskID() string { return t.taskID }
|
||||||
func (t *StatFileTask) DirPath() string { return filepath.Dir(t.Path) }
|
func (t *StatFileTask) DirPath() string { return filepath.Dir(t.Path) }
|
||||||
func (t *StatFileTask) Context() context.Context { return t.ctx }
|
func (t *StatFileTask) Context() context.Context { return t.ctx }
|
||||||
func (t *StatFileTask) Timeout() time.Duration { return 5 * time.Second }
|
func (t *StatFileTask) Timeout() time.Duration { return 5 * time.Second }
|
||||||
func (t *StatFileTask) Cancel() { if t.cancel != nil { t.cancel() } }
|
func (t *StatFileTask) Cancel() {
|
||||||
|
if t.cancel != nil {
|
||||||
|
t.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FileStat holds file metadata.
|
// FileStat holds file metadata.
|
||||||
type FileStat struct {
|
type FileStat struct {
|
||||||
|
|
@ -267,13 +273,17 @@ func (t *BuildLineIndexTask) Execute() Result {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *BuildLineIndexTask) Priority() Priority { return LowPriority }
|
func (t *BuildLineIndexTask) Priority() Priority { return LowPriority }
|
||||||
func (t *BuildLineIndexTask) TaskType() TaskType { return TypeBuildLineIndex }
|
func (t *BuildLineIndexTask) TaskType() TaskType { return TypeBuildLineIndex }
|
||||||
func (t *BuildLineIndexTask) TaskID() string { return t.taskID }
|
func (t *BuildLineIndexTask) TaskID() string { return t.taskID }
|
||||||
func (t *BuildLineIndexTask) DirPath() string { return filepath.Dir(t.Path) }
|
func (t *BuildLineIndexTask) DirPath() string { return filepath.Dir(t.Path) }
|
||||||
func (t *BuildLineIndexTask) Context() context.Context { return t.ctx }
|
func (t *BuildLineIndexTask) Context() context.Context { return t.ctx }
|
||||||
func (t *BuildLineIndexTask) Timeout() time.Duration { return 30 * time.Second }
|
func (t *BuildLineIndexTask) Timeout() time.Duration { return 30 * time.Second }
|
||||||
func (t *BuildLineIndexTask) Cancel() { if t.cancel != nil { t.cancel() } }
|
func (t *BuildLineIndexTask) Cancel() {
|
||||||
|
if t.cancel != nil {
|
||||||
|
t.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// BuildIndexTask builds the directory index for the browser.
|
// BuildIndexTask builds the directory index for the browser.
|
||||||
type BuildIndexTask struct {
|
type BuildIndexTask struct {
|
||||||
|
|
@ -304,13 +314,17 @@ func (t *BuildIndexTask) Execute() Result {
|
||||||
return Result{TaskID: t.taskID, TaskType: TypeBuildIndex, Success: true, Data: entries}
|
return Result{TaskID: t.taskID, TaskType: TypeBuildIndex, Success: true, Data: entries}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *BuildIndexTask) Priority() Priority { return HighPriority }
|
func (t *BuildIndexTask) Priority() Priority { return HighPriority }
|
||||||
func (t *BuildIndexTask) TaskType() TaskType { return TypeBuildIndex }
|
func (t *BuildIndexTask) TaskType() TaskType { return TypeBuildIndex }
|
||||||
func (t *BuildIndexTask) TaskID() string { return t.taskID }
|
func (t *BuildIndexTask) TaskID() string { return t.taskID }
|
||||||
func (t *BuildIndexTask) DirPath() string { return t.Dir }
|
func (t *BuildIndexTask) DirPath() string { return t.Dir }
|
||||||
func (t *BuildIndexTask) Context() context.Context { return t.ctx }
|
func (t *BuildIndexTask) Context() context.Context { return t.ctx }
|
||||||
func (t *BuildIndexTask) Timeout() time.Duration { return 5 * time.Second }
|
func (t *BuildIndexTask) Timeout() time.Duration { return 5 * time.Second }
|
||||||
func (t *BuildIndexTask) Cancel() { if t.cancel != nil { t.cancel() } }
|
func (t *BuildIndexTask) Cancel() {
|
||||||
|
if t.cancel != nil {
|
||||||
|
t.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// LoadPagesTask loads specific pages from a directory index.
|
// LoadPagesTask loads specific pages from a directory index.
|
||||||
type LoadPagesTask struct {
|
type LoadPagesTask struct {
|
||||||
|
|
@ -343,13 +357,17 @@ func (t *LoadPagesTask) Execute() Result {
|
||||||
return Result{TaskID: t.taskID, TaskType: TypeLoadPages, Success: true, Data: t.PageIdxs}
|
return Result{TaskID: t.taskID, TaskType: TypeLoadPages, Success: true, Data: t.PageIdxs}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *LoadPagesTask) Priority() Priority { return HighPriority }
|
func (t *LoadPagesTask) Priority() Priority { return HighPriority }
|
||||||
func (t *LoadPagesTask) TaskType() TaskType { return TypeLoadPages }
|
func (t *LoadPagesTask) TaskType() TaskType { return TypeLoadPages }
|
||||||
func (t *LoadPagesTask) TaskID() string { return t.taskID }
|
func (t *LoadPagesTask) TaskID() string { return t.taskID }
|
||||||
func (t *LoadPagesTask) DirPath() string { return t.Dir }
|
func (t *LoadPagesTask) DirPath() string { return t.Dir }
|
||||||
func (t *LoadPagesTask) Context() context.Context { return t.ctx }
|
func (t *LoadPagesTask) Context() context.Context { return t.ctx }
|
||||||
func (t *LoadPagesTask) Timeout() time.Duration { return 5 * time.Second }
|
func (t *LoadPagesTask) Timeout() time.Duration { return 5 * time.Second }
|
||||||
func (t *LoadPagesTask) Cancel() { if t.cancel != nil { t.cancel() } }
|
func (t *LoadPagesTask) Cancel() {
|
||||||
|
if t.cancel != nil {
|
||||||
|
t.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ReadDirTask reads directory entries.
|
// ReadDirTask reads directory entries.
|
||||||
type ReadDirTask struct {
|
type ReadDirTask struct {
|
||||||
|
|
@ -381,13 +399,17 @@ func (t *ReadDirTask) Execute() Result {
|
||||||
return Result{TaskID: t.taskID, TaskType: TypeReadDir, Success: true, Data: entries}
|
return Result{TaskID: t.taskID, TaskType: TypeReadDir, Success: true, Data: entries}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ReadDirTask) Priority() Priority { return HighPriority }
|
func (t *ReadDirTask) Priority() Priority { return HighPriority }
|
||||||
func (t *ReadDirTask) TaskType() TaskType { return TypeReadDir }
|
func (t *ReadDirTask) TaskType() TaskType { return TypeReadDir }
|
||||||
func (t *ReadDirTask) TaskID() string { return t.taskID }
|
func (t *ReadDirTask) TaskID() string { return t.taskID }
|
||||||
func (t *ReadDirTask) DirPath() string { return t.Dir }
|
func (t *ReadDirTask) DirPath() string { return t.Dir }
|
||||||
func (t *ReadDirTask) Context() context.Context { return t.ctx }
|
func (t *ReadDirTask) Context() context.Context { return t.ctx }
|
||||||
func (t *ReadDirTask) Timeout() time.Duration { return 5 * time.Second }
|
func (t *ReadDirTask) Timeout() time.Duration { return 5 * time.Second }
|
||||||
func (t *ReadDirTask) Cancel() { if t.cancel != nil { t.cancel() } }
|
func (t *ReadDirTask) Cancel() {
|
||||||
|
if t.cancel != nil {
|
||||||
|
t.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SaveStateTask persists application state.
|
// SaveStateTask persists application state.
|
||||||
type SaveStateTask struct {
|
type SaveStateTask struct {
|
||||||
|
|
@ -421,13 +443,17 @@ func (t *SaveStateTask) Execute() Result {
|
||||||
return Result{TaskID: t.taskID, TaskType: TypeSaveState, Success: true}
|
return Result{TaskID: t.taskID, TaskType: TypeSaveState, Success: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *SaveStateTask) Priority() Priority { return LowPriority }
|
func (t *SaveStateTask) Priority() Priority { return LowPriority }
|
||||||
func (t *SaveStateTask) TaskType() TaskType { return TypeSaveState }
|
func (t *SaveStateTask) TaskType() TaskType { return TypeSaveState }
|
||||||
func (t *SaveStateTask) TaskID() string { return t.taskID }
|
func (t *SaveStateTask) TaskID() string { return t.taskID }
|
||||||
func (t *SaveStateTask) DirPath() string { return filepath.Dir(t.Path) }
|
func (t *SaveStateTask) DirPath() string { return filepath.Dir(t.Path) }
|
||||||
func (t *SaveStateTask) Context() context.Context { return t.ctx }
|
func (t *SaveStateTask) Context() context.Context { return t.ctx }
|
||||||
func (t *SaveStateTask) Timeout() time.Duration { return 5 * time.Second }
|
func (t *SaveStateTask) Timeout() time.Duration { return 5 * time.Second }
|
||||||
func (t *SaveStateTask) Cancel() { if t.cancel != nil { t.cancel() } }
|
func (t *SaveStateTask) Cancel() {
|
||||||
|
if t.cancel != nil {
|
||||||
|
t.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SaveUndoTask persists undo stack.
|
// SaveUndoTask persists undo stack.
|
||||||
type SaveUndoTask struct {
|
type SaveUndoTask struct {
|
||||||
|
|
@ -461,13 +487,17 @@ func (t *SaveUndoTask) Execute() Result {
|
||||||
return Result{TaskID: t.taskID, TaskType: TypeSaveUndo, Success: true}
|
return Result{TaskID: t.taskID, TaskType: TypeSaveUndo, Success: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *SaveUndoTask) Priority() Priority { return LowPriority }
|
func (t *SaveUndoTask) Priority() Priority { return LowPriority }
|
||||||
func (t *SaveUndoTask) TaskType() TaskType { return TypeSaveUndo }
|
func (t *SaveUndoTask) TaskType() TaskType { return TypeSaveUndo }
|
||||||
func (t *SaveUndoTask) TaskID() string { return t.taskID }
|
func (t *SaveUndoTask) TaskID() string { return t.taskID }
|
||||||
func (t *SaveUndoTask) DirPath() string { return filepath.Dir(t.Path) }
|
func (t *SaveUndoTask) DirPath() string { return filepath.Dir(t.Path) }
|
||||||
func (t *SaveUndoTask) Context() context.Context { return t.ctx }
|
func (t *SaveUndoTask) Context() context.Context { return t.ctx }
|
||||||
func (t *SaveUndoTask) Timeout() time.Duration { return 5 * time.Second }
|
func (t *SaveUndoTask) Timeout() time.Duration { return 5 * time.Second }
|
||||||
func (t *SaveUndoTask) Cancel() { if t.cancel != nil { t.cancel() } }
|
func (t *SaveUndoTask) Cancel() {
|
||||||
|
if t.cancel != nil {
|
||||||
|
t.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ReadFileTask reads the full content of a file.
|
// ReadFileTask reads the full content of a file.
|
||||||
// Used as a fallback for small files or initial load before chunking is set up.
|
// 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}
|
return Result{TaskID: t.taskID, TaskType: TypeReadFile, FilePath: t.Path, Success: true, Data: content}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ReadFileTask) Priority() Priority { return HighPriority }
|
func (t *ReadFileTask) Priority() Priority { return HighPriority }
|
||||||
func (t *ReadFileTask) TaskType() TaskType { return TypeReadFile }
|
func (t *ReadFileTask) TaskType() TaskType { return TypeReadFile }
|
||||||
func (t *ReadFileTask) TaskID() string { return t.taskID }
|
func (t *ReadFileTask) TaskID() string { return t.taskID }
|
||||||
func (t *ReadFileTask) DirPath() string { return filepath.Dir(t.Path) }
|
func (t *ReadFileTask) DirPath() string { return filepath.Dir(t.Path) }
|
||||||
func (t *ReadFileTask) Context() context.Context { return t.ctx }
|
func (t *ReadFileTask) Context() context.Context { return t.ctx }
|
||||||
func (t *ReadFileTask) Timeout() time.Duration { return 5 * time.Second }
|
func (t *ReadFileTask) Timeout() time.Duration { return 5 * time.Second }
|
||||||
func (t *ReadFileTask) Cancel() { if t.cancel != nil { t.cancel() } }
|
func (t *ReadFileTask) Cancel() {
|
||||||
|
if t.cancel != nil {
|
||||||
|
t.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SearchData is the payload of a completed SearchTask: the byte ranges
|
// SearchData is the payload of a completed SearchTask: the byte ranges
|
||||||
// [start, end) of every match of Query in the scanned Text, ascending and
|
// [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.
|
// unit; it is the standard library's two-way search.
|
||||||
func indexOf(s, sub string) int { return strings.Index(s, sub) }
|
func indexOf(s, sub string) int { return strings.Index(s, sub) }
|
||||||
|
|
||||||
func (t *SearchTask) Priority() Priority { return MediumPriority }
|
func (t *SearchTask) Priority() Priority { return MediumPriority }
|
||||||
func (t *SearchTask) TaskType() TaskType { return TypeSearch }
|
func (t *SearchTask) TaskType() TaskType { return TypeSearch }
|
||||||
func (t *SearchTask) TaskID() string { return t.taskID }
|
func (t *SearchTask) TaskID() string { return t.taskID }
|
||||||
func (t *SearchTask) DirPath() string { return "" }
|
func (t *SearchTask) DirPath() string { return "" }
|
||||||
func (t *SearchTask) Context() context.Context { return t.ctx }
|
func (t *SearchTask) Context() context.Context { return t.ctx }
|
||||||
func (t *SearchTask) Timeout() time.Duration { return 10 * time.Second }
|
func (t *SearchTask) Timeout() time.Duration { return 10 * time.Second }
|
||||||
func (t *SearchTask) Cancel() { if t.cancel != nil { t.cancel() } }
|
func (t *SearchTask) Cancel() {
|
||||||
|
if t.cancel != nil {
|
||||||
|
t.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WriteFileTask writes content to a file.
|
// WriteFileTask writes content to a file.
|
||||||
type WriteFileTask struct {
|
type WriteFileTask struct {
|
||||||
|
|
@ -626,13 +664,17 @@ func (t *WriteFileTask) Execute() Result {
|
||||||
return Result{TaskID: t.taskID, TaskType: TypeWriteFile, FilePath: t.Path, Success: true}
|
return Result{TaskID: t.taskID, TaskType: TypeWriteFile, FilePath: t.Path, Success: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *WriteFileTask) Priority() Priority { return LowPriority }
|
func (t *WriteFileTask) Priority() Priority { return LowPriority }
|
||||||
func (t *WriteFileTask) TaskType() TaskType { return TypeWriteFile }
|
func (t *WriteFileTask) TaskType() TaskType { return TypeWriteFile }
|
||||||
func (t *WriteFileTask) TaskID() string { return t.taskID }
|
func (t *WriteFileTask) TaskID() string { return t.taskID }
|
||||||
func (t *WriteFileTask) DirPath() string { return filepath.Dir(t.Path) }
|
func (t *WriteFileTask) DirPath() string { return filepath.Dir(t.Path) }
|
||||||
func (t *WriteFileTask) Context() context.Context { return t.ctx }
|
func (t *WriteFileTask) Context() context.Context { return t.ctx }
|
||||||
func (t *WriteFileTask) Timeout() time.Duration { return 5 * time.Second }
|
func (t *WriteFileTask) Timeout() time.Duration { return 5 * time.Second }
|
||||||
func (t *WriteFileTask) Cancel() { if t.cancel != nil { t.cancel() } }
|
func (t *WriteFileTask) Cancel() {
|
||||||
|
if t.cancel != nil {
|
||||||
|
t.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Mock File System (for testing/development) ---
|
// --- Mock File System (for testing/development) ---
|
||||||
// MockFS implements the pool.FileSystem interface for testing.
|
// MockFS implements the pool.FileSystem interface for testing.
|
||||||
|
|
@ -709,12 +751,12 @@ type mockFileInfo struct {
|
||||||
modTime time.Time
|
modTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *mockFileInfo) Name() string { return f.name }
|
func (f *mockFileInfo) Name() string { return f.name }
|
||||||
func (f *mockFileInfo) Size() int64 { return f.size }
|
func (f *mockFileInfo) Size() int64 { return f.size }
|
||||||
func (f *mockFileInfo) Mode() os.FileMode { return os.FileMode(f.mode) }
|
func (f *mockFileInfo) Mode() os.FileMode { return os.FileMode(f.mode) }
|
||||||
func (f *mockFileInfo) ModTime() time.Time { return f.modTime }
|
func (f *mockFileInfo) ModTime() time.Time { return f.modTime }
|
||||||
func (f *mockFileInfo) IsDir() bool { return false }
|
func (f *mockFileInfo) IsDir() bool { return false }
|
||||||
func (f *mockFileInfo) Sys() any { return nil }
|
func (f *mockFileInfo) Sys() any { return nil }
|
||||||
|
|
||||||
// mockDirEntry implements types.DirEntry for MockFS ReadDir.
|
// mockDirEntry implements types.DirEntry for MockFS ReadDir.
|
||||||
type mockDirEntry struct {
|
type mockDirEntry struct {
|
||||||
|
|
@ -722,8 +764,8 @@ type mockDirEntry struct {
|
||||||
isDir bool
|
isDir bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *mockDirEntry) Name() string { return e.name }
|
func (e *mockDirEntry) Name() string { return e.name }
|
||||||
func (e *mockDirEntry) IsDir() bool { return e.isDir }
|
func (e *mockDirEntry) IsDir() bool { return e.isDir }
|
||||||
func (e *mockDirEntry) Info() (os.FileInfo, error) {
|
func (e *mockDirEntry) Info() (os.FileInfo, error) {
|
||||||
return &mockFileInfo{name: e.name, size: 0}, nil
|
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
|
var _ FileSystem = (*MockFS)(nil) // Compile-time interface check
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,25 +68,25 @@ func (li *LineIndex) FindLogicalLineForByteOffset(byteOffset int) int {
|
||||||
if len(li.Offsets) == 0 {
|
if len(li.Offsets) == 0 {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Binary search to find the line that contains this byte offset
|
// Binary search to find the line that contains this byte offset
|
||||||
low, high := 0, len(li.Offsets)-1
|
low, high := 0, len(li.Offsets)-1
|
||||||
for low <= high {
|
for low <= high {
|
||||||
mid := (low + high) / 2
|
mid := (low + high) / 2
|
||||||
midOffset := int(li.Offsets[mid])
|
midOffset := int(li.Offsets[mid])
|
||||||
|
|
||||||
if byteOffset >= midOffset {
|
if byteOffset >= midOffset {
|
||||||
// This line starts at or before our byte offset
|
// This line starts at or before our byte offset
|
||||||
// Check if the next line starts after 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 {
|
if mid == len(li.Offsets)-1 || int(li.Offsets[mid+1]) > byteOffset {
|
||||||
return mid // Found the line
|
return mid // Found the line
|
||||||
}
|
|
||||||
low = mid + 1
|
|
||||||
} else {
|
|
||||||
high = mid - 1
|
|
||||||
}
|
}
|
||||||
|
low = mid + 1
|
||||||
|
} else {
|
||||||
|
high = mid - 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1 // Not found
|
return -1 // Not found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,15 @@ import (
|
||||||
// - Workers execute tasks and post results on resultChan
|
// - Workers execute tasks and post results on resultChan
|
||||||
// - Workers never access state directly
|
// - Workers never access state directly
|
||||||
type WorkerPool struct {
|
type WorkerPool struct {
|
||||||
highWorkChan chan Task
|
highWorkChan chan Task
|
||||||
lowWorkChan chan Task
|
lowWorkChan chan Task
|
||||||
resultChan chan Result
|
resultChan chan Result
|
||||||
workerCount int
|
workerCount int
|
||||||
stopOnce sync.Once
|
stopOnce sync.Once
|
||||||
stopChan chan struct{}
|
stopChan chan struct{}
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
taskCounter atomic.Int64
|
taskCounter atomic.Int64
|
||||||
running atomic.Bool
|
running atomic.Bool
|
||||||
|
|
||||||
// pendingTasks tracks in-flight tasks for cancellation by directory.
|
// pendingTasks tracks in-flight tasks for cancellation by directory.
|
||||||
pendingTasks map[string][]Task
|
pendingTasks map[string][]Task
|
||||||
|
|
|
||||||
|
|
@ -42,13 +42,13 @@ func (t *stubTask) Execute() Result {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *stubTask) Priority() Priority { return t.priority }
|
func (t *stubTask) Priority() Priority { return t.priority }
|
||||||
func (t *stubTask) TaskID() string { return t.id }
|
func (t *stubTask) TaskID() string { return t.id }
|
||||||
func (t *stubTask) TaskType() TaskType { return t.taskType }
|
func (t *stubTask) TaskType() TaskType { return t.taskType }
|
||||||
func (t *stubTask) DirPath() string { return "" }
|
func (t *stubTask) DirPath() string { return "" }
|
||||||
func (t *stubTask) Context() context.Context { return context.Background() }
|
func (t *stubTask) Context() context.Context { return context.Background() }
|
||||||
func (t *stubTask) Cancel() {}
|
func (t *stubTask) Cancel() {}
|
||||||
func (t *stubTask) Timeout() time.Duration { return 5 * time.Second }
|
func (t *stubTask) Timeout() time.Duration { return 5 * time.Second }
|
||||||
|
|
||||||
func TestWorkerPool_StartStop(t *testing.T) {
|
func TestWorkerPool_StartStop(t *testing.T) {
|
||||||
pool := NewWorkerPool(4)
|
pool := NewWorkerPool(4)
|
||||||
|
|
@ -672,9 +672,9 @@ func TestWorkerPool_ResultIsSuccess(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
result2 := Result{
|
result2 := Result{
|
||||||
TaskID: "test",
|
TaskID: "test",
|
||||||
Success: false,
|
Success: false,
|
||||||
Error: fmt.Errorf("error"),
|
Error: fmt.Errorf("error"),
|
||||||
}
|
}
|
||||||
if result2.IsSuccess() {
|
if result2.IsSuccess() {
|
||||||
t.Error("Result should not be successful")
|
t.Error("Result should not be successful")
|
||||||
|
|
@ -683,7 +683,7 @@ func TestWorkerPool_ResultIsSuccess(t *testing.T) {
|
||||||
|
|
||||||
func TestWorkerPool_ResultIsError(t *testing.T) {
|
func TestWorkerPool_ResultIsError(t *testing.T) {
|
||||||
result := Result{
|
result := Result{
|
||||||
TaskID: "test",
|
TaskID: "test",
|
||||||
Success: false,
|
Success: false,
|
||||||
Error: fmt.Errorf("error"),
|
Error: fmt.Errorf("error"),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ func TestEditorInitialLayout(t *testing.T) {
|
||||||
t.Fatalf("GoToEditor: %v", err)
|
t.Fatalf("GoToEditor: %v", err)
|
||||||
}
|
}
|
||||||
h.SendConfig(780, 1688)
|
h.SendConfig(780, 1688)
|
||||||
|
|
||||||
// Wait for a new frame after switching to editor page
|
// Wait for a new frame after switching to editor page
|
||||||
_, err := e2e.WaitForNewFrame(h, h.FrameCount(), 5*time.Second)
|
_, err := e2e.WaitForNewFrame(h, h.FrameCount(), 5*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ type TestScenario struct {
|
||||||
Setup func(*Harness)
|
Setup func(*Harness)
|
||||||
Actions []HarnessAction
|
Actions []HarnessAction
|
||||||
Assertions []FrameAssertion
|
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
|
Timeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -646,7 +646,7 @@ func TestRestore_LinePinHoldsAcrossLateWrapFeedback(t *testing.T) {
|
||||||
File: "/wrap.txt",
|
File: "/wrap.txt",
|
||||||
Cursor: 0,
|
Cursor: 0,
|
||||||
Scroll: float64(ui.Dp(500 * lh)), // the saving session's visual-space offset
|
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,
|
ScrollSub: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -248,7 +248,7 @@ func TestSortModeChangePreservesSearchFilter(t *testing.T) {
|
||||||
// When the sort mode changes, the position map changes,
|
// When the sort mode changes, the position map changes,
|
||||||
// so the *index* of the entry "Documents" (which matches "doc")
|
// so the *index* of the entry "Documents" (which matches "doc")
|
||||||
// will change!
|
// will change!
|
||||||
|
|
||||||
// Let's print the entries to see if they are still correct,
|
// Let's print the entries to see if they are still correct,
|
||||||
// ignoring the index values themselves.
|
// ignoring the index values themselves.
|
||||||
// (Owner-side snapshot: all reads happen on the logic goroutine.)
|
// (Owner-side snapshot: all reads happen on the logic goroutine.)
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,14 @@ package ui
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Standard dimensions in Dp
|
// Standard dimensions in Dp
|
||||||
StatusBarLineHeight = Dp(24)
|
StatusBarLineHeight = Dp(24)
|
||||||
StatusBarFilenameLine = Dp(24)
|
StatusBarFilenameLine = Dp(24)
|
||||||
StatusBarIconsLine = Dp(24)
|
StatusBarIconsLine = Dp(24)
|
||||||
BottomBarHeight = Dp(24)
|
BottomBarHeight = Dp(24)
|
||||||
IconSize = Dp(24)
|
IconSize = Dp(24)
|
||||||
IconGap = Dp(36)
|
IconGap = Dp(36)
|
||||||
Padding = Dp(8)
|
Padding = Dp(8)
|
||||||
ButtonPadding = Dp(8)
|
ButtonPadding = Dp(8)
|
||||||
)
|
)
|
||||||
|
|
||||||
// LineHeightScale is the baseline-to-baseline spacing multiplier for editor text.
|
// LineHeightScale is the baseline-to-baseline spacing multiplier for editor text.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user