Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/show file diff #42

Merged
merged 8 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ deploy: clean build

clean:
go clean
rm ${OUTPUT_BIN}
rm -f ${OUTPUT_BIN}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/oklog/run v1.1.0
github.com/pterm/pterm v0.12.79
github.com/rivo/tview v0.0.0-20240728114935-65571ae51e71
github.com/sergi/go-diff v1.2.0
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
Expand Down
6 changes: 3 additions & 3 deletions internal/ui/dialog/delete_file_dialog.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ func NewDeleteFileDialog(application *tview.Application, file *data.FileBrowserE
func (d *DeleteFileDialog) createLayout() {
dialogTitle := " Delete File "

textDesctiption := fmt.Sprintf("Delete '%s'?", d.file.Name)
textDesctiptionView := tview.NewTextView().SetText(textDesctiption)
textDescription := fmt.Sprintf("Delete '%s'?", d.file.Name)
textDescriptionView := tview.NewTextView().SetText(textDescription)

optionTable := tview.NewTable()
optionTable.SetSelectable(true, false)
Expand Down Expand Up @@ -94,7 +94,7 @@ func (d *DeleteFileDialog) createLayout() {
}

dialogContent := tview.NewFlex().SetDirection(tview.FlexRow)
dialogContent.AddItem(textDesctiptionView, 0, 1, false)
dialogContent.AddItem(textDescriptionView, 0, 1, false)
dialogContent.AddItem(optionTable, 0, 1, true)

dialog := createModal(dialogTitle, dialogContent, 50, 6)
Expand Down
6 changes: 3 additions & 3 deletions internal/ui/dialog/delete_snapshot_dialog.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ func NewDeleteSnapshotDialog(application *tview.Application, snapshot *data.Snap
func (d *DeleteSnapshotDialog) createLayout() {
dialogTitle := " Destroy Snapshot "

textDesctiption := fmt.Sprintf("Destroy '%s'?", d.snapshot.Snapshot.Name)
textDesctiptionView := tview.NewTextView().SetText(textDesctiption)
textDescription := fmt.Sprintf("Destroy '%s'?", d.snapshot.Snapshot.Name)
textDescriptionView := tview.NewTextView().SetText(textDescription)

optionTable := tview.NewTable()
optionTable.SetSelectable(true, false)
Expand Down Expand Up @@ -92,7 +92,7 @@ func (d *DeleteSnapshotDialog) createLayout() {
}

dialogContent := tview.NewFlex().SetDirection(tview.FlexRow)
dialogContent.AddItem(textDesctiptionView, 0, 1, false)
dialogContent.AddItem(textDescriptionView, 0, 1, false)
dialogContent.AddItem(optionTable, 0, 1, true)

dialog := createModal(dialogTitle, dialogContent, 50, 6)
Expand Down
36 changes: 31 additions & 5 deletions internal/ui/dialog/file_action_dialog.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@ import (
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"golang.org/x/exp/slices"
"os/exec"
"zfs-file-history/internal/data"
"zfs-file-history/internal/data/diff_state"
"zfs-file-history/internal/ui/util"
)

const (
ActionDialog util.Page = "ActionDialog"

// recursively restores all files and folders top to bottom starting with the given entry
FileDialogRestoreFileActionId DialogActionId = iota
FileDialogShowDiffActionId DialogActionId = iota
FileDialogRestoreFileActionId
FileDialogRestoreRecursiveDialogActionId
FileDialogDeleteDialogActionId
FileDialogCreateSnapshotDialogActionId
Expand Down Expand Up @@ -41,8 +44,8 @@ func NewFileActionDialog(application *tview.Application, file *data.FileBrowserE
func (d *FileActionDialog) createLayout() {
dialogTitle := " Select Action "

textDesctiption := fmt.Sprintf("What do you want to do with '%s'?", d.file.Name)
textDesctiptionView := tview.NewTextView().SetText(textDesctiption)
textDescription := fmt.Sprintf("What do you want to do with '%s'?", d.file.Name)
textDescriptionView := tview.NewTextView().SetText(textDescription)

optionTable := tview.NewTable()
optionTable.SetSelectable(true, false)
Expand Down Expand Up @@ -75,7 +78,13 @@ func (d *FileActionDialog) createLayout() {
}

if d.file.Type == data.File {
dialogOptions = slices.Insert(dialogOptions, 0, &DialogOption{
if DiffBinExists() && d.file.DiffState == diff_state.Modified {
dialogOptions = slices.Insert(dialogOptions, 0, &DialogOption{
Id: FileDialogShowDiffActionId,
Name: fmt.Sprintf("Show diff"),
})
}
dialogOptions = slices.Insert(dialogOptions, 1, &DialogOption{
Id: FileDialogRestoreFileActionId,
Name: fmt.Sprintf("Restore file"),
})
Expand Down Expand Up @@ -123,7 +132,7 @@ func (d *FileActionDialog) createLayout() {
}

dialogContent := tview.NewFlex().SetDirection(tview.FlexRow)
dialogContent.AddItem(textDesctiptionView, 0, 1, false)
dialogContent.AddItem(textDescriptionView, 0, 1, false)
dialogContent.AddItem(optionTable, 0, 1, true)

dialog := createModal(dialogTitle, dialogContent, 50, 15)
Expand All @@ -142,6 +151,14 @@ func (d *FileActionDialog) createLayout() {
d.layout = dialog
}

func DiffBinExists() bool {
_, err := exec.LookPath(DiffBinPath)
if err != nil {
return false
}
return true
}

func (d *FileActionDialog) GetName() string {
return string(ActionDialog)
}
Expand Down Expand Up @@ -169,6 +186,8 @@ func (d *FileActionDialog) RestoreFile() {

func (d *FileActionDialog) selectAction(option *DialogOption) {
switch option.Id {
case FileDialogShowDiffActionId:
d.ShowDiff()
case FileDialogRestoreFileActionId:
d.RestoreFile()
case FileDialogRestoreRecursiveDialogActionId:
Expand Down Expand Up @@ -202,3 +221,10 @@ func (d *FileActionDialog) CreateSnapshot() {
d.actionChannel <- FileDialogCreateSnapshotDialogActionId
}()
}

func (d *FileActionDialog) ShowDiff() {
go func() {
d.actionChannel <- DialogCloseActionId
d.actionChannel <- FileDialogShowDiffActionId
}()
}
113 changes: 113 additions & 0 deletions internal/ui/dialog/file_diff_dialog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package dialog

import (
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"os/exec"
"strings"
"zfs-file-history/internal/data"
"zfs-file-history/internal/ui/util"
)

const (
DiffBinPath = "/usr/bin/diff"

FileDiffDialogPage util.Page = "FileDiffDialog"
)

type FileDiffDialog struct {
application *tview.Application
file *data.FileBrowserEntry
snapshot *data.SnapshotBrowserEntry
layout *tview.Flex
actionChannel chan DialogActionId
}

func NewFileDiffDialog(application *tview.Application, file *data.FileBrowserEntry, snapshot *data.SnapshotBrowserEntry) *FileDiffDialog {
dialog := &FileDiffDialog{
application: application,
file: file,
snapshot: snapshot,
actionChannel: make(chan DialogActionId),
}

dialog.createLayout()

return dialog
}

func (d *FileDiffDialog) createLayout() {
dialogTitle := " File Diff "

realFilePath := d.file.RealFile.Path
snapshotFilePath := d.snapshot.Snapshot.GetSnapshotPath(d.file.RealFile.Path)

output, err := exec.Command(
DiffBinPath,
"-U", "3",
snapshotFilePath,
realFilePath,
).Output()
diffText := string(output)
if err != nil && err.Error() != "exit status 1" {
diffText = "error calculating diff: " + err.Error()
}

diffTextLines := strings.Split(diffText, "\n")
for i := 0; i < len(diffTextLines); i++ {
line := diffTextLines[i]
if strings.HasPrefix(line, "+") {
diffTextLines[i] = `[green]` + line + `[white]`
}
if strings.HasPrefix(line, "-") {
diffTextLines[i] = `[red]` + line + `[white]`
}
}
diffText = strings.Join(diffTextLines, "\n")

textDescriptionView := tview.NewTextView().
SetDynamicColors(true).
SetRegions(true).
SetChangedFunc(func() {
d.application.Draw()
})

textDescriptionView.SetText(diffText)

closeTextView := util.CreateAttentionTextView("Press 'esc' to close")

dialogContent := tview.NewFlex().SetDirection(tview.FlexRow)
dialogContent.AddItem(textDescriptionView, 0, 1, true)
dialogContent.AddItem(closeTextView, 1, 0, false)
dialogContent.SetBorderPadding(0, 0, 1, 1)

width := 80
height := 20
dialog := createModal(dialogTitle, dialogContent, width, height)
dialog.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Key() == tcell.KeyEscape {
d.Close()
return nil
}
return event
})
d.layout = dialog
}

func (d *FileDiffDialog) GetName() string {
return string(FileDiffDialogPage)
}

func (d *FileDiffDialog) GetLayout() *tview.Flex {
return d.layout
}

func (d *FileDiffDialog) GetActionChannel() <-chan DialogActionId {
return d.actionChannel
}

func (d *FileDiffDialog) Close() {
go func() {
d.actionChannel <- DialogCloseActionId
}()
}
6 changes: 3 additions & 3 deletions internal/ui/dialog/snapshot_action_dialog.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ func NewSnapshotActionDialog(application *tview.Application, snapshot *data.Snap
func (d *SnapshotActionDialog) createLayout() {
dialogTitle := " Select Action "

textDesctiption := fmt.Sprintf("What do you want to do with '%s'?", d.snapshot.Snapshot.Name)
textDesctiptionView := tview.NewTextView().SetText(textDesctiption)
textDescription := fmt.Sprintf("What do you want to do with '%s'?", d.snapshot.Snapshot.Name)
textDescriptionView := tview.NewTextView().SetText(textDescription)

optionTable := tview.NewTable()
optionTable.SetSelectable(true, false)
Expand Down Expand Up @@ -100,7 +100,7 @@ func (d *SnapshotActionDialog) createLayout() {
}

dialogContent := tview.NewFlex().SetDirection(tview.FlexRow)
dialogContent.AddItem(textDesctiptionView, 0, 1, false)
dialogContent.AddItem(textDescriptionView, 0, 1, false)
dialogContent.AddItem(optionTable, 0, 1, true)

dialog := createModal(dialogTitle, dialogContent, 50, 10)
Expand Down
18 changes: 18 additions & 0 deletions internal/ui/file_browser/file_browser.go
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,9 @@ func (fileBrowser *FileBrowserComponent) openActionDialog(selection *data.FileBr
actionDialogLayout := dialog.NewFileActionDialog(fileBrowser.application, selection)
actionHandler := func(action dialog.DialogActionId) bool {
switch action {
case dialog.FileDialogShowDiffActionId:
fileBrowser.showDiff(selection, fileBrowser.currentSnapshot)
return true
case dialog.FileDialogCreateSnapshotDialogActionId:
fileBrowser.createSnapshot(selection)
return true
Expand Down Expand Up @@ -688,6 +691,7 @@ func (fileBrowser *FileBrowserComponent) showDialog(d dialog.Dialog, actionHandl
}
if action == dialog.DialogCloseActionId {
fileBrowser.layout.RemovePage(d.GetName())
fileBrowser.application.Draw()
}
}
}()
Expand Down Expand Up @@ -716,6 +720,20 @@ func (fileBrowser *FileBrowserComponent) runRestoreFileAction(entry *data.FileBr
})
}

func (fileBrowser *FileBrowserComponent) showDiff(selection *data.FileBrowserEntry, snapshot *data.SnapshotBrowserEntry) {
if selection == nil || snapshot == nil {
return
}
d := dialog.NewFileDiffDialog(fileBrowser.application, selection, snapshot)
fileBrowser.showDialog(d, func(action dialog.DialogActionId) bool {
switch action {
case dialog.DialogCloseActionId:
fileBrowser.Refresh()
}
return false
})
}

func (fileBrowser *FileBrowserComponent) delete(entry *data.FileBrowserEntry) {
go func() {
path := entry.RealFile.Path
Expand Down
3 changes: 2 additions & 1 deletion internal/ui/snapshot_browser/snapshot_browser.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ func (snapshotBrowser *SnapshotBrowserComponent) Refresh(force bool) {
func (snapshotBrowser *SnapshotBrowserComponent) SetFileEntry(fileEntry *data.FileBrowserEntry) {
if fileEntry != nil &&
snapshotBrowser.currentFileEntry != nil &&
snapshotBrowser.currentFileEntry.GetRealPath() == fileEntry.GetRealPath() {
snapshotBrowser.currentFileEntry.GetRealPath() == fileEntry.GetRealPath() &&
snapshotBrowser.currentFileEntry.DiffState == fileEntry.DiffState {
return
}
snapshotBrowser.currentFileEntry = fileEntry
Expand Down