aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/config/config.go2
-rw-r--r--internal/i18n/i18n.go283
-rw-r--r--internal/tui/reader.go116
-rw-r--r--internal/tui/reader_test.go58
4 files changed, 310 insertions, 149 deletions
diff --git a/internal/config/config.go b/internal/config/config.go
index e2ea0d3..a7365bb 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -25,7 +25,7 @@ var seedTOML []byte
// Version is lectio's release version, shared by every binary's
// -v/--version output (lectio, lectio-ui, lectio-web).
-const Version = "0.24.1"
+const Version = "0.25.0"
// validVersions are the five scripture versions lectio understands.
var validVersions = map[string]bool{
diff --git a/internal/i18n/i18n.go b/internal/i18n/i18n.go
index 16d489a..36aa288 100644
--- a/internal/i18n/i18n.go
+++ b/internal/i18n/i18n.go
@@ -34,6 +34,9 @@ type UI struct {
ReaderMarkPrompt, ReaderBookmarksTitle, ReaderBookmarksKeys, ReaderNoBookmarks string
ReaderMarkVerseKeys, ReaderConfirmDelete, ReaderConfirmKeys string
ReaderMarkNote, ReaderMarkTags, ReaderMarkHelp string
+ // Reader go-to-chapter prompt: the dialog label (rendered after the book
+ // name) and its enter/esc help line.
+ ReaderJumpChapter, ReaderJumpChapterKeys string
// CLI banner label words and connective: the banner is
// "<word> <BannerConnective> <date>" (e.g. "Gospel for 2026-07-22" /
@@ -116,75 +119,77 @@ var enUI = UI{
"aklamacja": "Acclamation",
"ewangelia": "Gospel",
},
- FooterKeys: "tab/⇧tab version ←/→ day d date j/k scroll space/b page g/G top/bottom r refresh q quit",
- Loading: "loading…",
- NoReadingsFor: "no readings for ",
- ErrorPrefix: "error: ",
- ErrorHint: "change date (←/→) or refresh (r)",
- JumpPrompt: "go to date (YYYY-MM-DD)",
- ReaderTitle: "reader — pick a book",
- ReaderPickKeys: "type to filter ↑/↓ move enter open esc quit",
- ReaderReadKeys: "n/p chapter tab version j/k scroll m mark b bookmarks esc books q quit",
- ReaderNoText: "(no text in this version)",
- ReaderNoMatch: "(no matching books)",
- ReaderMarkPrompt: "note (enter to save, esc to cancel)",
- ReaderBookmarksTitle: "bookmarks",
- ReaderBookmarksKeys: "j/k move enter open d delete esc back q quit",
- ReaderNoBookmarks: "no bookmarks yet — press m while reading",
- ReaderMarkVerseKeys: "↑/↓ pick verse enter add note esc cancel",
- ReaderConfirmDelete: "Delete this bookmark?",
- ReaderConfirmKeys: "y = delete n = cancel",
- ReaderMarkNote: "note",
- ReaderMarkTags: "tags",
- ReaderMarkHelp: "enter save · tab switch field · esc cancel",
- BannerGospel: "Gospel",
- BannerReadings: "Readings",
- BannerConnective: "for",
- Months: [12]string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"},
- Lectionary: "lectionary",
- OptModern: "modern",
- OptTraditional: "traditional",
- Parts: "parts",
- OptGospel: "Gospel",
- OptAll: "all parts",
- Layout: "layout",
- OptHorizontal: "horizontal",
- OptColumns: "columns",
- OptInterlinear: "interlinear",
- Theme: "theme",
- Mono: "mono",
- NoInterlinearVerses: "(no verses to align)",
- NavReader: "reader",
- WebBook: "book",
- WebChapter: "chapter",
- NavHome: "home",
- NavSettings: "settings",
- NavBookmarks: "bookmarks",
- WebVerseOptional: "verse (optional)",
- WebTagsPlaceholder: "tags, comma-separated",
- WebAddBookmark: "bookmark",
- WebAllTags: "all",
- WebNoBookmarks: "No bookmarks yet.",
- WebDelete: "delete",
- WebSaved: "Saved.",
- WebTradLang: "traditional language",
- WebUILang: "UI language",
- WebSiglaStyle: "sigla style",
- WebDefaultVersion: "default version",
- WebVersions: "versions",
- WebWebVersions: "web versions",
- WebOffline: "offline",
- WebWidth: "width",
- WebPort: "web port",
- WebPager: "pager",
- WebSave: "Save",
- Export: "download",
- Calendar: "month calendar",
- NoReadingsDay: "no readings for this day",
- NoVersion: "(not in %s)",
- NoVersionPartial: "(not in %s: %s)",
- NoReference: "(no reference)",
- NoReferenceErr: "(no reference: %v)",
+ FooterKeys: "tab/⇧tab version ←/→ day d date j/k scroll space/b page g/G top/bottom r refresh q quit",
+ Loading: "loading…",
+ NoReadingsFor: "no readings for ",
+ ErrorPrefix: "error: ",
+ ErrorHint: "change date (←/→) or refresh (r)",
+ JumpPrompt: "go to date (YYYY-MM-DD)",
+ ReaderTitle: "reader — pick a book",
+ ReaderPickKeys: "type to filter ↑/↓ move enter open esc quit",
+ ReaderReadKeys: "n/p chapter c go-to tab version j/k scroll m mark b bookmarks esc books q quit",
+ ReaderNoText: "(no text in this version)",
+ ReaderNoMatch: "(no matching books)",
+ ReaderMarkPrompt: "note (enter to save, esc to cancel)",
+ ReaderBookmarksTitle: "bookmarks",
+ ReaderBookmarksKeys: "j/k move enter open d delete esc back q quit",
+ ReaderNoBookmarks: "no bookmarks yet — press m while reading",
+ ReaderMarkVerseKeys: "↑/↓ pick verse enter add note esc cancel",
+ ReaderConfirmDelete: "Delete this bookmark?",
+ ReaderConfirmKeys: "y = delete n = cancel",
+ ReaderMarkNote: "note",
+ ReaderMarkTags: "tags",
+ ReaderMarkHelp: "enter save · tab switch field · esc cancel",
+ ReaderJumpChapter: "go to chapter",
+ ReaderJumpChapterKeys: "enter go · esc cancel",
+ BannerGospel: "Gospel",
+ BannerReadings: "Readings",
+ BannerConnective: "for",
+ Months: [12]string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"},
+ Lectionary: "lectionary",
+ OptModern: "modern",
+ OptTraditional: "traditional",
+ Parts: "parts",
+ OptGospel: "Gospel",
+ OptAll: "all parts",
+ Layout: "layout",
+ OptHorizontal: "horizontal",
+ OptColumns: "columns",
+ OptInterlinear: "interlinear",
+ Theme: "theme",
+ Mono: "mono",
+ NoInterlinearVerses: "(no verses to align)",
+ NavReader: "reader",
+ WebBook: "book",
+ WebChapter: "chapter",
+ NavHome: "home",
+ NavSettings: "settings",
+ NavBookmarks: "bookmarks",
+ WebVerseOptional: "verse (optional)",
+ WebTagsPlaceholder: "tags, comma-separated",
+ WebAddBookmark: "bookmark",
+ WebAllTags: "all",
+ WebNoBookmarks: "No bookmarks yet.",
+ WebDelete: "delete",
+ WebSaved: "Saved.",
+ WebTradLang: "traditional language",
+ WebUILang: "UI language",
+ WebSiglaStyle: "sigla style",
+ WebDefaultVersion: "default version",
+ WebVersions: "versions",
+ WebWebVersions: "web versions",
+ WebOffline: "offline",
+ WebWidth: "width",
+ WebPort: "web port",
+ WebPager: "pager",
+ WebSave: "Save",
+ Export: "download",
+ Calendar: "month calendar",
+ NoReadingsDay: "no readings for this day",
+ NoVersion: "(not in %s)",
+ NoVersionPartial: "(not in %s: %s)",
+ NoReference: "(no reference)",
+ NoReferenceErr: "(no reference: %v)",
}
var plUI = UI{
@@ -202,73 +207,75 @@ var plUI = UI{
"aklamacja": "Aklamacja",
"ewangelia": "Ewangelia",
},
- FooterKeys: "tab/⇧tab wersja ←/→ dzień d data j/k przewiń spacja/b strona g/G góra/dół r odśwież q wyjście",
- Loading: "ładowanie…",
- NoReadingsFor: "brak czytań na ",
- ErrorPrefix: "błąd: ",
- ErrorHint: "zmień datę (←/→) lub odśwież (r)",
- JumpPrompt: "przejdź do daty (RRRR-MM-DD)",
- ReaderTitle: "czytnik — wybierz księgę",
- ReaderPickKeys: "wpisz, by filtrować ↑/↓ ruch enter otwórz esc wyjście",
- ReaderReadKeys: "n/p rozdział tab wersja j/k przewiń m zakładka b zakładki esc księgi q wyjście",
- ReaderNoText: "(brak tekstu w tej wersji)",
- ReaderNoMatch: "(brak pasujących ksiąg)",
- ReaderMarkPrompt: "notatka (enter zapisz, esc anuluj)",
- ReaderBookmarksTitle: "zakładki",
- ReaderBookmarksKeys: "j/k ruch enter otwórz d usuń esc wróć q wyjście",
- ReaderNoBookmarks: "brak zakładek — naciśnij m podczas czytania",
- ReaderMarkVerseKeys: "↑/↓ wybierz werset enter notatka esc anuluj",
- ReaderConfirmDelete: "Usunąć zakładkę?",
- ReaderConfirmKeys: "y = usuń n = anuluj",
- ReaderMarkNote: "notatka",
- ReaderMarkTags: "tagi",
- ReaderMarkHelp: "enter zapisz · tab pole · esc anuluj",
- BannerGospel: "Ewangelia",
- BannerReadings: "Czytania",
- BannerConnective: "na",
- Months: [12]string{"Styczeń", "Luty", "Marzec", "Kwiecień", "Maj", "Czerwiec", "Lipiec", "Sierpień", "Wrzesień", "Październik", "Listopad", "Grudzień"},
- Lectionary: "lekcjonarz",
- OptModern: "nowy",
- OptTraditional: "tradycyjny",
- Parts: "zakres",
- OptGospel: "Ewangelia",
- OptAll: "wszystkie części",
- Layout: "układ",
- OptHorizontal: "poziomo",
- OptColumns: "kolumny",
- OptInterlinear: "interlinearnie",
- Theme: "motyw",
- Mono: "mono",
- NoInterlinearVerses: "(brak wersetów do zestawienia interlinearnego)",
- NavReader: "czytnik",
- WebBook: "księga",
- WebChapter: "rozdział",
- NavHome: "strona główna",
- NavSettings: "ustawienia",
- NavBookmarks: "zakładki",
- WebVerseOptional: "werset (opcjonalnie)",
- WebTagsPlaceholder: "tagi, oddzielone przecinkami",
- WebAddBookmark: "zakładka",
- WebAllTags: "wszystkie",
- WebNoBookmarks: "Brak zakładek.",
- WebDelete: "usuń",
- WebSaved: "Zapisano.",
- WebTradLang: "język tradycyjny",
- WebUILang: "język interfejsu",
- WebSiglaStyle: "styl sigli",
- WebDefaultVersion: "wersja domyślna",
- WebVersions: "wersje",
- WebWebVersions: "wersje web",
- WebOffline: "offline",
- WebWidth: "szerokość",
- WebPort: "port web",
- WebPager: "pager",
- WebSave: "Zapisz",
- Export: "pobierz",
- Calendar: "kalendarz miesiąca",
- NoReadingsDay: "brak czytań na ten dzień",
- NoVersion: "(brak w „%s”)",
- NoVersionPartial: "(brak w „%s”: %s)",
- NoReference: "(brak odwołania)",
- NoReferenceErr: "(brak odwołania: %w)",
+ FooterKeys: "tab/⇧tab wersja ←/→ dzień d data j/k przewiń spacja/b strona g/G góra/dół r odśwież q wyjście",
+ Loading: "ładowanie…",
+ NoReadingsFor: "brak czytań na ",
+ ErrorPrefix: "błąd: ",
+ ErrorHint: "zmień datę (←/→) lub odśwież (r)",
+ JumpPrompt: "przejdź do daty (RRRR-MM-DD)",
+ ReaderTitle: "czytnik — wybierz księgę",
+ ReaderPickKeys: "wpisz, by filtrować ↑/↓ ruch enter otwórz esc wyjście",
+ ReaderReadKeys: "n/p rozdział c skok tab wersja j/k przewiń m zakładka b zakładki esc księgi q wyjście",
+ ReaderNoText: "(brak tekstu w tej wersji)",
+ ReaderNoMatch: "(brak pasujących ksiąg)",
+ ReaderMarkPrompt: "notatka (enter zapisz, esc anuluj)",
+ ReaderBookmarksTitle: "zakładki",
+ ReaderBookmarksKeys: "j/k ruch enter otwórz d usuń esc wróć q wyjście",
+ ReaderNoBookmarks: "brak zakładek — naciśnij m podczas czytania",
+ ReaderMarkVerseKeys: "↑/↓ wybierz werset enter notatka esc anuluj",
+ ReaderConfirmDelete: "Usunąć zakładkę?",
+ ReaderConfirmKeys: "y = usuń n = anuluj",
+ ReaderMarkNote: "notatka",
+ ReaderMarkTags: "tagi",
+ ReaderMarkHelp: "enter zapisz · tab pole · esc anuluj",
+ ReaderJumpChapter: "przejdź do rozdziału",
+ ReaderJumpChapterKeys: "enter przejdź · esc anuluj",
+ BannerGospel: "Ewangelia",
+ BannerReadings: "Czytania",
+ BannerConnective: "na",
+ Months: [12]string{"Styczeń", "Luty", "Marzec", "Kwiecień", "Maj", "Czerwiec", "Lipiec", "Sierpień", "Wrzesień", "Październik", "Listopad", "Grudzień"},
+ Lectionary: "lekcjonarz",
+ OptModern: "nowy",
+ OptTraditional: "tradycyjny",
+ Parts: "zakres",
+ OptGospel: "Ewangelia",
+ OptAll: "wszystkie części",
+ Layout: "układ",
+ OptHorizontal: "poziomo",
+ OptColumns: "kolumny",
+ OptInterlinear: "interlinearnie",
+ Theme: "motyw",
+ Mono: "mono",
+ NoInterlinearVerses: "(brak wersetów do zestawienia interlinearnego)",
+ NavReader: "czytnik",
+ WebBook: "księga",
+ WebChapter: "rozdział",
+ NavHome: "strona główna",
+ NavSettings: "ustawienia",
+ NavBookmarks: "zakładki",
+ WebVerseOptional: "werset (opcjonalnie)",
+ WebTagsPlaceholder: "tagi, oddzielone przecinkami",
+ WebAddBookmark: "zakładka",
+ WebAllTags: "wszystkie",
+ WebNoBookmarks: "Brak zakładek.",
+ WebDelete: "usuń",
+ WebSaved: "Zapisano.",
+ WebTradLang: "język tradycyjny",
+ WebUILang: "język interfejsu",
+ WebSiglaStyle: "styl sigli",
+ WebDefaultVersion: "wersja domyślna",
+ WebVersions: "wersje",
+ WebWebVersions: "wersje web",
+ WebOffline: "offline",
+ WebWidth: "szerokość",
+ WebPort: "port web",
+ WebPager: "pager",
+ WebSave: "Zapisz",
+ Export: "pobierz",
+ Calendar: "kalendarz miesiąca",
+ NoReadingsDay: "brak czytań na ten dzień",
+ NoVersion: "(brak w „%s”)",
+ NoVersionPartial: "(brak w „%s”: %s)",
+ NoReference: "(brak odwołania)",
+ NoReferenceErr: "(brak odwołania: %w)",
}
diff --git a/internal/tui/reader.go b/internal/tui/reader.go
index df92349..7f23f66 100644
--- a/internal/tui/reader.go
+++ b/internal/tui/reader.go
@@ -3,6 +3,7 @@ package tui
import (
"fmt"
"sort"
+ "strconv"
"strings"
tea "github.com/charmbracelet/bubbletea"
@@ -21,11 +22,12 @@ import (
type readerMode int
const (
- modePick readerMode = iota
- modeRead // scrolling chapter view
- modeMarkVerse // picking the verse to bookmark (highlighted cursor)
- modeMark // typing an optional note for the chosen verse
- modeBookmarks // the saved-bookmarks list
+ modePick readerMode = iota
+ modeRead // scrolling chapter view
+ modeMarkVerse // picking the verse to bookmark (highlighted cursor)
+ modeMark // typing an optional note for the chosen verse
+ modeBookmarks // the saved-bookmarks list
+ modeChapterJump // typing a chapter number to jump to
)
// selStyle marks the picker's selected row (reverse video, legible on any theme).
@@ -70,11 +72,12 @@ type ReaderModel struct {
pickTop int // first visible match (list scroll)
// reading
- bookIdx int // index into books
- chapters []int // sorted chapters for current book+version
- chapPos int // index into chapters
- verses []bible.Verse
- scroll int
+ bookIdx int // index into books
+ chapters []int // sorted chapters for current book+version
+ chapPos int // index into chapters
+ verses []bible.Verse
+ scroll int
+ chapJumpBuf string // digits typed in modeChapterJump
// bookmarks
store *bookmarks.Store
@@ -218,6 +221,8 @@ func (m ReaderModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m.updateMark(msg)
case modeBookmarks:
return m.updateBookmarks(msg)
+ case modeChapterJump:
+ return m.updateChapterJump(msg)
default:
return m.updateRead(msg)
}
@@ -302,6 +307,12 @@ func (m ReaderModel) updateRead(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
return m.chapterStep(+1), nil
case "p", "[", "left", "h":
return m.chapterStep(-1), nil
+ case "c":
+ if len(m.chapters) > 1 {
+ m.mode = modeChapterJump
+ m.chapJumpBuf = ""
+ }
+ return m, nil
case "j", "down":
m.scroll = m.clampRead(m.scroll + 1)
return m, nil
@@ -457,6 +468,75 @@ func (m ReaderModel) updateBookmarks(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
return m, nil
}
+// updateChapterJump handles the go-to-chapter prompt: type digits, Enter jumps
+// (clamped to the book's chapter range), Esc cancels. Non-digit keys are
+// ignored so the buffer only ever holds a number.
+func (m ReaderModel) updateChapterJump(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
+ switch msg.Type {
+ case tea.KeyCtrlC:
+ return m, tea.Quit
+ case tea.KeyEsc:
+ m.mode = modeRead
+ m.chapJumpBuf = ""
+ return m, nil
+ case tea.KeyEnter:
+ m = m.jumpToChapter(m.chapJumpBuf)
+ m.chapJumpBuf = ""
+ m.mode = modeRead
+ return m, nil
+ case tea.KeyBackspace:
+ if r := []rune(m.chapJumpBuf); len(r) > 0 {
+ m.chapJumpBuf = string(r[:len(r)-1])
+ }
+ return m, nil
+ case tea.KeyRunes:
+ for _, r := range msg.Runes {
+ if r >= '0' && r <= '9' && len([]rune(m.chapJumpBuf)) < 4 {
+ m.chapJumpBuf += string(r)
+ }
+ }
+ return m, nil
+ }
+ return m, nil
+}
+
+// jumpToChapter parses buf as a chapter number, clamps it to the book's
+// chapter range, and opens that chapter at its top. An empty or non-numeric
+// buf is a no-op (the prompt just closes). Because the corpora number chapters
+// contiguously, the clamped number always names an existing chapter; if a book
+// ever had a gap, the nearest existing chapter at or after it is used.
+func (m ReaderModel) jumpToChapter(buf string) ReaderModel {
+ if len(m.chapters) == 0 {
+ return m
+ }
+ n, err := strconv.Atoi(strings.TrimSpace(buf))
+ if err != nil {
+ return m
+ }
+ lo, hi := m.chapters[0], m.chapters[len(m.chapters)-1]
+ if n < lo {
+ n = lo
+ }
+ if n > hi {
+ n = hi
+ }
+ pos := len(m.chapters) - 1
+ for i, c := range m.chapters {
+ if c >= n {
+ pos = i
+ break
+ }
+ }
+ if pos == m.chapPos {
+ return m
+ }
+ m.chapPos = pos
+ m.scroll = 0
+ m = m.loadVerses()
+ m.savePlace()
+ return m
+}
+
// currentChapter is the chapter number currently shown (0 if none loaded).
func (m ReaderModel) currentChapter() int {
if len(m.chapters) > 0 && m.chapPos < len(m.chapters) {
@@ -735,11 +815,27 @@ func (m ReaderModel) View() string {
return m.viewMark()
case modeBookmarks:
return m.viewBookmarks()
+ case modeChapterJump:
+ return m.viewChapterJump()
default: // modeRead + modeMarkVerse (verse cursor highlighted in the reading)
return m.viewRead()
}
}
+// viewChapterJump renders the go-to-chapter prompt as a prominent centered
+// dialog showing the book's valid chapter range and the digits typed so far.
+func (m ReaderModel) viewChapterJump() string {
+ ui := i18n.Get(m.cfg.UILanguage)
+ lo, hi := 0, 0
+ if len(m.chapters) > 0 {
+ lo, hi = m.chapters[0], m.chapters[len(m.chapters)-1]
+ }
+ title := modalTitleStyle.Render(fmt.Sprintf("%s %s", m.books[m.bookIdx].Name, ui.ReaderJumpChapter))
+ prompt := fmt.Sprintf("%d-%d: %s▏", lo, hi, m.chapJumpBuf)
+ inner := title + "\n\n" + prompt + "\n\n" + citationStyle.Render(ui.ReaderJumpChapterKeys)
+ return m.modal(inner)
+}
+
// viewMark renders the bookmark note/tags box as a prominent centered dialog.
// The note and tags wrap (with a hanging indent) within a bounded width, so a
// long note flows down inside the box instead of overrunning the border.
diff --git a/internal/tui/reader_test.go b/internal/tui/reader_test.go
index 904836e..9d324ba 100644
--- a/internal/tui/reader_test.go
+++ b/internal/tui/reader_test.go
@@ -165,6 +165,64 @@ func TestReaderBookmarkFlow(t *testing.T) {
}
}
+func TestReaderChapterJump(t *testing.T) {
+ m := enReader(t)
+ m = win(m, 80, 24)
+ m = key(m, runes("jn"))
+ m = key(m, tea.KeyMsg{Type: tea.KeyEnter}) // open John 1 (21 chapters)
+ if m.mode != modeRead {
+ t.Fatal("did not enter reading")
+ }
+ // c opens the jump prompt
+ m = key(m, runes("c"))
+ if m.mode != modeChapterJump {
+ t.Fatalf("c did not open the chapter-jump prompt (mode=%v)", m.mode)
+ }
+ if hi := m.chapters[len(m.chapters)-1]; hi != 21 {
+ t.Fatalf("John should have 21 chapters, got %d", hi)
+ }
+ if v := m.View(); !strings.Contains(v, "1-21") || !strings.Contains(v, "go to chapter") {
+ t.Errorf("jump prompt missing range/label:\n%s", v)
+ }
+ // non-digit keys are ignored; only digits accumulate
+ m = key(m, runes("x1"))
+ m = key(m, runes("4"))
+ if m.chapJumpBuf != "14" {
+ t.Fatalf("buffer = %q want 14 (non-digits filtered)", m.chapJumpBuf)
+ }
+ m = key(m, tea.KeyMsg{Type: tea.KeyBackspace})
+ m = key(m, runes("2")) // -> "12"
+ m = key(m, tea.KeyMsg{Type: tea.KeyEnter})
+ if m.mode != modeRead {
+ t.Fatalf("enter did not return to reading (mode=%v)", m.mode)
+ }
+ if m.currentChapter() != 12 {
+ t.Errorf("jumped to chapter %d want 12", m.currentChapter())
+ }
+ if m.scroll != 0 {
+ t.Errorf("jump should reset scroll, got %d", m.scroll)
+ }
+
+ // out-of-range clamps to the last chapter
+ m = key(m, runes("c"))
+ m = key(m, runes("999"))
+ m = key(m, tea.KeyMsg{Type: tea.KeyEnter})
+ if m.currentChapter() != 21 {
+ t.Errorf("999 should clamp to 21, got %d", m.currentChapter())
+ }
+
+ // esc cancels without moving
+ m = key(m, runes("c"))
+ m = key(m, runes("3"))
+ m = key(m, tea.KeyMsg{Type: tea.KeyEsc})
+ if m.mode != modeRead {
+ t.Fatalf("esc did not return to reading (mode=%v)", m.mode)
+ }
+ if m.currentChapter() != 21 {
+ t.Errorf("esc changed chapter to %d want 21", m.currentChapter())
+ }
+}
+
func TestReaderRemembersPlace(t *testing.T) {
dir := t.TempDir()
t.Setenv("XDG_DATA_HOME", dir)