simplify keybinds

This commit is contained in:
2026-01-04 05:07:11 +00:00
parent c960397adc
commit 34fde8ac27
3 changed files with 82 additions and 25 deletions
+3 -3
View File
@@ -101,11 +101,11 @@ func (t *FsTree) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyMsg: case tea.KeyMsg:
t.errMsg = "" t.errMsg = ""
switch m.String() { switch m.String() {
case "w": case "w", "up":
_ = t.MoveUp() _ = t.MoveUp()
case "s": case "s", "down":
_ = t.MoveDown() _ = t.MoveDown()
case "e": case "e", "space":
_ = t.ToggleSelectedExpand() _ = t.ToggleSelectedExpand()
case "n": // new file case "n": // new file
return t, func() tea.Msg { return RequestInputMsg{Action: ActionNewFile} } return t, func() tea.Msg { return RequestInputMsg{Action: ActionNewFile} }
+6
View File
@@ -202,6 +202,12 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, cmd return m, cmd
} }
// If editing, forward all keys to noteView and ignore global bindings
if m.noteView.IsEditing() {
_, cmd := m.noteView.Update(msg)
return m, cmd
}
switch msg.String() { switch msg.String() {
case "q", "ctrl+c": case "q", "ctrl+c":
return m, tea.Quit return m, tea.Quit
+73 -22
View File
@@ -11,6 +11,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/charmbracelet/bubbles/textarea"
"github.com/charmbracelet/bubbles/viewport" "github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/glamour" "github.com/charmbracelet/glamour"
@@ -26,16 +27,20 @@ const (
type NoteView struct { type NoteView struct {
// the actual content // the actual content
path string path string
title string // the first line is always # title title string // the first line is always # title
content string // strip the first line from file rest is content content string // strip the first line from file rest is content
hints []string //a hint is anything that matches (hint: <text>) in content rawContent string // full content for editing
hints []string //a hint is anything that matches (hint: <text>) in content
// display layer // display layer
err error err error
loading bool loading bool
vp viewport.Model vp viewport.Model
mdRenderer *glamour.TermRenderer mdRenderer *glamour.TermRenderer
viewState ViewState viewState ViewState
// editing
textarea textarea.Model
isEditing bool
} }
// ================== messages =================== // ================== messages ===================
@@ -44,10 +49,11 @@ type loadNote struct {
} }
type loadedNote struct { type loadedNote struct {
title string title string
content string content string
hints []string rawContent string
err error hints []string
err error
} }
func NewNoteView() *NoteView { func NewNoteView() *NoteView {
@@ -55,16 +61,23 @@ func NewNoteView() *NoteView {
glamour.WithAutoStyle(), glamour.WithAutoStyle(),
glamour.WithWordWrap(80), glamour.WithWordWrap(80),
) )
ta := textarea.New()
ta.Focus()
return &NoteView{ return &NoteView{
loading: true, loading: true,
mdRenderer: mdRenderer, mdRenderer: mdRenderer,
vp: viewport.New(0, 0), vp: viewport.New(0, 0),
viewState: StateTitleOnly, viewState: StateTitleOnly,
textarea: ta,
} }
} }
func (m *NoteView) Init() tea.Cmd { func (m *NoteView) Init() tea.Cmd {
return nil return textarea.Blink
}
func (m *NoteView) IsEditing() bool {
return m.isEditing
} }
func (m *NoteView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m *NoteView) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
@@ -72,22 +85,38 @@ func (m *NoteView) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.WindowSizeMsg: case tea.WindowSizeMsg:
m.vp.Width = msg.Width m.vp.Width = msg.Width
m.vp.Height = msg.Height m.vp.Height = msg.Height
m.textarea.SetWidth(msg.Width)
m.textarea.SetHeight(msg.Height)
return m, nil return m, nil
case tea.KeyMsg: case tea.KeyMsg:
if m.isEditing {
switch msg.String() {
case "ctrl+w":
m.isEditing = false
return m, nil
case "ctrl+s":
return m, saveContent(m.path, m.textarea.Value())
}
var cmd tea.Cmd
m.textarea, cmd = m.textarea.Update(msg)
return m, cmd
}
switch msg.String() { switch msg.String() {
case "d": case "enter":
if m.path != "" && !m.loading {
m.isEditing = true
m.textarea.SetValue(m.rawContent)
m.textarea.Focus()
return m, textarea.Blink
}
case "d", "right":
m.viewState = StateContent m.viewState = StateContent
m.vp.SetContent(m.renderNote()) m.vp.SetContent(m.renderNote())
case "a": case "a", "left":
m.viewState = StateHints m.viewState = StateHints
m.vp.SetContent(m.renderNote()) m.vp.SetContent(m.renderNote())
case "up":
m.vp.ScrollUp(1)
return m, nil
case "down":
m.vp.ScrollDown(1)
return m, nil
case "pgup": case "pgup":
m.vp.PageUp() m.vp.PageUp()
return m, nil return m, nil
@@ -104,6 +133,7 @@ func (m *NoteView) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil //noop return m, nil //noop
} }
m.path = msg.path m.path = msg.path
m.isEditing = false
m.loading = true m.loading = true
return m, fetchContent(msg.path) return m, fetchContent(msg.path)
@@ -111,13 +141,18 @@ func (m *NoteView) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.loading = false m.loading = false
m.title = msg.title m.title = msg.title
m.content = msg.content m.content = msg.content
m.rawContent = msg.rawContent
m.hints = msg.hints m.hints = msg.hints
m.err = msg.err m.err = msg.err
m.viewState = StateTitleOnly m.viewState = StateTitleOnly
m.vp.SetContent(m.renderNote()) m.vp.SetContent(m.renderNote())
return m, nil return m, nil
case tea.MouseMsg: case tea.MouseMsg:
if m.isEditing {
return m, nil // or handle mouse in textarea if supported
}
var cmd tea.Cmd var cmd tea.Cmd
m.vp, cmd = m.vp.Update(msg) m.vp, cmd = m.vp.Update(msg)
return m, cmd return m, cmd
@@ -134,6 +169,10 @@ func (m NoteView) View() string {
return "Error: " + m.err.Error() return "Error: " + m.err.Error()
} }
if m.isEditing {
return m.textarea.View()
}
return m.vp.View() return m.vp.View()
} }
@@ -144,7 +183,8 @@ func fetchContent(path string) tea.Cmd {
return loadedNote{err: err} return loadedNote{err: err}
} }
content := string(data) rawContent := string(data)
content := rawContent
lines := strings.Split(content, "\n") lines := strings.Split(content, "\n")
var title string var title string
@@ -160,13 +200,24 @@ func fetchContent(path string) tea.Cmd {
content = strings.TrimSpace(content) content = strings.TrimSpace(content)
return loadedNote{ return loadedNote{
title: title, title: title,
content: content, content: content,
hints: hints, rawContent: rawContent,
hints: hints,
} }
} }
} }
func saveContent(path, content string) tea.Cmd {
return func() tea.Msg {
err := os.WriteFile(path, []byte(content), 0644)
if err != nil {
return loadedNote{err: err}
}
return fetchContent(path)()
}
}
func extractHints(content string) []string { func extractHints(content string) []string {
re := regexp.MustCompile(`(?s)\(hint:\s*(.*?)\)`) re := regexp.MustCompile(`(?s)\(hint:\s*(.*?)\)`)
matches := re.FindAllStringSubmatch(content, -1) matches := re.FindAllStringSubmatch(content, -1)
@@ -203,7 +254,7 @@ func (m NoteView) renderNote() string {
body, err2 = m.mdRenderer.Render(hintsList) body, err2 = m.mdRenderer.Render(hintsList)
} }
case StateTitleOnly: case StateTitleOnly:
body = "\n(Press 'space' to show content, 'h' to show hints)" body = "'a' to view hints, 'd' to view content."
} }
if err2 != nil { if err2 != nil {