1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-01-04 03:48:07 +02:00

feat: introduce a copy menu into the file view

This commit is contained in:
AzraelSec 2023-11-02 23:31:38 +01:00 committed by Stefan Haller
parent 2162e5ff64
commit c7012528fc
8 changed files with 351 additions and 0 deletions

View File

@ -17,3 +17,26 @@ func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj {
NewGitCmd("diff").Arg("--submodule", "--no-ext-diff", "--color").Arg(diffArgs...).ToArgv(),
)
}
func (self *DiffCommands) internalDiffCmdObj(diffArgs ...string) *GitCommandBuilder {
return NewGitCmd("diff").
Arg("--no-ext-diff", "--no-color").
Arg(diffArgs...)
}
func (self *DiffCommands) GetPathDiff(path string, staged bool) (string, error) {
return self.cmd.New(
self.internalDiffCmdObj().
ArgIf(staged, "--staged").
Arg(path).
ToArgv(),
).RunWithOutput()
}
func (self *DiffCommands) GetAllDiff(staged bool) (string, error) {
return self.cmd.New(
self.internalDiffCmdObj().
ArgIf(staged, "--staged").
ToArgv(),
).RunWithOutput()
}

View File

@ -371,6 +371,7 @@ type KeybindingFilesConfig struct {
ToggleTreeView string `yaml:"toggleTreeView"`
OpenMergeTool string `yaml:"openMergeTool"`
OpenStatusFilter string `yaml:"openStatusFilter"`
CopyFileInfoToClipboard string `yaml:"copyFileInfoToClipboard"`
}
type KeybindingBranchesConfig struct {
@ -763,6 +764,7 @@ func GetDefaultConfig() *UserConfig {
OpenMergeTool: "M",
OpenStatusFilter: "<c-b>",
ConfirmDiscard: "x",
CopyFileInfoToClipboard: "y",
},
Branches: KeybindingBranchesConfig{
CopyPullRequestURL: "<c-y>",

View File

@ -37,6 +37,12 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
Handler: self.handleStatusFilterPressed,
Description: self.c.Tr.FileFilter,
},
{
Key: opts.GetKey(opts.Config.Files.CopyFileInfoToClipboard),
Handler: self.openCopyMenu,
Description: self.c.Tr.CopyToClipboardMenu,
OpensMenu: true,
},
{
Key: opts.GetKey(opts.Config.Files.CommitChanges),
Handler: self.c.Helpers().WorkingTree.HandleCommitPress,
@ -748,6 +754,103 @@ func (self *FilesController) createStashMenu() error {
})
}
func (self *FilesController) openCopyMenu() error {
node := self.context().GetSelected()
copyNameItem := &types.MenuItem{
Label: self.c.Tr.CopyFileName,
OnPress: func() error {
if err := self.c.OS().CopyToClipboard(node.Name()); err != nil {
return self.c.Error(err)
}
self.c.Toast(self.c.Tr.FileNameCopiedToast)
return nil
},
Key: 'n',
}
copyPathItem := &types.MenuItem{
Label: self.c.Tr.CopyFilePath,
OnPress: func() error {
if err := self.c.OS().CopyToClipboard(node.Path); err != nil {
return self.c.Error(err)
}
self.c.Toast(self.c.Tr.FilePathCopiedToast)
return nil
},
Key: 'p',
}
copyFileDiffItem := &types.MenuItem{
Label: self.c.Tr.CopySelectedDiff,
Tooltip: self.c.Tr.CopyFileDiffTooltip,
OnPress: func() error {
path := self.context().GetSelectedPath()
hasStaged := self.hasPathStagedChanges(node)
diff, err := self.c.Git().Diff.GetPathDiff(path, hasStaged)
if err != nil {
return self.c.Error(err)
}
if err := self.c.OS().CopyToClipboard(diff); err != nil {
return self.c.Error(err)
}
self.c.Toast(self.c.Tr.FileDiffCopiedToast)
return nil
},
Key: 's',
}
copyAllDiff := &types.MenuItem{
Label: self.c.Tr.CopyAllFilesDiff,
Tooltip: self.c.Tr.CopyFileDiffTooltip,
OnPress: func() error {
hasStaged := self.c.Helpers().WorkingTree.AnyStagedFiles()
diff, err := self.c.Git().Diff.GetAllDiff(hasStaged)
if err != nil {
return self.c.Error(err)
}
if err := self.c.OS().CopyToClipboard(diff); err != nil {
return self.c.Error(err)
}
self.c.Toast(self.c.Tr.AllFilesDiffCopiedToast)
return nil
},
Key: 'a',
}
if node == nil {
copyNameItem.DisabledReason = self.c.Tr.NoContentToCopyError
copyPathItem.DisabledReason = self.c.Tr.NoContentToCopyError
copyFileDiffItem.DisabledReason = self.c.Tr.NoContentToCopyError
}
if node != nil && !node.GetHasStagedOrTrackedChanges() {
copyFileDiffItem.DisabledReason = self.c.Tr.NoContentToCopyError
}
if !self.anyStagedOrTrackedFile() {
copyAllDiff.DisabledReason = self.c.Tr.NoContentToCopyError
}
return self.c.Menu(types.CreateMenuOptions{
Title: self.c.Tr.CopyToClipboardMenu,
Items: []*types.MenuItem{
copyNameItem,
copyPathItem,
copyFileDiffItem,
copyAllDiff,
},
})
}
func (self *FilesController) anyStagedOrTrackedFile() bool {
if !self.c.Helpers().WorkingTree.AnyStagedFiles() {
return self.c.Helpers().WorkingTree.AnyTrackedFiles()
}
return true
}
func (self *FilesController) hasPathStagedChanges(node *filetree.FileNode) bool {
return node.SomeFile(func(t *models.File) bool {
return t.HasStagedChanges
})
}
func (self *FilesController) stash() error {
return self.handleStashSave(self.c.Git().Stash.Push, self.c.Tr.Actions.StashAllChanges)
}

