Bug fixes.
This commit is contained in:
parent
0dac354f2a
commit
bb55b4ab81
|
|
@ -1,4 +1,9 @@
|
||||||
This file lists descriptions of outstanding bugs. Bugs are separated by empty lines.
|
This file lists descriptions of outstanding bugs. Bugs are separated by empty lines.
|
||||||
|
|
||||||
On the BrowserPage, when I enter a search that limits the list of files, clicking on a file
|
If I move the cursor (using the arrow keys) to the right side of the screen in a word-wrapped
|
||||||
delivers teh wrong file to the editor page.
|
line, the cursor does not move to the next line right away, instead it moves through whitespace on
|
||||||
|
the right side of the wrapped line, sometimes moving off screen. Also in a word wrapped file, moving
|
||||||
|
the cursor to the right does not move all the way down to the end of the file, it stops at some
|
||||||
|
point before the end. Also when I change the screen width to change the word wrap, the cursor does
|
||||||
|
not stay at the same position in the file but instead tries to stay in the same screen position,
|
||||||
|
not following the word wrap as it adjusts the new screen config.
|
||||||
|
|
|
||||||
|
|
@ -51,13 +51,29 @@ func clampScrollOffset(s *BrowserState) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolveTapIndex maps a ListView row index to the actual sorted entry index.
|
||||||
|
// When search is active, the row index is into SearchResults, so we must
|
||||||
|
// look through SearchResults to find the real entry index.
|
||||||
|
func resolveTapIndex(s *BrowserState, index int) int {
|
||||||
|
if s.Query != "" && len(s.SearchResults) > 0 {
|
||||||
|
if index < 0 || index >= len(s.SearchResults) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return s.SearchResults[index]
|
||||||
|
}
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
// HandleBrowserTap processes a tap on the ListView at the given index.
|
// HandleBrowserTap processes a tap on the ListView at the given index.
|
||||||
func HandleBrowserTap(bm *BrowserManager, s *BrowserState, index int) {
|
func HandleBrowserTap(bm *BrowserManager, s *BrowserState, index int) {
|
||||||
if index < 0 || index >= s.TotalEntries {
|
// Resolve the ListView row index to the actual sorted entry index.
|
||||||
|
// When search is active, the row is an index into SearchResults.
|
||||||
|
entryIndex := resolveTapIndex(s, index)
|
||||||
|
if entryIndex < 0 || entryIndex >= s.TotalEntries {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
entry, ok := getEntryByIndex(s, index)
|
entry, ok := getEntryByIndex(s, entryIndex)
|
||||||
if !ok {
|
if !ok {
|
||||||
return // Page not loaded
|
return // Page not loaded
|
||||||
}
|
}
|
||||||
|
|
@ -76,9 +92,11 @@ func HandleBrowserTap(bm *BrowserManager, s *BrowserState, index int) {
|
||||||
bm.NavigateTo(newPath)
|
bm.NavigateTo(newPath)
|
||||||
} else {
|
} else {
|
||||||
// Open file
|
// Open file
|
||||||
s.SelectedIndex = index
|
s.SelectedIndex = entryIndex
|
||||||
fmt.Printf("HandleBrowserTap: Opening file %s\n", entry.Path)
|
fmt.Printf("HandleBrowserTap: Opening file %s\n", entry.Path)
|
||||||
|
if ui.OpenFile != nil {
|
||||||
ui.OpenFile(entry.Path)
|
ui.OpenFile(entry.Path)
|
||||||
|
}
|
||||||
// Dispatch ReadFileTask to the worker pool
|
// Dispatch ReadFileTask to the worker pool
|
||||||
task := pool.NewReadFileTask(entry.Path, bm.fs)
|
task := pool.NewReadFileTask(entry.Path, bm.fs)
|
||||||
fmt.Printf("HandleBrowserTap: Dispatching ReadFileTask for %s\n", entry.Path)
|
fmt.Printf("HandleBrowserTap: Dispatching ReadFileTask for %s\n", entry.Path)
|
||||||
|
|
|
||||||
|
|
@ -58,3 +58,75 @@ func TestTapUp(t *testing.T) {
|
||||||
t.Errorf("expected CurrentPath='/root', got %s", state.CurrentPath)
|
t.Errorf("expected CurrentPath='/root', got %s", state.CurrentPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTapWithSearchResults verifies that tapping a file in the search
|
||||||
|
// results list opens the correct file, not the file at the same index
|
||||||
|
// in the full directory listing.
|
||||||
|
func TestTapWithSearchResults(t *testing.T) {
|
||||||
|
state := NewBrowserState()
|
||||||
|
fs := mock.NewFileSystem()
|
||||||
|
wp := pool.NewWorkerPool(1)
|
||||||
|
bm, _ := NewBrowserManager(state, wp, fs)
|
||||||
|
state.CurrentPath = "/root"
|
||||||
|
|
||||||
|
// Mock entries: 5 files, sorted by name
|
||||||
|
entries := []Entry{
|
||||||
|
NewEntry("/root/alpha.txt", "alpha.txt", 100, time.Time{}, false),
|
||||||
|
NewEntry("/root/beta.txt", "beta.txt", 200, time.Time{}, false),
|
||||||
|
NewEntry("/root/gamma.txt", "gamma.txt", 300, time.Time{}, false),
|
||||||
|
NewEntry("/root/delta.txt", "delta.txt", 400, time.Time{}, false),
|
||||||
|
NewEntry("/root/epsilon.txt", "epsilon.txt", 500, time.Time{}, false),
|
||||||
|
}
|
||||||
|
state.Pages[0] = NewPage(0, entries)
|
||||||
|
state.TotalEntries = 5
|
||||||
|
|
||||||
|
// Simulate a search for "beta" that matches only index 1
|
||||||
|
state.Query = "beta"
|
||||||
|
state.SearchResults = []int{1}
|
||||||
|
|
||||||
|
// Tapping row 0 of the search results should open beta.txt (entry index 1),
|
||||||
|
// NOT alpha.txt (entry index 0).
|
||||||
|
HandleBrowserTap(bm, state, 0)
|
||||||
|
|
||||||
|
// Verify SelectedIndex was set to the resolved entry index (1), not the row index (0).
|
||||||
|
if state.SelectedIndex != 1 {
|
||||||
|
t.Errorf("expected SelectedIndex=1, got %d", state.SelectedIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTapWithMultipleSearchResults verifies tapping different rows of
|
||||||
|
// search results resolves to the correct entries.
|
||||||
|
func TestTapWithMultipleSearchResults(t *testing.T) {
|
||||||
|
state := NewBrowserState()
|
||||||
|
fs := mock.NewFileSystem()
|
||||||
|
wp := pool.NewWorkerPool(1)
|
||||||
|
bm, _ := NewBrowserManager(state, wp, fs)
|
||||||
|
state.CurrentPath = "/root"
|
||||||
|
|
||||||
|
// Mock entries: 5 files
|
||||||
|
entries := []Entry{
|
||||||
|
NewEntry("/root/aaa.txt", "aaa.txt", 100, time.Time{}, false),
|
||||||
|
NewEntry("/root/zzz.txt", "zzz.txt", 200, time.Time{}, false),
|
||||||
|
NewEntry("/root/aaa2.txt", "aaa2.txt", 300, time.Time{}, false),
|
||||||
|
NewEntry("/root/zzz2.txt", "zzz2.txt", 400, time.Time{}, false),
|
||||||
|
NewEntry("/root/mid.txt", "mid.txt", 500, time.Time{}, false),
|
||||||
|
}
|
||||||
|
state.Pages[0] = NewPage(0, entries)
|
||||||
|
state.TotalEntries = 5
|
||||||
|
|
||||||
|
// Simulate a search for "zzz" that matches indices 1 and 3
|
||||||
|
state.Query = "zzz"
|
||||||
|
state.SearchResults = []int{1, 3}
|
||||||
|
|
||||||
|
// Tapping row 0 should resolve to entry index 1 (zzz.txt)
|
||||||
|
HandleBrowserTap(bm, state, 0)
|
||||||
|
if state.SelectedIndex != 1 {
|
||||||
|
t.Errorf("row 0: expected SelectedIndex=1, got %d", state.SelectedIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tapping row 1 should resolve to entry index 3 (zzz2.txt)
|
||||||
|
HandleBrowserTap(bm, state, 1)
|
||||||
|
if state.SelectedIndex != 3 {
|
||||||
|
t.Errorf("row 1: expected SelectedIndex=3, got %d", state.SelectedIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,51 +2,22 @@ package editor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"pad/internal/ui"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestCursorExistsInLayout checks if the EditorLayout produces a cursor element.
|
// TestByteOffsetToLineCol_Logical verifies that the current logic
|
||||||
func TestCursorExistsInLayout(t *testing.T) {
|
// only handles logical lines (newline-delimited).
|
||||||
state := NewState()
|
func TestByteOffsetToLineCol_Logical(t *testing.T) {
|
||||||
TheState = state
|
buf := "hello\nworld"
|
||||||
|
// 012345 67890
|
||||||
|
|
||||||
// Set to EditorPage
|
// Logical line 0: "hello" (0-4), pos 5 is '\n', line 1 starts at 6
|
||||||
state.page = EditorPage
|
line, col := byteOffsetToLineCol(buf, 5)
|
||||||
|
if line != 0 || col != 5 {
|
||||||
elems := EditorLayout(ui.Dp(800), ui.Dp(600), false)
|
t.Errorf("expected (0, 5) for pos 5, got (%d, %d)", line, col)
|
||||||
|
|
||||||
cursorFound := false
|
|
||||||
for _, e := range elems {
|
|
||||||
// Assuming we add a Type() method to ui.Element interface later.
|
|
||||||
// For now, this will fail to compile if Type() doesn't exist.
|
|
||||||
// Let's assume we can cast or inspect the element.
|
|
||||||
if e.Type() == "cursor" {
|
|
||||||
cursorFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cursorFound {
|
line, col = byteOffsetToLineCol(buf, 6)
|
||||||
t.Error("Expected cursor element in EditorLayout")
|
if line != 1 || col != 0 {
|
||||||
}
|
t.Errorf("expected (1, 0) for pos 6, got (%d, %d)", line, col)
|
||||||
}
|
|
||||||
|
|
||||||
// TestKeyDownMove verifies key down events update CursorPosition.
|
|
||||||
func TestKeyDownMove(t *testing.T) {
|
|
||||||
state := NewState()
|
|
||||||
TheState = state
|
|
||||||
state.Editor.Buffer = "Hello"
|
|
||||||
state.Editor.CursorPosition = 1
|
|
||||||
|
|
||||||
// Simulate Right Arrow
|
|
||||||
HandleKeyDown("Right")
|
|
||||||
if state.Editor.CursorPosition != 2 {
|
|
||||||
t.Errorf("Expected cursor position 2, got %d", state.Editor.CursorPosition)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simulate Left Arrow
|
|
||||||
HandleKeyDown("Left")
|
|
||||||
if state.Editor.CursorPosition != 1 {
|
|
||||||
t.Errorf("Expected cursor position 1, got %d", state.Editor.CursorPosition)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -197,18 +197,39 @@ func HandleCursorMove(delta int) {
|
||||||
TheState.Editor.CursorPosition = newPos
|
TheState.Editor.CursorPosition = newPos
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleKeyDown interprets keyboard events for navigation.
|
// HandleKeyDown interprets keyboard events for navigation and editing.
|
||||||
|
// Receives both key.Event (as key.Name) and key.EditEvent from the main loop.
|
||||||
func HandleKeyDown(data any) {
|
func HandleKeyDown(data any) {
|
||||||
// The data is expected to be a key name string (key.Name)
|
switch v := data.(type) {
|
||||||
keyName := data.(key.Name)
|
case key.EditEvent:
|
||||||
log.Printf("HandleKeyDown: %s", keyName)
|
// Text input from IME / keyboard
|
||||||
switch keyName {
|
log.Printf("HandleKeyDown: EditEvent text=%q", v.Text)
|
||||||
|
HandleInsert(v.Text)
|
||||||
|
case key.Name:
|
||||||
|
log.Printf("HandleKeyDown: %s", v)
|
||||||
|
switch v {
|
||||||
case key.NameLeftArrow:
|
case key.NameLeftArrow:
|
||||||
HandleCursorMove(-1)
|
HandleCursorMove(-1)
|
||||||
case key.NameRightArrow:
|
case key.NameRightArrow:
|
||||||
HandleCursorMove(1)
|
HandleCursorMove(1)
|
||||||
|
case key.NameDeleteBackward:
|
||||||
|
HandleBackspace()
|
||||||
|
case key.NameDeleteForward:
|
||||||
|
HandleDelete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleDelete removes the character after the cursor.
|
||||||
|
func HandleDelete() {
|
||||||
|
pos := TheState.Editor.CursorPosition
|
||||||
|
buf := TheState.Editor.Buffer
|
||||||
|
if pos >= len(buf) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
TheState.Editor.Buffer = buf[:pos] + buf[pos+1:]
|
||||||
|
TheState.Editor.Dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
// HandleInsert inserts a string at the current cursor position.
|
// HandleInsert inserts a string at the current cursor position.
|
||||||
func HandleInsert(s string) {
|
func HandleInsert(s string) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user