diff --git a/pkg/ui/keys.go b/pkg/ui/keys.go index 32bb9be..0969e75 100644 --- a/pkg/ui/keys.go +++ b/pkg/ui/keys.go @@ -14,6 +14,8 @@ type KeyMap struct { PrevFile key.Binding CtrlD key.Binding CtrlU key.Binding + ScrollLeft key.Binding + ScrollRight key.Binding ToggleFileTree key.Binding Search key.Binding Quit key.Binding @@ -71,6 +73,14 @@ var keys = &KeyMap{ key.WithKeys("ctrl+u"), key.WithHelp("ctrl+u", "diff up"), ), + ScrollLeft: key.NewBinding( + key.WithKeys("left"), + key.WithHelp("←", "scroll left"), + ), + ScrollRight: key.NewBinding( + key.WithKeys("right"), + key.WithHelp("→", "scroll right"), + ), ToggleFileTree: key.NewBinding( key.WithKeys("e"), key.WithHelp("e", "toggle file tree"), @@ -124,6 +134,8 @@ func KeyGroups() [][]key.Binding { keys.PrevFile, keys.CtrlD, keys.CtrlU, + keys.ScrollLeft, + keys.ScrollRight, }, { keys.ToggleFileTree, keys.Search, diff --git a/pkg/ui/panes/diffviewer/diffviewer.go b/pkg/ui/panes/diffviewer/diffviewer.go index c69c7bf..f7638a8 100644 --- a/pkg/ui/panes/diffviewer/diffviewer.go +++ b/pkg/ui/panes/diffviewer/diffviewer.go @@ -10,7 +10,6 @@ import ( tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" "github.com/bluekeyes/go-gitdiff/gitdiff" - "github.com/charmbracelet/x/ansi" "github.com/dlvhdr/diffnav/pkg/filenode" "github.com/dlvhdr/diffnav/pkg/icons" @@ -80,18 +79,10 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { } case diffContentMsg: - // Truncate lines to viewport width to prevent ANSI escape overflow. - lines := strings.Split(msg.text, "\n") - for i, line := range lines { - if lipgloss.Width(line) > m.vp.Width() && m.vp.Width() > 0 { - lines[i] = ansi.Truncate(line, m.vp.Width(), "") - } - } - diff := strings.Join(lines, "\n") if _, ok := m.cache[msg.cacheKey]; ok { - m.cache[msg.cacheKey].diff = diff + m.cache[msg.cacheKey].diff = msg.text } - m.vp.SetContent(diff) + m.vp.SetContent(msg.text) } return m, tea.Batch(cmds...) @@ -291,6 +282,16 @@ func (m *Model) ScrollTop() { m.vp.GotoTop() } +// ScrollLeft scrolls the viewport one column toward column 0. +func (m *Model) ScrollLeft() { + m.vp.ScrollLeft(1) +} + +// ScrollRight scrolls the viewport one column away from column 0. +func (m *Model) ScrollRight() { + m.vp.ScrollRight(1) +} + func diffFile(node *cachedNode, width int, sideBySide bool) tea.Cmd { if width == 0 || node == nil || len(node.files) != 1 { return nil @@ -304,7 +305,14 @@ func diffFile(node *cachedNode, width int, sideBySide bool) tea.Cmd { args := []string{ "--paging=never", fmt.Sprintf("-w=%d", width), - fmt.Sprintf("--max-line-length=%d", width), + // Disable hard truncation and let delta's own line-wrapping (active + // in side-by-side mode) carry the full line through. With + // `--max-line-length=` and the default `--wrap-max-lines=2`, + // long lines were being clipped at the viewport before we saw + // them. Anything still wider than the viewport gets clipped with + // a visible "…" marker in the `diffContentMsg` handler. + "--max-line-length=0", + "--wrap-max-lines=unlimited", } if useSideBySide { args = append(args, "--side-by-side") @@ -340,7 +348,9 @@ func diffDir(dir *cachedNode, width int, sideBySide bool, preamble string) tea.C fmt.Sprintf("--file-style='%s bold %s'", c, c), fmt.Sprintf("--file-decoration-style='%s box %s'", c, c), fmt.Sprintf("-w=%d", width), - fmt.Sprintf("--max-line-length=%d", width), + // See `diffFile` for why these are set this way. + "--max-line-length=0", + "--wrap-max-lines=unlimited", } if useSideBySide { args = append(args, "--side-by-side") diff --git a/pkg/ui/tui.go b/pkg/ui/tui.go index feb7bda..35bc05c 100644 --- a/pkg/ui/tui.go +++ b/pkg/ui/tui.go @@ -303,6 +303,14 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } else { m.diffViewer.ScrollTop() } + case key.Matches(msg, keys.ScrollLeft): + if m.activePanel != FileTreePanel { + m.diffViewer.ScrollLeft() + } + case key.Matches(msg, keys.ScrollRight): + if m.activePanel != FileTreePanel { + m.diffViewer.ScrollRight() + } case key.Matches(msg, keys.Copy): cmd = m.fileTree.CopyCurrNodePath() if cmd != nil {