simplify keybinds
This commit is contained in:
@@ -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} }
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user