View File

@ -30,6 +30,15 @@ func (self *FileNode) GetHasUnstagedChanges() bool {
return self.SomeFile(func(file *models.File) bool { return file.HasUnstagedChanges })
}
func (self *FileNode) GetHasStagedOrTrackedChanges() bool {
if !self.GetHasStagedChanges() {
return self.SomeFile(func(t *models.File) bool {
return t.Tracked
})
}
return true
}
func (self *FileNode) GetHasStagedChanges() bool {
return self.SomeFile(func(file *models.File) bool { return file.HasStagedChanges })
}

View File

@ -1,6 +1,8 @@
package filetree
import (
"path"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
@ -300,3 +302,7 @@ func (self *Node[T]) ID() string {
func (self *Node[T]) Description() string {
return self.GetPath()
}
func (self *Node[T]) Name() string {
return path.Base(self.Path)
}

View File

@ -52,6 +52,17 @@ type TranslationSet struct {
Pull string
Scroll string
FileFilter string
CopyToClipboardMenu string
CopyFileName string
CopyFilePath string
CopyFileDiffTooltip string
CopySelectedDiff string
CopyAllFilesDiff string
NoContentToCopyError string
FileNameCopiedToast string
FilePathCopiedToast string
FileDiffCopiedToast string
AllFilesDiffCopiedToast string
FilterStagedFiles string
FilterUnstagedFiles string
ResetFilter string
@ -851,6 +862,17 @@ func EnglishTranslationSet() TranslationSet {
CantCheckoutBranchWhilePulling: "You cannot checkout another branch while pulling the current branch",
CantPullOrPushSameBranchTwice: "You cannot push or pull a branch while it is already being pushed or pulled",
FileFilter: "Filter files by status",
CopyToClipboardMenu: "Copy to clipboard",
CopyFileName: "File name",
CopyFilePath: "Path",
CopyFileDiffTooltip: "If there are staged items, this command considers only them. Otherwise, it considers all the unstaged ones.",
CopySelectedDiff: "Diff of selected file",
CopyAllFilesDiff: "Diff of all files",
NoContentToCopyError: "Nothing to copy",
FileNameCopiedToast: "File name copied to clipboard",
FilePathCopiedToast: "File path copied to clipboard",
FileDiffCopiedToast: "File diff copied to clipboard",
AllFilesDiffCopiedToast: "All files diff copied to clipboard",
FilterStagedFiles: "Show only staged files",
FilterUnstagedFiles: "Show only unstaged files",
ResetFilter: "Reset filter",

View File

@ -0,0 +1,185 @@
package file
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
// note: this is required to simulate the clipboard during CI
func expectClipboard(t *TestDriver, matcher *TextMatcher) {
defer t.Shell().DeleteFile("clipboard")
t.FileSystem().FileContent("clipboard", matcher)
}
var CopyMenu = NewIntegrationTest(NewIntegrationTestArgs{
Description: "The copy menu allows to copy name and diff of selected/all files",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
config.UserConfig.OS.CopyToClipboardCmd = "echo {{text}} > clipboard"
},
SetupRepo: func(shell *Shell) {},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
// Disabled item
t.Views().Files().
IsEmpty().
Press(keys.Files.CopyFileInfoToClipboard).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Copy to clipboard")).
Select(Contains("File name")).
Tooltip(Equals("Disabled: Nothing to copy")).
Confirm()
t.ExpectPopup().Alert().
Title(Equals("Error")).
Content(Equals("Nothing to copy")).
Confirm()
})
t.Shell().
CreateDir("dir").
CreateFile("dir/1-unstaged_file", "unstaged content")
// Empty content (new file)
t.Views().Files().
Press(keys.Universal.Refresh).
Lines(
Contains("dir").IsSelected(),
Contains("unstaged_file"),
).
SelectNextItem().
Press(keys.Files.CopyFileInfoToClipboard).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Copy to clipboard")).
Select(Contains("Diff of selected file")).
Tooltip(Contains("Disabled: Nothing to copy")).
Confirm()
t.ExpectPopup().Alert().
Title(Equals("Error")).
Content(Equals("Nothing to copy")).
Confirm()
}).
Press(keys.Files.CopyFileInfoToClipboard).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Copy to clipboard")).
Select(Contains("Diff of all files")).
Tooltip(Contains("Disabled: Nothing to copy")).
Confirm()
t.ExpectPopup().Alert().
Title(Equals("Error")).
Content(Equals("Nothing to copy")).
Confirm()
})
t.Shell().
GitAdd("dir/1-unstaged_file").
Commit("commit-unstaged").
UpdateFile("dir/1-unstaged_file", "unstaged content (new)").
CreateFileAndAdd("dir/2-staged_file", "staged content").
Commit("commit-staged").
UpdateFile("dir/2-staged_file", "staged content (new)").
GitAdd("dir/2-staged_file")
// Copy file name
t.Views().Files().
Press(keys.Universal.Refresh).
Lines(
Contains("dir"),
Contains("unstaged_file").IsSelected(),
Contains("staged_file"),
).
Press(keys.Files.CopyFileInfoToClipboard).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Copy to clipboard")).
Select(Contains("File name")).
Confirm()
expectClipboard(t, Contains("unstaged_file"))
})
// Copy file path
t.Views().Files().
Press(keys.Files.CopyFileInfoToClipboard).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Copy to clipboard")).
Select(Contains("Path")).
Confirm()
expectClipboard(t, Contains("dir/1-unstaged_file"))
})
// Selected path diff on a single (unstaged) file
t.Views().Files().
Press(keys.Files.CopyFileInfoToClipboard).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Copy to clipboard")).
Select(Contains("Diff of selected file")).
Tooltip(Equals("If there are staged items, this command considers only them. Otherwise, it considers all the unstaged ones.")).
Confirm()
expectClipboard(t, Contains("+unstaged content (new)"))
})
// Selected path diff with staged and unstaged files
t.Views().Files().
SelectPreviousItem().
Lines(
Contains("dir").IsSelected(),
Contains("unstaged_file"),
Contains("staged_file"),
).
Press(keys.Files.CopyFileInfoToClipboard).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Copy to clipboard")).
Select(Contains("Diff of selected file")).
Tooltip(Equals("If there are staged items, this command considers only them. Otherwise, it considers all the unstaged ones.")).
Confirm()
expectClipboard(t, Contains("+staged content (new)"))
})
// All files diff with staged files
t.Views().Files().
Press(keys.Files.CopyFileInfoToClipboard).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Copy to clipboard")).
Select(Contains("Diff of all files")).
Tooltip(Equals("If there are staged items, this command considers only them. Otherwise, it considers all the unstaged ones.")).
Confirm()
expectClipboard(t, Contains("+staged content (new)"))
})
// All files diff with no staged files
t.Views().Files().
SelectNextItem().
SelectNextItem().
Lines(
Contains("dir"),
Contains("unstaged_file"),
Contains("staged_file").IsSelected(),
).
Press(keys.Universal.Select).
Press(keys.Files.CopyFileInfoToClipboard).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Copy to clipboard")).
Select(Contains("Diff of all files")).
Tooltip(Equals("If there are staged items, this command considers only them. Otherwise, it considers all the unstaged ones.")).
Confirm()
expectClipboard(t, Contains("+staged content (new)").Contains("+unstaged content (new)"))
})
},
})

View File

@ -117,6 +117,7 @@ var tests = []*components.IntegrationTest{
diff.DiffAndApplyPatch,
diff.DiffCommits,
diff.IgnoreWhitespace,
file.CopyMenu,
file.DirWithUntrackedFile,
file.DiscardAllDirChanges,
file.DiscardChanges,