diff --git a/README.md b/README.md index 08860bd..1388be3 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,20 @@ go install github.com/dlvhdr/diffnav@latest git config --global pager.diff diffnav ``` +## Flags + +| Flag | Description | +|------|-------------| +| `--side-by-side, -s` | Force side-by-side diff view | +| `--unified, -u` | Force unified diff view | + +Example: + +```sh +git diff | diffnav --unified +git diff | diffnav -u +``` + ## Configuration The config file is searched in this order: @@ -76,6 +90,9 @@ ui: # Color filenames by git status (default: true) colorFileNames: false + + # Use side-by-side diff view (default: true, set false for unified) + sideBySide: true ``` | Option | Type | Default | Description | @@ -87,6 +104,7 @@ ui: | `ui.searchTreeWidth`| int | `50` | Width of the search panel | | `ui.icons` | string | `nerd-fonts` | Icon style: `nerd-fonts`, `nerd-fonts-alt`, `unicode`, or `ascii` | | `ui.colorFileNames` | bool | `true` | Color filenames by git status | +| `ui.sideBySide` | bool | `true` | Use side-by-side diff view (false for unified) | ### Delta @@ -107,6 +125,7 @@ If you want the exact delta configuration I'm using - [it can be found here](htt | y | Copy file path | | i | Cycle icon style | | o | Open file in $EDITOR | +| s | Toggle side-by-side/unified view | | Tab | Switch focus between the panes | | q | Quit | diff --git a/main.go b/main.go index f7a87e6..4363a76 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "flag" "fmt" "io" "os" @@ -19,6 +20,13 @@ import ( ) func main() { + // Parse CLI flags + sideBySideFlag := flag.Bool("side-by-side", false, "Force side-by-side diff view") + flag.BoolVar(sideBySideFlag, "s", false, "Force side-by-side diff view (shorthand)") + unifiedFlag := flag.Bool("unified", false, "Force unified diff view") + flag.BoolVar(unifiedFlag, "u", false, "Force unified diff view (shorthand)") + flag.Parse() + zone.NewGlobal() stat, err := os.Stdin.Stat() @@ -78,6 +86,14 @@ func main() { os.Exit(0) } cfg := config.Load() + + // Override config with CLI flags if specified + if *unifiedFlag { + cfg.UI.SideBySide = false + } else if *sideBySideFlag { + cfg.UI.SideBySide = true + } + p := tea.NewProgram(ui.New(input, cfg), tea.WithMouseAllMotion()) if _, err := p.Run(); err != nil { diff --git a/pkg/config/config.go b/pkg/config/config.go index c2da57a..360f66a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -16,6 +16,7 @@ type UIConfig struct { SearchTreeWidth int `yaml:"searchTreeWidth"` Icons string `yaml:"icons"` // "nerd-fonts" (default), "nerd-fonts-alt", "unicode", "ascii" ColorFileNames bool `yaml:"colorFileNames"` // Color filenames by git status (default: true) + SideBySide bool `yaml:"sideBySide"` // Side-by-side diff view (default: true) } type Config struct { @@ -32,6 +33,7 @@ func DefaultConfig() Config { SearchTreeWidth: 50, Icons: "nerd-fonts", ColorFileNames: true, + SideBySide: true, }, } } diff --git a/pkg/ui/keys.go b/pkg/ui/keys.go index 0a5de9f..3094674 100644 --- a/pkg/ui/keys.go +++ b/pkg/ui/keys.go @@ -13,6 +13,7 @@ type KeyMap struct { Copy key.Binding TogglePanel key.Binding OpenInEditor key.Binding + ToggleDiffView key.Binding } var keys = &KeyMap{ @@ -56,6 +57,10 @@ var keys = &KeyMap{ key.WithKeys("o"), key.WithHelp("o", "open"), ), + ToggleDiffView: key.NewBinding( + key.WithKeys("s"), + key.WithHelp("s", "toggle side-by-side"), + ), } func getKeys() []key.Binding { @@ -69,6 +74,7 @@ func getKeys() []key.Binding { keys.Search, keys.Copy, keys.OpenInEditor, + keys.ToggleDiffView, keys.Quit, } } diff --git a/pkg/ui/mainModel.go b/pkg/ui/mainModel.go index 88285c6..6d05caf 100644 --- a/pkg/ui/mainModel.go +++ b/pkg/ui/mainModel.go @@ -73,12 +73,13 @@ type mainModel struct { draggingSidebar bool customSidebarWidth int iconStyle string + sideBySide bool } func New(input string, cfg config.Config) mainModel { - m := mainModel{input: input, isShowingFileTree: cfg.UI.ShowFileTree, activePanel: FileTreePanel, config: cfg, iconStyle: cfg.UI.Icons} + m := mainModel{input: input, isShowingFileTree: cfg.UI.ShowFileTree, activePanel: FileTreePanel, config: cfg, iconStyle: cfg.UI.Icons, sideBySide: cfg.UI.SideBySide} m.fileTree = filetree.New(cfg.UI.Icons, cfg.UI.ColorFileNames) - m.diffViewer = diffviewer.New() + m.diffViewer = diffviewer.New(cfg.UI.SideBySide) m.help = help.New() helpSt := lipgloss.NewStyle() @@ -148,6 +149,10 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, dfCmd) case "i": m.cycleIconStyle() + case "s": + m.sideBySide = !m.sideBySide + cmd = m.diffViewer.SetSideBySide(m.sideBySide) + cmds = append(cmds, cmd) case "tab": if m.isShowingFileTree { if m.activePanel == FileTreePanel { @@ -371,7 +376,7 @@ func (m mainModel) View() string { // Width(0) means only the border is rendered (1 char). grabLine := lipgloss.NewStyle(). Width(0). - Height(m.height - m.footerHeight() - m.headerHeight() - 1). + Height(m.height-m.footerHeight()-m.headerHeight()-1). Border(lipgloss.NormalBorder(), false, true, false, false). BorderForeground(lipgloss.Color("8")). Render("") diff --git a/pkg/ui/panes/diffviewer/diffviewer.go b/pkg/ui/panes/diffviewer/diffviewer.go index 40549a5..24f3fb9 100644 --- a/pkg/ui/panes/diffviewer/diffviewer.go +++ b/pkg/ui/panes/diffviewer/diffviewer.go @@ -20,14 +20,16 @@ const dirHeaderHeight = 3 type Model struct { common.Common - vp viewport.Model - buffer *bytes.Buffer - file *gitdiff.File + vp viewport.Model + buffer *bytes.Buffer + file *gitdiff.File + sideBySide bool } -func New() Model { +func New(sideBySide bool) Model { return Model{ - vp: viewport.Model{}, + vp: viewport.Model{}, + sideBySide: sideBySide, } } @@ -76,7 +78,7 @@ func (m *Model) SetSize(width, height int) tea.Cmd { m.Height = height m.vp.Width = m.Width m.vp.Height = m.Height - dirHeaderHeight - return diff(m.file, m.Width) + return diff(m.file, m.Width, m.sideBySide) } func (m Model) headerView() string { @@ -117,13 +119,19 @@ func (m Model) headerView() string { func (m Model) SetFilePatch(file *gitdiff.File) (Model, tea.Cmd) { m.buffer = new(bytes.Buffer) m.file = file - return m, diff(m.file, m.Width) + return m, diff(m.file, m.Width, m.sideBySide) } func (m *Model) GoToTop() { m.vp.GotoTop() } +// SetSideBySide updates the diff view mode and re-renders. +func (m *Model) SetSideBySide(sideBySide bool) tea.Cmd { + m.sideBySide = sideBySide + return diff(m.file, m.Width, m.sideBySide) +} + func (m *Model) LineUp(n int) { m.vp.LineUp(n) } @@ -142,19 +150,19 @@ func (m *Model) ScrollDown(lines int) { m.vp.LineDown(lines) } - -func diff(file *gitdiff.File, width int) tea.Cmd { +func diff(file *gitdiff.File, width int, sideBySidePreference bool) tea.Cmd { if width == 0 || file == nil { return nil } return func() tea.Msg { - sideBySide := !file.IsNew && !file.IsDelete + // Only use side-by-side if preference is true AND file is not new/deleted + useSideBySide := sideBySidePreference && !file.IsNew && !file.IsDelete args := []string{ "--paging=never", fmt.Sprintf("-w=%d", width), fmt.Sprintf("--max-line-length=%d", width), } - if sideBySide { + if useSideBySide { args = append(args, "--side-by-side") } deltac := exec.Command("delta", args...)