Polish text caret and selection flow

This commit is contained in:
vrubelroman 2026-04-25 02:21:43 +03:00
parent 641ad17676
commit ea8c596ef6
2 changed files with 41 additions and 9 deletions

View file

@ -54,7 +54,7 @@ func DefaultKeyMap() KeyMap {
SelectUp: key.NewBinding(key.WithKeys("shift+up", "K"), key.WithHelp("S-↑/K", "select up")), SelectUp: key.NewBinding(key.WithKeys("shift+up", "K"), key.WithHelp("S-↑/K", "select up")),
SelectDown: key.NewBinding(key.WithKeys("shift+down", "J"), key.WithHelp("S-↓/J", "select down")), SelectDown: key.NewBinding(key.WithKeys("shift+down", "J"), key.WithHelp("S-↓/J", "select down")),
PageUp: key.NewBinding(key.WithKeys("pgup"), key.WithHelp("PgUp", "page up")), PageUp: key.NewBinding(key.WithKeys("pgup"), key.WithHelp("PgUp", "page up")),
PageDown: key.NewBinding(key.WithKeys("pgdown", "f"), key.WithHelp("PgDn/f", "page down")), PageDown: key.NewBinding(),
Open: key.NewBinding(key.WithKeys("enter", "right"), key.WithHelp("Enter", "open")), Open: key.NewBinding(key.WithKeys("enter", "right"), key.WithHelp("Enter", "open")),
Back: key.NewBinding(key.WithKeys("backspace", "left"), key.WithHelp("←", "parent")), Back: key.NewBinding(key.WithKeys("backspace", "left"), key.WithHelp("←", "parent")),
Switch: key.NewBinding(key.WithKeys("tab", "h", "l"), key.WithHelp("Tab/h/l", "switch pane")), Switch: key.NewBinding(key.WithKeys("tab", "h", "l"), key.WithHelp("Tab/h/l", "switch pane")),

View file

@ -113,6 +113,7 @@ type copyDoneMsg struct {
} }
type dismissNoticeMsg struct{} type dismissNoticeMsg struct{}
type dismissYankFlashMsg struct{}
type externalOpenMsg struct { type externalOpenMsg struct {
path string path string
err error err error
@ -174,9 +175,10 @@ type Model struct {
status string status string
busy bool busy bool
lastClick mouseClickState lastClick mouseClickState
hover hoverState hover hoverState
pendingY bool pendingY bool
yankFlashLine int
copyJob *copyJobState copyJob *copyJobState
nextCopyJob int nextCopyJob int
@ -430,6 +432,11 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
return m, nil return m, nil
case dismissYankFlashMsg:
m.yankFlashLine = -1
m.syncPreviewContent()
return m, nil
case externalOpenMsg: case externalOpenMsg:
if msg.err != nil { if msg.err != nil {
m.status = fmt.Sprintf("Open failed: %v", msg.err) m.status = fmt.Sprintf("Open failed: %v", msg.err)
@ -523,7 +530,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.visualMode { if m.visualMode {
return m.exitVisualMode("Visual mode: off") return m.exitVisualMode("Visual mode: off")
} }
return m.toggleVisualMode() if m.cursorMode {
return m.toggleVisualMode()
}
return m, nil
case key.Matches(msg, m.keys.Cancel), msg.String() == "q": case key.Matches(msg, m.keys.Cancel), msg.String() == "q":
if m.visualMode { if m.visualMode {
return m.exitVisualMode("Visual mode: off") return m.exitVisualMode("Visual mode: off")
@ -606,7 +616,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case key.Matches(msg, m.keys.Caret): case key.Matches(msg, m.keys.Caret):
return m.toggleCaretMode() return m.toggleCaretMode()
case key.Matches(msg, m.keys.Visual): case key.Matches(msg, m.keys.Visual):
return m.toggleVisualMode() if m.cursorMode {
return m.toggleVisualMode()
}
return m, nil
case key.Matches(msg, m.keys.Edit): case key.Matches(msg, m.keys.Edit):
return m.handleEdit() return m.handleEdit()
case key.Matches(msg, m.keys.Info): case key.Matches(msg, m.keys.Info):
@ -1530,8 +1543,10 @@ func (m *Model) yankCursorLine() (tea.Model, tea.Cmd) {
m.status = fmt.Sprintf("Copy failed: %v", err) m.status = fmt.Sprintf("Copy failed: %v", err)
return m, nil return m, nil
} }
m.yankFlashLine = line
m.syncPreviewContent()
m.status = "Copied current line" m.status = "Copied current line"
return m, nil return m, dismissYankFlashCmd(140 * time.Millisecond)
} }
func (m *Model) cycleSort() (tea.Model, tea.Cmd) { func (m *Model) cycleSort() (tea.Model, tea.Cmd) {
@ -1613,7 +1628,6 @@ func (m *Model) openHelpModal() {
" k / Up move up", " k / Up move up",
" Shift+Down/J extend selection down", " Shift+Down/J extend selection down",
" Shift+Up/K extend selection up", " Shift+Up/K extend selection up",
" PgDn / f page down",
" PgUp / b page up", " PgUp / b page up",
" Enter / Right open selected entry", " Enter / Right open selected entry",
" Backspace/Left go to parent directory", " Backspace/Left go to parent directory",
@ -1625,9 +1639,13 @@ func (m *Model) openHelpModal() {
" F9 / o toggle preview/info pane", " F9 / o toggle preview/info pane",
" F3 plain text view or fullscreen image viewer", " F3 plain text view or fullscreen image viewer",
" i show text caret in preview pane", " i show text caret in preview pane",
" v visual selection in text preview pane", " v start visual selection from caret",
" F3 / Esc / q close view mode", " F3 / Esc / q close view mode",
" yy copy current line in caret mode",
" y copy visual selection to clipboard", " y copy visual selection to clipboard",
" h / l move caret left/right",
" w / b move caret by word",
" q / Esc close caret/info mode",
" Ctrl+t mouse selection mode in text preview pane", " Ctrl+t mouse selection mode in text preview pane",
" Space calculate selected directory size", " Space calculate selected directory size",
" s cycle sort mode", " s cycle sort mode",
@ -1759,6 +1777,10 @@ func (m *Model) renderTextCursorContent() string {
selected := lipgloss.NewStyle(). selected := lipgloss.NewStyle().
Background(m.palette.Marked). Background(m.palette.Marked).
Foreground(m.palette.Text) Foreground(m.palette.Text)
flashed := lipgloss.NewStyle().
Background(m.palette.Accent).
Foreground(m.palette.Background).
Bold(true)
cursor := lipgloss.NewStyle(). cursor := lipgloss.NewStyle().
Background(m.palette.Warning). Background(m.palette.Warning).
Foreground(m.palette.Background). Foreground(m.palette.Background).
@ -1829,6 +1851,10 @@ func (m *Model) renderTextCursorContent() string {
} }
line = left + mid + right line = left + mid + right
} }
if idx == m.yankFlashLine {
lines[idx] = flashed.Render(marker + line)
continue
}
lines[idx] = marker + line lines[idx] = marker + line
} }
return strings.Join(lines, "\n") return strings.Join(lines, "\n")
@ -2970,6 +2996,12 @@ func dismissNoticeCmd(delay time.Duration) tea.Cmd {
}) })
} }
func dismissYankFlashCmd(delay time.Duration) tea.Cmd {
return tea.Tick(delay, func(time.Time) tea.Msg {
return dismissYankFlashMsg{}
})
}
func (m *Model) startCopyJob(kind fileOpKind, sourcePaths []string, targetDir string, overwrite bool, stats vfs.TransferStats) tea.Cmd { func (m *Model) startCopyJob(kind fileOpKind, sourcePaths []string, targetDir string, overwrite bool, stats vfs.TransferStats) tea.Cmd {
m.nextCopyJob++ m.nextCopyJob++
jobID := m.nextCopyJob jobID := m.nextCopyJob