Adjust view/info keybindings and improve read-only view mode

This commit is contained in:
vrubelroman 2026-04-24 10:08:33 +03:00
parent 3c2016eaf0
commit d22a40e987
2 changed files with 76 additions and 21 deletions

View file

@ -38,7 +38,7 @@ func DefaultKeyMap() KeyMap {
Help: key.NewBinding(key.WithKeys("f1", "?"), key.WithHelp("F1/?", "help")), Help: key.NewBinding(key.WithKeys("f1", "?"), key.WithHelp("F1/?", "help")),
View: key.NewBinding(key.WithKeys("f3", "v"), key.WithHelp("F3/v", "view")), View: key.NewBinding(key.WithKeys("f3", "v"), key.WithHelp("F3/v", "view")),
Edit: key.NewBinding(key.WithKeys("f4", "e"), key.WithHelp("F4/e", "edit")), Edit: key.NewBinding(key.WithKeys("f4", "e"), key.WithHelp("F4/e", "edit")),
Info: key.NewBinding(key.WithKeys("i"), key.WithHelp("i", "info")), Info: key.NewBinding(key.WithKeys("f9", "i"), key.WithHelp("F9/i", "info")),
SelectText: key.NewBinding(key.WithKeys("ctrl+t"), key.WithHelp("C-t", "text select")), SelectText: key.NewBinding(key.WithKeys("ctrl+t"), key.WithHelp("C-t", "text select")),
ToggleHidden: key.NewBinding(key.WithKeys("."), key.WithHelp(".", "hidden")), ToggleHidden: key.NewBinding(key.WithKeys("."), key.WithHelp(".", "hidden")),
CycleTheme: key.NewBinding(key.WithKeys("t"), key.WithHelp("t", "theme")), CycleTheme: key.NewBinding(key.WithKeys("t"), key.WithHelp("t", "theme")),
@ -67,7 +67,7 @@ func DefaultKeyMap() KeyMap {
} }
func (k KeyMap) ShortHelp() []key.Binding { func (k KeyMap) ShortHelp() []key.Binding {
return []key.Binding{k.Help, k.Copy, k.Move, k.Delete, k.Quit} return []key.Binding{k.Help, k.View, k.Copy, k.Move, k.Delete, k.Info, k.Quit}
} }
func (k KeyMap) FullHelp() [][]key.Binding { func (k KeyMap) FullHelp() [][]key.Binding {

View file

@ -148,6 +148,8 @@ type Model struct {
active PaneID active PaneID
infoMode bool infoMode bool
selectMode bool selectMode bool
viewMode bool
viewPrevInfo bool
previewModel viewport.Model previewModel viewport.Model
previewData vfs.Preview previewData vfs.Preview
@ -223,7 +225,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if selected, ok := m.activePane().Selected(); ok && selected.Path == msg.entryPath { if selected, ok := m.activePane().Selected(); ok && selected.Path == msg.entryPath {
m.applyPreview(msg.preview) m.applyPreview(msg.preview)
} }
if m.selectMode && msg.preview.Kind != vfs.PreviewKindText { if m.selectMode && !m.viewMode && msg.preview.Kind != vfs.PreviewKindText {
m.selectMode = false m.selectMode = false
return m, enableMouseCmd() return m, enableMouseCmd()
} }
@ -394,6 +396,26 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.modal.kind != modalNone { if m.modal.kind != modalNone {
return m.handleModalKey(msg) return m.handleModalKey(msg)
} }
if m.viewMode {
switch {
case key.Matches(msg, m.keys.View), key.Matches(msg, m.keys.Cancel), msg.String() == "q":
return m.exitViewMode()
case key.Matches(msg, m.keys.Up):
m.previewModel.LineUp(1)
return m, nil
case key.Matches(msg, m.keys.Down):
m.previewModel.LineDown(1)
return m, nil
case key.Matches(msg, m.keys.PageUp):
m.previewModel.LineUp(max(m.previewModel.Height-2, 1))
return m, nil
case key.Matches(msg, m.keys.PageDown):
m.previewModel.LineDown(max(m.previewModel.Height-2, 1))
return m, nil
default:
return m, nil
}
}
switch { switch {
case key.Matches(msg, m.keys.Quit): case key.Matches(msg, m.keys.Quit):
@ -525,7 +547,7 @@ func (m Model) View() string {
parts := make([]string, 0, 3) parts := make([]string, 0, 3)
parts = append(parts, panels) parts = append(parts, panels)
if m.cfg.UI.ShowFooter { if m.cfg.UI.ShowFooter && !m.viewMode {
parts = append(parts, renderFooter(m)) parts = append(parts, renderFooter(m))
} }
@ -871,20 +893,34 @@ func (m *Model) handleDelete() (tea.Model, tea.Cmd) {
func (m *Model) handleView() (tea.Model, tea.Cmd) { func (m *Model) handleView() (tea.Model, tea.Cmd) {
selected, ok := m.activePane().Selected() selected, ok := m.activePane().Selected()
if !ok || selected.IsParent || selected.IsDir { if !ok || selected.IsParent || selected.IsDir {
m.status = "Preview refreshed" m.status = "Select a file to view"
return m, m.loadPreviewCmd() return m, nil
}
if m.viewMode {
return m.exitViewMode()
} }
command, name, err := externalCommand("PAGER", []string{"less", "more"}, selected.Path) m.viewPrevInfo = m.infoMode
if err != nil { m.infoMode = true
m.status = "Preview refreshed in center pane" m.selectMode = true
return m, m.loadPreviewCmd() m.viewMode = true
} m.resizePreview()
m.syncPreviewContent()
m.status = "View mode: F3/Esc/q to close"
return m, tea.Batch(m.loadPreviewCmd(), disableMouseCmd())
}
m.status = fmt.Sprintf("Opening %s with %s", selected.DisplayName(), name) func (m *Model) exitViewMode() (tea.Model, tea.Cmd) {
return m, tea.ExecProcess(command, func(err error) tea.Msg { if !m.viewMode {
return opMsg{kind: opView, sourcePath: selected.Path, err: err} return m, nil
}) }
m.viewMode = false
m.selectMode = false
m.infoMode = m.viewPrevInfo
m.resizePreview()
m.syncPreviewContent()
m.status = "View mode: off"
return m, tea.Batch(m.loadPreviewCmd(), enableMouseCmd())
} }
func (m *Model) handleOpenExternal() (tea.Model, tea.Cmd) { func (m *Model) handleOpenExternal() (tea.Model, tea.Cmd) {
@ -926,6 +962,19 @@ func (m *Model) handleEdit() (tea.Model, tea.Cmd) {
} }
func (m *Model) handleMouse(msg tea.MouseMsg) (tea.Model, tea.Cmd) { func (m *Model) handleMouse(msg tea.MouseMsg) (tea.Model, tea.Cmd) {
if m.viewMode {
switch {
case msg.Action == tea.MouseActionPress && msg.Button == tea.MouseButtonWheelUp:
m.previewModel.LineUp(3)
return m, nil
case msg.Action == tea.MouseActionPress && msg.Button == tea.MouseButtonWheelDown:
m.previewModel.LineDown(3)
return m, nil
default:
return m, nil
}
}
switch { switch {
case msg.Action == tea.MouseActionMotion: case msg.Action == tea.MouseActionMotion:
paneID, index, ok := m.mouseTarget(msg.X, msg.Y) paneID, index, ok := m.mouseTarget(msg.X, msg.Y)
@ -1033,6 +1082,10 @@ func (m *Model) toggleInfo() (tea.Model, tea.Cmd) {
} }
func (m *Model) toggleSelectMode() (tea.Model, tea.Cmd) { func (m *Model) toggleSelectMode() (tea.Model, tea.Cmd) {
if m.viewMode {
m.status = "Close view mode first (F3/Esc/q)"
return m, nil
}
if m.selectMode { if m.selectMode {
m.selectMode = false m.selectMode = false
m.status = "Text selection mode: off" m.status = "Text selection mode: off"
@ -1120,7 +1173,9 @@ func (m *Model) openHelpModal() {
" r refresh both panes", " r refresh both panes",
"", "",
"View and Panels", "View and Panels",
" i toggle preview/info pane", " F9 / i toggle preview/info pane",
" F3 / v open read-only view mode",
" F3 / Esc / q close view mode",
" Ctrl+t toggle text selection mode in text preview", " Ctrl+t toggle text selection mode in text preview",
" Space calculate selected directory size", " Space calculate selected directory size",
" s cycle sort mode", " s cycle sort mode",