- internal/test/e2e/: FrameCapture, Harness, ElementAssertions, helpers - internal/editor/logic.go: Add done channel and Done() method for graceful shutdown - internal/editor/mock_setup.go: Mock filesystem for tests - internal/browser/: Browser layout and search logic - internal/io/: Worker pool for async tasks - Update architecture docs and spec
244 lines
6.0 KiB
Go
244 lines
6.0 KiB
Go
package editor
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"pad/internal/browser"
|
|
"pad/internal/io/pool/mock"
|
|
)
|
|
|
|
// populateMockFileSystem seeds the mock filesystem with sample data for testing.
|
|
// Creates a realistic directory structure with subdirectories, various file types,
|
|
// and varied sizes/dates to exercise the browser functionality.
|
|
func populateMockFileSystem(fs *mock.FileSystem) {
|
|
baseTime := time.Date(2026, 5, 15, 10, 0, 0, 0, time.UTC)
|
|
|
|
// --- Root directories ---
|
|
dirs := []string{
|
|
"/Documents",
|
|
"/Pictures",
|
|
"/Music",
|
|
"/Downloads",
|
|
"/Projects",
|
|
"/Projects/pad",
|
|
"/Projects/goplus",
|
|
"/Documents/Work",
|
|
"/Documents/Personal",
|
|
"/Pictures/Vacation",
|
|
}
|
|
for i, d := range dirs {
|
|
fs.AddDir(d, baseTime.AddDate(0, 0, -i))
|
|
}
|
|
|
|
// --- Root files ---
|
|
rootFiles := []struct {
|
|
name string
|
|
size int64
|
|
days int
|
|
}{
|
|
{"README.md", 2048, -30},
|
|
{"config.yaml", 512, -60},
|
|
{"notes.txt", 1024, -15},
|
|
{"setup.log", 4096, -7},
|
|
{".gitignore", 128, -90},
|
|
}
|
|
for _, f := range rootFiles {
|
|
fs.AddFile("/"+f.name, make([]byte, f.size), baseTime.AddDate(0, 0, f.days))
|
|
}
|
|
|
|
// --- Documents/Work files ---
|
|
workFiles := []struct {
|
|
name string
|
|
size int64
|
|
days int
|
|
}{
|
|
{"Q1_Report.docx", 524288, -45},
|
|
{"Q2_Report.docx", 614400, -14},
|
|
{"Budget_2026.xlsx", 262144, -30},
|
|
{"Meeting_Notes.txt", 8192, -1},
|
|
{"Presentation.pptx", 1048576, -21},
|
|
}
|
|
for _, f := range workFiles {
|
|
fs.AddFile("/Documents/Work/"+f.name, make([]byte, f.size), baseTime.AddDate(0, 0, f.days))
|
|
}
|
|
|
|
// --- Documents/Personal files ---
|
|
personalFiles := []struct {
|
|
name string
|
|
size int64
|
|
days int
|
|
}{
|
|
{"Resume.pdf", 32768, -60},
|
|
{"Tax_Return_2025.pdf", 131072, -90},
|
|
{"Recipe_Collection.txt", 4096, -120},
|
|
{"Letter_to_Friend.txt", 2048, -5},
|
|
}
|
|
for _, f := range personalFiles {
|
|
fs.AddFile("/Documents/Personal/"+f.name, make([]byte, f.size), baseTime.AddDate(0, 0, f.days))
|
|
}
|
|
|
|
// --- Projects/pad source files ---
|
|
padFiles := []struct {
|
|
name string
|
|
size int64
|
|
days int
|
|
}{
|
|
{"main.go", 3072, -2},
|
|
{"state.go", 4096, -2},
|
|
{"logic.go", 5120, -1},
|
|
{"browser.go", 2048, -7},
|
|
{"types.go", 1536, -7},
|
|
{"index.go", 3584, -7},
|
|
{"layout.go", 2560, -5},
|
|
{"handlers.go", 1024, -3},
|
|
{"go.mod", 256, -30},
|
|
{"go.sum", 512, -30},
|
|
}
|
|
for _, f := range padFiles {
|
|
fs.AddFile("/Projects/pad/"+f.name, make([]byte, f.size), baseTime.AddDate(0, 0, f.days))
|
|
}
|
|
|
|
// --- Pictures/Vacation files ---
|
|
vacationFiles := []struct {
|
|
name string
|
|
size int64
|
|
days int
|
|
}{
|
|
{"IMG_0001.jpg", 3145728, -180},
|
|
{"IMG_0002.jpg", 2621440, -180},
|
|
{"IMG_0003.jpg", 4194304, -179},
|
|
{"IMG_0004.jpg", 3670016, -179},
|
|
{"IMG_0005.jpg", 2097152, -178},
|
|
}
|
|
for _, f := range vacationFiles {
|
|
fs.AddFile("/Pictures/Vacation/"+f.name, make([]byte, f.size), baseTime.AddDate(0, 0, f.days))
|
|
}
|
|
|
|
// --- Downloads files ---
|
|
downloads := []struct {
|
|
name string
|
|
size int64
|
|
days int
|
|
}{
|
|
{"installer.dmg", 104857600, -10},
|
|
{"archive.tar.gz", 52428800, -20},
|
|
{"patch.diff", 8192, -3},
|
|
}
|
|
for _, f := range downloads {
|
|
fs.AddFile("/Downloads/"+f.name, make([]byte, f.size), baseTime.AddDate(0, 0, f.days))
|
|
}
|
|
}
|
|
|
|
// sortModeKey converts a browser.SortMode to its JSON key string.
|
|
func sortModeKey(mode browser.SortMode) string {
|
|
switch mode {
|
|
case 0: // SortModeNameAsc
|
|
return "name_asc"
|
|
case 1: // SortModeNameDesc
|
|
return "name_desc"
|
|
case 2: // SortModeDateAsc
|
|
return "date_asc"
|
|
case 3: // SortModeDateDesc
|
|
return "date_desc"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// modeCount returns the total number of sort modes.
|
|
func modeCount() int {
|
|
return 4
|
|
}
|
|
|
|
// buildBrowserIndex converts mock DirEntry results into a browser.DirectoryIndex
|
|
// with pre-computed position maps for all sort modes.
|
|
func buildBrowserIndex(entries []mock.DirEntry) *browser.DirectoryIndex {
|
|
var browserEntries []browser.Entry
|
|
for _, e := range entries {
|
|
info, err := e.Info()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
browserEntries = append(browserEntries, browser.Entry{
|
|
Path: e.Name(),
|
|
Name: e.Name(),
|
|
Size: info.Size(),
|
|
ModTime: info.ModTime(),
|
|
IsDir: info.IsDir(),
|
|
})
|
|
}
|
|
|
|
// Build position maps for all sort modes
|
|
sortOrders := make(map[string][]int)
|
|
for mode := browser.SortMode(0); mode < browser.SortMode(modeCount()); mode++ {
|
|
key := sortModeKey(mode)
|
|
if key != "" {
|
|
sortOrders[key] = buildPositionMap(browserEntries, mode)
|
|
}
|
|
}
|
|
|
|
return &browser.DirectoryIndex{
|
|
Path: "/",
|
|
EntryCount: len(browserEntries),
|
|
Entries: browserEntries,
|
|
SortOrders: sortOrders,
|
|
}
|
|
}
|
|
|
|
// buildPositionMap creates a sorted index → raw index mapping.
|
|
func buildPositionMap(entries []browser.Entry, mode browser.SortMode) []int {
|
|
n := len(entries)
|
|
indices := make([]int, n)
|
|
for i := range indices {
|
|
indices[i] = i
|
|
}
|
|
|
|
// Sort indices based on entry comparison
|
|
cmp := comparator(mode)
|
|
sort.SliceStable(indices, func(i, j int) bool {
|
|
a, b := entries[indices[i]], entries[indices[j]]
|
|
return cmp(a, b) < 0
|
|
})
|
|
|
|
return indices
|
|
}
|
|
|
|
// comparator returns a comparison function for the given SortMode.
|
|
// Returns negative if a < b, zero if equal, positive if a > b.
|
|
func comparator(mode browser.SortMode) func(a, b browser.Entry) int {
|
|
switch mode {
|
|
case 0: // SortModeNameAsc
|
|
return func(a, b browser.Entry) int {
|
|
if c := strings.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name)); c != 0 {
|
|
return c
|
|
}
|
|
return strings.Compare(a.Name, b.Name)
|
|
}
|
|
case 1: // SortModeNameDesc
|
|
return func(a, b browser.Entry) int {
|
|
if c := strings.Compare(strings.ToLower(b.Name), strings.ToLower(a.Name)); c != 0 {
|
|
return c
|
|
}
|
|
return strings.Compare(b.Name, a.Name)
|
|
}
|
|
case 2: // SortModeDateAsc
|
|
return func(a, b browser.Entry) int {
|
|
if c := a.ModTime.Compare(b.ModTime); c != 0 {
|
|
return c
|
|
}
|
|
return strings.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name))
|
|
}
|
|
case 3: // SortModeDateDesc
|
|
return func(a, b browser.Entry) int {
|
|
if c := b.ModTime.Compare(a.ModTime); c != 0 {
|
|
return c
|
|
}
|
|
return strings.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name))
|
|
}
|
|
default:
|
|
return func(a, b browser.Entry) int { return 0 }
|
|
}
|
|
}
|