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")),
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")),
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")),
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")),

View file

@ -113,6 +113,7 @@ type copyDoneMsg struct {
}
type dismissNoticeMsg struct{}
type dismissYankFlashMsg struct{}
type externalOpenMsg struct {
path string
err error
@ -177,6 +178,7 @@ type Model struct {
lastClick mouseClickState
hover hoverState
pendingY bool
yankFlashLine int
copyJob *copyJobState
nextCopyJob int
@ -430,6 +432,11 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
return m, nil
case dismissYankFlashMsg:
m.yankFlashLine = -1
m.syncPreviewContent()
return m, nil
case externalOpenMsg:
if msg.err != nil {
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 {
return m.exitVisualMode("Visual mode: off")
}
if m.cursorMode {
return m.toggleVisualMode()
}
return m, nil
case key.Matches(msg, m.keys.Cancel), msg.String() == "q":
if m.visualMode {
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):
return m.toggleCaretMode()
case key.Matches(msg, m.keys.Visual):
if m.cursorMode {
return m.toggleVisualMode()
}
return m, nil
case key.Matches(msg, m.keys.Edit):
return m.handleEdit()
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)
return m, nil
}
m.yankFlashLine = line
m.syncPreviewContent()
m.status = "Copied current line"
return m, nil
return m, dismissYankFlashCmd(140 * time.Millisecond)
}
func (m *Model) cycleSort() (tea.Model, tea.Cmd) {
@ -1613,7 +1628,6 @@ func (m *Model) openHelpModal() {
" k / Up move up",
" Shift+Down/J extend selection down",
" Shift+Up/K extend selection up",
" PgDn / f page down",
" PgUp / b page up",
" Enter / Right open selected entry",
" Backspace/Left go to parent directory",
@ -1625,9 +1639,13 @@ func (m *Model) openHelpModal() {
" F9 / o toggle preview/info pane",
" F3 plain text view or fullscreen image viewer",
" 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",
" yy copy current line in caret mode",
" 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",
" Space calculate selected directory size",
" s cycle sort mode",
@ -1759,6 +1777,10 @@ func (m *Model) renderTextCursorContent() string {
selected := lipgloss.NewStyle().
Background(m.palette.Marked).
Foreground(m.palette.Text)
flashed := lipgloss.NewStyle().
Background(m.palette.Accent).
Foreground(m.palette.Background).
Bold(true)
cursor := lipgloss.NewStyle().
Background(m.palette.Warning).
Foreground(m.palette.Background).
@ -1829,6 +1851,10 @@ func (m *Model) renderTextCursorContent() string {
}
line = left + mid + right
}
if idx == m.yankFlashLine {
lines[idx] = flashed.Render(marker + line)
continue
}
lines[idx] = marker + line
}
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 {
m.nextCopyJob++
jobID := m.nextCopyJob