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...)