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.
|
||||
|
||||
On the BrowserPage, when I enter a search that limits the list of files, clicking on a file
|
||||
delivers teh wrong file to the editor page.
|
||||
If I move the cursor (using the arrow keys) to the right side of the screen in a word-wrapped
|
||||
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.
|
||||
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
|
||||
}
|
||||
|
||||
entry, ok := getEntryByIndex(s, index)
|
||||
entry, ok := getEntryByIndex(s, entryIndex)
|
||||
if !ok {
|
||||
return // Page not loaded
|
||||
}
|
||||
|
|
@ -76,9 +92,11 @@ func HandleBrowserTap(bm *BrowserManager, s *BrowserState, index int) {
|
|||
bm.NavigateTo(newPath)
|
||||
} else {
|
||||
// Open file
|
||||
s.SelectedIndex = index
|
||||
s.SelectedIndex = entryIndex
|
||||
fmt.Printf("HandleBrowserTap: Opening file %s\n", entry.Path)
|
||||
if ui.OpenFile != nil {
|
||||
ui.OpenFile(entry.Path)
|
||||
}
|
||||
// Dispatch ReadFileTask to the worker pool
|
||||
task := pool.NewReadFileTask(entry.Path, bm.fs)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (
|
||||
"testing"
|
||||
"pad/internal/ui"
|
||||
)
|
||||
|
||||
// TestCursorExistsInLayout checks if the EditorLayout produces a cursor element.
|
||||
func TestCursorExistsInLayout(t *testing.T) {
|
||||
state := NewState()
|
||||
TheState = state
|
||||
// TestByteOffsetToLineCol_Logical verifies that the current logic
|
||||
// only handles logical lines (newline-delimited).
|
||||
func TestByteOffsetToLineCol_Logical(t *testing.T) {
|
||||
buf := "hello\nworld"
|
||||
// 012345 67890
|
||||
|
||||
// Set to EditorPage
|
||||
state.page = EditorPage
|
||||
|
||||
elems := EditorLayout(ui.Dp(800), ui.Dp(600), false)
|
||||
|
||||
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
|
||||
}
|
||||
// Logical line 0: "hello" (0-4), pos 5 is '\n', line 1 starts at 6
|
||||
line, col := byteOffsetToLineCol(buf, 5)
|
||||
if line != 0 || col != 5 {
|
||||
t.Errorf("expected (0, 5) for pos 5, got (%d, %d)", line, col)
|
||||
}
|
||||
|
||||
if !cursorFound {
|
||||
t.Error("Expected cursor element in EditorLayout")
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
line, col = byteOffsetToLineCol(buf, 6)
|
||||
if line != 1 || col != 0 {
|
||||
t.Errorf("expected (1, 0) for pos 6, got (%d, %d)", line, col)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -197,17 +197,38 @@ func HandleCursorMove(delta int) {
|
|||
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) {
|
||||
// The data is expected to be a key name string (key.Name)
|
||||
keyName := data.(key.Name)
|
||||
log.Printf("HandleKeyDown: %s", keyName)
|
||||
switch keyName {
|
||||
switch v := data.(type) {
|
||||
case key.EditEvent:
|
||||
// Text input from IME / keyboard
|
||||
log.Printf("HandleKeyDown: EditEvent text=%q", v.Text)
|
||||
HandleInsert(v.Text)
|
||||
case key.Name:
|
||||
log.Printf("HandleKeyDown: %s", v)
|
||||
switch v {
|
||||
case key.NameLeftArrow:
|
||||
HandleCursorMove(-1)
|
||||
case key.NameRightArrow:
|
||||
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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user