fix: integrate browser-to-editor file loading and remove sample text
This commit is contained in:
parent
2c3702f421
commit
ec87e95b35
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"pad/internal/io/pool"
|
||||||
"pad/internal/ui"
|
"pad/internal/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -76,5 +77,8 @@ func HandleBrowserTap(bm *BrowserManager, s *BrowserState, index int) {
|
||||||
// Open file
|
// Open file
|
||||||
s.SelectedIndex = index
|
s.SelectedIndex = index
|
||||||
ui.OpenFile(entry.Path)
|
ui.OpenFile(entry.Path)
|
||||||
|
// Dispatch ReadFileTask to the worker pool
|
||||||
|
task := pool.NewReadFileTask(entry.Path, bm.fs)
|
||||||
|
bm.workerPool.Dispatch(task)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ type Logic struct {
|
||||||
lastLineYChan chan int // last line Y (Dp, sent as int) feedback from renderer
|
lastLineYChan chan int // last line Y (Dp, sent as int) feedback from renderer
|
||||||
resultChan chan ResultEvent
|
resultChan chan ResultEvent
|
||||||
searchQueryChan chan string // search text updates from main goroutine
|
searchQueryChan chan string // search text updates from main goroutine
|
||||||
|
openFileChan chan string // Added for asynchronous file loading
|
||||||
workerPool *pool.WorkerPool
|
workerPool *pool.WorkerPool
|
||||||
mockFS *mock.FileSystem
|
mockFS *mock.FileSystem
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
@ -83,6 +84,7 @@ func NewLogic() *Logic {
|
||||||
lastLineYChan: make(chan int),
|
lastLineYChan: make(chan int),
|
||||||
resultChan: make(chan ResultEvent),
|
resultChan: make(chan ResultEvent),
|
||||||
searchQueryChan: make(chan string),
|
searchQueryChan: make(chan string),
|
||||||
|
openFileChan: make(chan string), // Initialized
|
||||||
workerPool: wp,
|
workerPool: wp,
|
||||||
mockFS: mockFS,
|
mockFS: mockFS,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
|
|
@ -161,6 +163,9 @@ func (l *Logic) Run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
l.frameChan <- l.state.layout(l.browserManager)
|
l.frameChan <- l.state.layout(l.browserManager)
|
||||||
|
case path := <-l.openFileChan:
|
||||||
|
// Dispatch ReadFileTask to worker pool
|
||||||
|
l.workerPool.Dispatch(pool.NewReadFileTask(path, l.mockFS))
|
||||||
case res := <-l.workerPool.ResultChan():
|
case res := <-l.workerPool.ResultChan():
|
||||||
l.handleWorkerResult(res)
|
l.handleWorkerResult(res)
|
||||||
case <-l.resultChan:
|
case <-l.resultChan:
|
||||||
|
|
@ -173,6 +178,12 @@ func (l *Logic) Run() {
|
||||||
func (l *Logic) handleWorkerResult(res pool.Result) {
|
func (l *Logic) handleWorkerResult(res pool.Result) {
|
||||||
if res.IsBrowserResult() {
|
if res.IsBrowserResult() {
|
||||||
l.browserManager.HandleResult(res)
|
l.browserManager.HandleResult(res)
|
||||||
|
} else if res.TaskType == pool.TypeReadFile {
|
||||||
|
if res.Success {
|
||||||
|
if content, ok := res.Data.([]byte); ok {
|
||||||
|
l.state.ActiveFileContent = string(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
l.frameChan <- l.state.layout(l.browserManager)
|
l.frameChan <- l.state.layout(l.browserManager)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,100 +37,100 @@ func populateMockFileSystem(fs *mock.FileSystem) {
|
||||||
|
|
||||||
// --- Root files ---
|
// --- Root files ---
|
||||||
rootFiles := []struct {
|
rootFiles := []struct {
|
||||||
name string
|
name string
|
||||||
size int64
|
content string
|
||||||
days int
|
days int
|
||||||
}{
|
}{
|
||||||
{"README.md", 2048, -30},
|
{"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", 512, -60},
|
{"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", 1024, -15},
|
{"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", 4096, -7},
|
{"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", 128, -90},
|
{".gitignore", "*.log\n.pad/\n.git/\n*.tmp\n", -90},
|
||||||
}
|
}
|
||||||
for _, f := range rootFiles {
|
for _, f := range rootFiles {
|
||||||
fs.AddFile("/"+f.name, make([]byte, f.size), baseTime.AddDate(0, 0, f.days))
|
fs.AddFile("/"+f.name, []byte(f.content), baseTime.AddDate(0, 0, f.days))
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Documents/Work files ---
|
// --- Documents/Work files ---
|
||||||
workFiles := []struct {
|
workFiles := []struct {
|
||||||
name string
|
name string
|
||||||
size int64
|
content string
|
||||||
days int
|
days int
|
||||||
}{
|
}{
|
||||||
{"Q1_Report.docx", 524288, -45},
|
{"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", 614400, -14},
|
{"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", 262144, -30},
|
{"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", 8192, -1},
|
{"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", 1048576, -21},
|
{"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 {
|
for _, f := range workFiles {
|
||||||
fs.AddFile("/Documents/Work/"+f.name, make([]byte, f.size), baseTime.AddDate(0, 0, f.days))
|
fs.AddFile("/Documents/Work/"+f.name, []byte(f.content), baseTime.AddDate(0, 0, f.days))
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Documents/Personal files ---
|
// --- Documents/Personal files ---
|
||||||
personalFiles := []struct {
|
personalFiles := []struct {
|
||||||
name string
|
name string
|
||||||
size int64
|
content string
|
||||||
days int
|
days int
|
||||||
}{
|
}{
|
||||||
{"Resume.pdf", 32768, -60},
|
{"Resume.pdf", "Greg Pomerantz\nGo & Graphics Software Engineer\n\nSkills: Go, Gio, OpenGL, Concurrency, Virtualization\n", -60},
|
||||||
{"Tax_Return_2025.pdf", 131072, -90},
|
{"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", 4096, -120},
|
{"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", 2048, -5},
|
{"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 {
|
for _, f := range personalFiles {
|
||||||
fs.AddFile("/Documents/Personal/"+f.name, make([]byte, f.size), baseTime.AddDate(0, 0, f.days))
|
fs.AddFile("/Documents/Personal/"+f.name, []byte(f.content), baseTime.AddDate(0, 0, f.days))
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Projects/pad source files ---
|
// --- Projects/pad source files ---
|
||||||
padFiles := []struct {
|
padFiles := []struct {
|
||||||
name string
|
name string
|
||||||
size int64
|
content string
|
||||||
days int
|
days int
|
||||||
}{
|
}{
|
||||||
{"main.go", 3072, -2},
|
{"main.go", "package main\n\nfunc main() {\n\t// Startup logic\n}\n", -2},
|
||||||
{"state.go", 4096, -2},
|
{"state.go", "package editor\n\n// Embedded state engine\n", -2},
|
||||||
{"logic.go", 5120, -1},
|
{"logic.go", "package editor\n\n// Concurrency logic loop\n", -1},
|
||||||
{"browser.go", 2048, -7},
|
{"browser.go", "package browser\n\n// Core directory browser\n", -7},
|
||||||
{"types.go", 1536, -7},
|
{"types.go", "package browser\n\n// Struct definitions\n", -7},
|
||||||
{"index.go", 3584, -7},
|
{"index.go", "package browser\n\n// Cached indices\n", -7},
|
||||||
{"layout.go", 2560, -5},
|
{"layout.go", "package browser\n\n// Layout tree creation\n", -5},
|
||||||
{"handlers.go", 1024, -3},
|
{"handlers.go", "package browser\n\n// Event handling\n", -3},
|
||||||
{"go.mod", 256, -30},
|
{"go.mod", "module pad\n\ngo 1.24.2\n", -30},
|
||||||
{"go.sum", 512, -30},
|
{"go.sum", "gioui.org v0.9.0 h1:...\n", -30},
|
||||||
}
|
}
|
||||||
for _, f := range padFiles {
|
for _, f := range padFiles {
|
||||||
fs.AddFile("/Projects/pad/"+f.name, make([]byte, f.size), baseTime.AddDate(0, 0, f.days))
|
fs.AddFile("/Projects/pad/"+f.name, []byte(f.content), baseTime.AddDate(0, 0, f.days))
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Pictures/Vacation files ---
|
// --- Pictures/Vacation files ---
|
||||||
vacationFiles := []struct {
|
vacationFiles := []struct {
|
||||||
name string
|
name string
|
||||||
size int64
|
content string
|
||||||
days int
|
days int
|
||||||
}{
|
}{
|
||||||
{"IMG_0001.jpg", 3145728, -180},
|
{"IMG_0001.jpg", "[Binary JPEG Image Data Mock]", -180},
|
||||||
{"IMG_0002.jpg", 2621440, -180},
|
{"IMG_0002.jpg", "[Binary JPEG Image Data Mock]", -180},
|
||||||
{"IMG_0003.jpg", 4194304, -179},
|
{"IMG_0003.jpg", "[Binary JPEG Image Data Mock]", -179},
|
||||||
{"IMG_0004.jpg", 3670016, -179},
|
{"IMG_0004.jpg", "[Binary JPEG Image Data Mock]", -179},
|
||||||
{"IMG_0005.jpg", 2097152, -178},
|
{"IMG_0005.jpg", "[Binary JPEG Image Data Mock]", -178},
|
||||||
}
|
}
|
||||||
for _, f := range vacationFiles {
|
for _, f := range vacationFiles {
|
||||||
fs.AddFile("/Pictures/Vacation/"+f.name, make([]byte, f.size), baseTime.AddDate(0, 0, f.days))
|
fs.AddFile("/Pictures/Vacation/"+f.name, []byte(f.content), baseTime.AddDate(0, 0, f.days))
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Downloads files ---
|
// --- Downloads files ---
|
||||||
downloads := []struct {
|
downloads := []struct {
|
||||||
name string
|
name string
|
||||||
size int64
|
content string
|
||||||
days int
|
days int
|
||||||
}{
|
}{
|
||||||
{"installer.dmg", 104857600, -10},
|
{"installer.dmg", "[DMG Disk Image Mock - 100MB]", -10},
|
||||||
{"archive.tar.gz", 52428800, -20},
|
{"archive.tar.gz", "[Compressed TAR GZ Archive Mock - 50MB]", -20},
|
||||||
{"patch.diff", 8192, -3},
|
{"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 {
|
for _, f := range downloads {
|
||||||
fs.AddFile("/Downloads/"+f.name, make([]byte, f.size), baseTime.AddDate(0, 0, f.days))
|
fs.AddFile("/Downloads/"+f.name, []byte(f.content), baseTime.AddDate(0, 0, f.days))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,27 +9,6 @@ func init() {
|
||||||
ui.OpenFile = OpenFile
|
ui.OpenFile = OpenFile
|
||||||
}
|
}
|
||||||
|
|
||||||
// SampleText is a static lorem-ipsum text used for display-only testing.
|
|
||||||
// Roughly 3 KB, filling a few pages of the editor.
|
|
||||||
const SampleText = `
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
|
||||||
|
|
||||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architectus qui exercitationem ullam corporis suscipit dolorum et saepe fugiat. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.
|
|
||||||
|
|
||||||
Neque porro consequatur autem velbeat viciis quam autem voluptas minus odio voluptatem. Quis autem vel natus autem sequis dolor tempor. Ut enim minima voluptate et quis autem sequia dolor tempor. Sed autem quia dolor sed consequat et voluptate autem sequia dolor tempor. Nemo enim sed consequat et voluptate autem sequia dolor tempor.
|
|
||||||
|
|
||||||
The quick brown fox jumps over the lazy dog. This is a short line to test how the editor handles lines that are much shorter than the wrap width. Some lines will be very long and wrap many times, while others fit on a single line easily.
|
|
||||||
|
|
||||||
Attitulam velis, te sum quae dolorem sequia dolor tempor. Ut enim minima voluptate et quis autem sequia dolor tempor. Sed autem quia dolor sed consequat et voluptate autem sequia dolor tempor. Nemo enim sed consequat et voluptate autem sequia dolor tempor.
|
|
||||||
|
|
||||||
There are also words that are extremelylonganddonothaveanywhitespacesinwhichcasewithheuristicswrappingthewholewordwilloverflowthewrapwidthratherthanbeingbrokenmidcharacter. This is expected behavior for a text editor — long identifiers, URLs, or concatenated text should stay on one line even if they exceed the viewport width.
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
|
||||||
|
|
||||||
Two words: antidisestablishmentarianism and floccinauciniphilolipaecpelagioodontophont索菲乌斯 are examples of long words that may overflow the wrap width.
|
|
||||||
|
|
||||||
In conclusion, this sample text provides a variety of line lengths, word lengths, and paragraph structures to exercise the word wrap implementation. Short lines, long lines, very long words, normal words, empty lines — all present here.`
|
|
||||||
|
|
||||||
// EditorFontSize is the font size used for editor text.
|
// EditorFontSize is the font size used for editor text.
|
||||||
const EditorFontSize = 14 // unit.Sp
|
const EditorFontSize = 14 // unit.Sp
|
||||||
|
|
||||||
|
|
@ -73,14 +52,16 @@ type State struct {
|
||||||
// Browser state (directly embedded per architecture §8)
|
// Browser state (directly embedded per architecture §8)
|
||||||
Browser browser.BrowserState // Embedded, not a pointer
|
Browser browser.BrowserState // Embedded, not a pointer
|
||||||
// Editor state
|
// Editor state
|
||||||
ActiveFilename string // filename shown in editor status bar
|
ActiveFilename string // filename shown in editor status bar
|
||||||
|
ActiveFileContent string // content of the active file
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewState() *State {
|
func NewState() *State {
|
||||||
return &State{
|
return &State{
|
||||||
scale: 1.0,
|
scale: 1.0,
|
||||||
page: EditorPage,
|
page: BrowserPage,
|
||||||
Browser: *browser.NewBrowserState(),
|
Browser: *browser.NewBrowserState(),
|
||||||
|
ActiveFileContent: "Select a file to edit...", // Empty or placeholder initially
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,6 +147,7 @@ func GoToEditor(data any) {
|
||||||
// data is the filename string from the browser list.
|
// data is the filename string from the browser list.
|
||||||
func OpenFile(data any) {
|
func OpenFile(data any) {
|
||||||
TheState.ActiveFilename = data.(string)
|
TheState.ActiveFilename = data.(string)
|
||||||
|
TheState.ScrollOffset = 0 // Reset editor scroll to top when opening a new file
|
||||||
TheState.page = EditorPage
|
TheState.page = EditorPage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -257,7 +239,7 @@ func EditorLayout(screenWidth, screenHeight ui.Dp, wordWrap bool) []ui.Element {
|
||||||
|
|
||||||
editor := ui.NewTextField(
|
editor := ui.NewTextField(
|
||||||
"editor_text",
|
"editor_text",
|
||||||
SampleText,
|
TheState.ActiveFileContent,
|
||||||
editorRegion,
|
editorRegion,
|
||||||
editorRegion.W,
|
editorRegion.W,
|
||||||
TheState.ScrollOffset,
|
TheState.ScrollOffset,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user