Selection handles now track the finger 1:1 (anchor grab point + displacement) instead of snapping by whole lines, and crossing the opposite handle flips the selection (native behaviour) instead of clearing it. Caret and tap/handle line resolution use VisualLineStarts instead of the min-Y baseline: the window's first visual line may be an empty line with no recorded glyphs, which used to draw boundary carets one line too low per leading empty line and land taps/dragged handles one line below the finger. New exported ui.CaretPoint centralises byte->insertion-point mapping. The off-screen caret no longer clamps to the window edge: EditorLayout ships the true (possibly negative / past-end) window-relative cursor and the renderer skips the caret when the cursor is outside the shaped window, so scrolling past the caret no longer makes it jump onto the top/bottom line. IME/router replay fixes: key.FocusCmd is issued only on a focus transition (a per-frame no-op still takes the immediate-command path and re-queues all pointer events), and the key.SelectionCmd IME sync is deferred while a handle drag is in progress (each push re-injected the drag into every gesture). Handle drags forward only Grabbed events; a tap inside a handle grab box is a no-op. Also: key.FocusEvent no longer logs as unexpected in main; dead code removed (worker taskWrapper, browser applyXxxResult stubs, scrollIndex, mock_setup sortModeKey/lineSpan helpers); mock FileSystem.ListPaths prefix match uses strings.HasPrefix; build scripts run the new scripts/check.sh static gate (go vet + staticcheck). Tests: caret_point_test, touch_selection updates (flip/empty-line cases), off-window caret e2e, selection drag e2e grab step.
138 lines
6.1 KiB
Go
138 lines
6.1 KiB
Go
package editor
|
|
|
|
import (
|
|
"time"
|
|
|
|
"pad/internal/io/pool"
|
|
"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 pool.FileSystem) {
|
|
mfs, ok := fs.(*mock.FileSystem)
|
|
if !ok {
|
|
panic("populateMockFileSystem requires a *mock.FileSystem")
|
|
}
|
|
baseTime := time.Date(2026, 5, 15, 10, 0, 0, 0, time.UTC)
|
|
|
|
// Create root directory - required for ReadDir("/") to succeed
|
|
mfs.AddDir("/", baseTime)
|
|
|
|
// --- Root directories ---
|
|
dirs := []string{
|
|
"/Documents",
|
|
"/Pictures",
|
|
"/Music",
|
|
"/Downloads",
|
|
"/Projects",
|
|
"/Projects/pad",
|
|
"/Projects/goplus",
|
|
"/Documents/Work",
|
|
"/Documents/Personal",
|
|
"/Pictures/Vacation",
|
|
}
|
|
for i, d := range dirs {
|
|
mfs.AddDir(d, baseTime.AddDate(0, 0, -i))
|
|
}
|
|
|
|
// --- Root files ---
|
|
rootFiles := []struct {
|
|
name string
|
|
content string
|
|
days int
|
|
}{
|
|
{"README.md", "# Pad Text Editor\n\nWelcome to Pad, a high-performance text editor built in Go and Gio.\nThis is a mock file loaded from the in-memory mock filesystem.\n\n## Features\n- Virtualized scroll list\n- Fast case-insensitive search\n- Pre-sorted directory indexing\n", -30},
|
|
{"config.yaml", "editor:\n font_size: 14\n line_height: 1.2\n word_wrap: true\ntheme:\n background: \"#ffffff\"\n foreground: \"#000000\"\n", -60},
|
|
{"notes.txt", "Pad Browser Implementation Todo:\n1. Implement directory navigation (Done)\n2. Implement file tapping to open in editor (Done)\n3. Implement external change detection\n4. Add alphabetical index sidebar\n", -15},
|
|
{"setup.log", "2026-06-01 08:00:00 [INFO] Initializing Pad system...\n2026-06-01 08:00:01 [INFO] Mock filesystem populated successfully.\n2026-06-01 08:00:02 [INFO] Worker pool started with 4 goroutines.\n2026-06-01 08:00:03 [INFO] Startup complete, showing browser page.\n", -7},
|
|
{".gitignore", "*.log\n.pad/\n.git/\n*.tmp\n", -90},
|
|
}
|
|
for _, f := range rootFiles {
|
|
mfs.AddFile("/"+f.name, []byte(f.content), baseTime.AddDate(0, 0, f.days))
|
|
}
|
|
|
|
// --- Documents/Work files ---
|
|
workFiles := []struct {
|
|
name string
|
|
content string
|
|
days int
|
|
}{
|
|
{"Q1_Report.docx", "Pad Word Document Mock\n\nQuarter 1 Performance Report:\nOverall efficiency increased by 15% following implementation of channel-based thread communication.\n", -45},
|
|
{"Q2_Report.docx", "Pad Word Document Mock\n\nQuarter 2 Planning:\nTargeting zero-alloc layout computations and sub-16ms frame times across all devices.\n", -14},
|
|
{"Budget_2026.xlsx", "Pad Spreadsheet Mock\n\nQ1 Expenses: $12,450.00\nQ2 Budgeted: $15,000.00\nTotal Project Allocation: $50,000.00\n", -30},
|
|
{"Meeting_Notes.txt", "Team Sync Notes - June 1, 2026\n\nAttendees: Greg, Agent\n- Solved scroll clamping with search query active.\n- Wired ListView taps to HandleBrowserTap.\n- Dispatched ReadFileTask to render file contents dynamically.\n", -1},
|
|
{"Presentation.pptx", "Pad Presentation Mock\n\nSlide 1: Title - No Practical Limits\nSlide 2: Virtualization is the key\nSlide 3: Single-Owner thread design\n", -21},
|
|
}
|
|
for _, f := range workFiles {
|
|
mfs.AddFile("/Documents/Work/"+f.name, []byte(f.content), baseTime.AddDate(0, 0, f.days))
|
|
}
|
|
|
|
// --- Documents/Personal files ---
|
|
personalFiles := []struct {
|
|
name string
|
|
content string
|
|
days int
|
|
}{
|
|
{"Resume.pdf", "Greg Pomerantz\nGo & Graphics Software Engineer\n\nSkills: Go, Gio, OpenGL, Concurrency, Virtualization\n", -60},
|
|
{"Tax_Return_2025.pdf", "Form 1040 Mock\n\nAdjusted Gross Income: $120,000.00\nTotal Tax Due: $18,450.00\nRefund Owed: $1,200.00\n", -90},
|
|
{"Recipe_Collection.txt", "Classic Pancakes:\n- 1 cup flour\n- 2 tbsp sugar\n- 1 tbsp baking powder\n- 1/2 tsp salt\n- 1 cup milk\n- 1 egg\n- 2 tbsp melted butter\n", -120},
|
|
{"Letter_to_Friend.txt", "Hey friend,\n\nI've been working on a really cool Go text editor called Pad. It uses Gio for rendering and handles massive files without blocking the GUI thread. Talk soon!\n", -5},
|
|
}
|
|
for _, f := range personalFiles {
|
|
mfs.AddFile("/Documents/Personal/"+f.name, []byte(f.content), baseTime.AddDate(0, 0, f.days))
|
|
}
|
|
|
|
// --- Projects/pad source files ---
|
|
padFiles := []struct {
|
|
name string
|
|
content string
|
|
days int
|
|
}{
|
|
{"main.go", "package main\n\nfunc main() {\n\t// Startup logic\n}\n", -2},
|
|
{"state.go", "package editor\n\n// Embedded state engine\n", -2},
|
|
{"logic.go", "package editor\n\n// Concurrency logic loop\n", -1},
|
|
{"browser.go", "package browser\n\n// Core directory browser\n", -7},
|
|
{"types.go", "package browser\n\n// Struct definitions\n", -7},
|
|
{"index.go", "package browser\n\n// Cached indices\n", -7},
|
|
{"layout.go", "package browser\n\n// Layout tree creation\n", -5},
|
|
{"handlers.go", "package browser\n\n// Event handling\n", -3},
|
|
{"go.mod", "module pad\n\ngo 1.24.2\n", -30},
|
|
{"go.sum", "gioui.org v0.9.0 h1:...\n", -30},
|
|
}
|
|
for _, f := range padFiles {
|
|
mfs.AddFile("/Projects/pad/"+f.name, []byte(f.content), baseTime.AddDate(0, 0, f.days))
|
|
}
|
|
|
|
// --- Pictures/Vacation files ---
|
|
vacationFiles := []struct {
|
|
name string
|
|
content string
|
|
days int
|
|
}{
|
|
{"IMG_0001.jpg", "[Binary JPEG Image Data Mock]", -180},
|
|
{"IMG_0002.jpg", "[Binary JPEG Image Data Mock]", -180},
|
|
{"IMG_0003.jpg", "[Binary JPEG Image Data Mock]", -179},
|
|
{"IMG_0004.jpg", "[Binary JPEG Image Data Mock]", -179},
|
|
{"IMG_0005.jpg", "[Binary JPEG Image Data Mock]", -178},
|
|
}
|
|
for _, f := range vacationFiles {
|
|
mfs.AddFile("/Pictures/Vacation/"+f.name, []byte(f.content), baseTime.AddDate(0, 0, f.days))
|
|
}
|
|
|
|
// --- Downloads files ---
|
|
downloads := []struct {
|
|
name string
|
|
content string
|
|
days int
|
|
}{
|
|
{"installer.dmg", "[DMG Disk Image Mock - 100MB]", -10},
|
|
{"archive.tar.gz", "[Compressed TAR GZ Archive Mock - 50MB]", -20},
|
|
{"patch.diff", "--- a/internal/browser/handlers.go\n+++ b/internal/browser/handlers.go\n@@ -65,3 +65,3 @@\n-bm.NavigateTo(newPath)\n+bm.NavigateTo(entry.Path)\n", -3},
|
|
}
|
|
for _, f := range downloads {
|
|
mfs.AddFile("/Downloads/"+f.name, []byte(f.content), baseTime.AddDate(0, 0, f.days))
|
|
}
|
|
}
|