diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md index f162014b7..8dc8b60a8 100644 --- a/docs/keybindings/Keybindings_en.md +++ b/docs/keybindings/Keybindings_en.md @@ -56,6 +56,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | Key | Action | Info | |-----|--------|-------------| | `` `` | Copy path to clipboard | | +| `` y `` | Copy to clipboard | | | `` c `` | Checkout | Checkout file. This replaces the file in your working tree with the version from the selected commit. | | `` d `` | Remove | Discard this commit's changes to this file. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes this file. | | `` o `` | Open file | Open file in default application. | diff --git a/docs/keybindings/Keybindings_ja.md b/docs/keybindings/Keybindings_ja.md index a1046c8dc..2e55fdee2 100644 --- a/docs/keybindings/Keybindings_ja.md +++ b/docs/keybindings/Keybindings_ja.md @@ -134,6 +134,7 @@ If you would instead like to start an interactive rebase from the selected commi | Key | Action | Info | |-----|--------|-------------| | `` `` | ファイル名をクリップボードにコピー | | +| `` y `` | Copy to clipboard | | | `` c `` | チェックアウト | Checkout file. This replaces the file in your working tree with the version from the selected commit. | | `` d `` | Remove | Discard this commit's changes to this file. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes this file. | | `` o `` | ファイルを開く | Open file in default application. | diff --git a/docs/keybindings/Keybindings_ko.md b/docs/keybindings/Keybindings_ko.md index 127ac7166..e3c1c81ae 100644 --- a/docs/keybindings/Keybindings_ko.md +++ b/docs/keybindings/Keybindings_ko.md @@ -299,6 +299,7 @@ If you would instead like to start an interactive rebase from the selected commi | Key | Action | Info | |-----|--------|-------------| | `` `` | 파일명을 클립보드에 복사 | | +| `` y `` | 클립보드에 복사 | | | `` c `` | 체크아웃 | Checkout file | | `` d `` | Remove | Discard this commit's changes to this file | | `` o `` | 파일 닫기 | Open file in default application. | diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md index 37bacb20f..eb940bac2 100644 --- a/docs/keybindings/Keybindings_nl.md +++ b/docs/keybindings/Keybindings_nl.md @@ -129,6 +129,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | Key | Action | Info | |-----|--------|-------------| | `` `` | Kopieer de bestandsnaam naar het klembord | | +| `` y `` | Copy to clipboard | | | `` c `` | Uitchecken | Bestand uitchecken | | `` d `` | Remove | Uitsluit deze commit zijn veranderingen aan dit bestand | | `` o `` | Open bestand | Open file in default application. | diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md index 8db7a0e73..a30007005 100644 --- a/docs/keybindings/Keybindings_pl.md +++ b/docs/keybindings/Keybindings_pl.md @@ -238,6 +238,7 @@ Jeśli chcesz zamiast tego rozpocząć interaktywny rebase od wybranego commita, | Key | Action | Info | |-----|--------|-------------| | `` `` | Kopiuj ścieżkę do schowka | | +| `` y `` | Kopiuj do schowka | | | `` c `` | Przełącz | Przełącz plik. Zastępuje plik w twoim drzewie roboczym wersją z wybranego commita. | | `` d `` | Usuń | Odrzuć zmiany w tym pliku z tego commita. Uruchamia interaktywny rebase w tle, więc możesz otrzymać konflikt scalania, jeśli późniejszy commit również zmienia ten plik. | | `` o `` | Otwórz plik | Otwórz plik w domyślnej aplikacji. | diff --git a/docs/keybindings/Keybindings_pt.md b/docs/keybindings/Keybindings_pt.md index f256b9e78..aa933c179 100644 --- a/docs/keybindings/Keybindings_pt.md +++ b/docs/keybindings/Keybindings_pt.md @@ -135,6 +135,7 @@ Veja a documentação: | Key | Action | Info | |-----|--------|-------------| | `` `` | Copy path to clipboard | | +| `` y `` | Copy to clipboard | | | `` c `` | Verificar | Checkout file. This replaces the file in your working tree with the version from the selected commit. | | `` d `` | Remove | Discard this commit's changes to this file. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes this file. | | `` o `` | Abrir arquivo | Abrir arquivo no aplicativo padrão. | diff --git a/docs/keybindings/Keybindings_ru.md b/docs/keybindings/Keybindings_ru.md index 9c17e4c6b..28abe78eb 100644 --- a/docs/keybindings/Keybindings_ru.md +++ b/docs/keybindings/Keybindings_ru.md @@ -261,6 +261,7 @@ If you would instead like to start an interactive rebase from the selected commi | Key | Action | Info | |-----|--------|-------------| | `` `` | Скопировать название файла в буфер обмена | | +| `` y `` | Copy to clipboard | | | `` c `` | Переключить | Переключить файл | | `` d `` | Remove | Отменить изменения коммита в этом файле | | `` o `` | Открыть файл | Open file in default application. | diff --git a/docs/keybindings/Keybindings_zh-CN.md b/docs/keybindings/Keybindings_zh-CN.md index 790a64f53..663dd98f0 100644 --- a/docs/keybindings/Keybindings_zh-CN.md +++ b/docs/keybindings/Keybindings_zh-CN.md @@ -186,6 +186,7 @@ _图例:`` 意味着ctrl+b, `意味着Alt+b, `B` 意味着shift+b_ | Key | Action | Info | |-----|--------|-------------| | `` `` | 将文件名复制到剪贴板 | | +| `` y `` | 复制到剪贴板 | | | `` c `` | 检出 | 检出文件 | | `` d `` | 删除 | 放弃对此文件的提交变更 | | `` o `` | 打开文件 | 使用默认程序打开该文件 | diff --git a/docs/keybindings/Keybindings_zh-TW.md b/docs/keybindings/Keybindings_zh-TW.md index 895a81e79..cb828d757 100644 --- a/docs/keybindings/Keybindings_zh-TW.md +++ b/docs/keybindings/Keybindings_zh-TW.md @@ -210,6 +210,7 @@ If you would instead like to start an interactive rebase from the selected commi | Key | Action | Info | |-----|--------|-------------| | `` `` | 複製檔案名稱到剪貼簿 | | +| `` y `` | 複製到剪貼簿 | | | `` c `` | 檢出 | 檢出檔案 | | `` d `` | Remove | Discard this commit's changes to this file. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes this file. | | `` o `` | 開啟檔案 | 使用預設軟體開啟 | diff --git a/pkg/gui/controllers/commits_files_controller.go b/pkg/gui/controllers/commits_files_controller.go index 61dfa1d85..0e54fb25b 100644 --- a/pkg/gui/controllers/commits_files_controller.go +++ b/pkg/gui/controllers/commits_files_controller.go @@ -41,6 +41,12 @@ func NewCommitFilesController( func (self *CommitFilesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { bindings := []*types.Binding{ + { + Key: opts.GetKey(opts.Config.Files.CopyFileInfoToClipboard), + Handler: self.openCopyMenu, + Description: self.c.Tr.CopyToClipboardMenu, + OpensMenu: true, + }, { Key: opts.GetKey(opts.Config.CommitFiles.CheckoutCommitFile), Handler: self.withItem(self.checkout), @@ -181,6 +187,77 @@ func (self *CommitFilesController) onClickMain(opts gocui.ViewMouseBindingOpts) return self.enterCommitFile(node, types.OnFocusOpts{ClickedWindowName: "main", ClickedViewLineIdx: opts.Y}) } +func (self *CommitFilesController) copyDiffToClipboard(path string, toastMessage string) error { + from, to := self.context().GetFromAndToForDiff() + from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(from) + + cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(from, to, reverse, path, true) + diff, err := cmdObj.RunWithOutput() + if err != nil { + return err + } + if err := self.c.OS().CopyToClipboard(diff); err != nil { + return err + } + self.c.Toast(toastMessage) + return nil +} + +func (self *CommitFilesController) 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 err + } + self.c.Toast(self.c.Tr.FileNameCopiedToast) + return nil + }, + DisabledReason: self.require(self.singleItemSelected())(), + Key: 'n', + } + copyPathItem := &types.MenuItem{ + Label: self.c.Tr.CopyFilePath, + OnPress: func() error { + if err := self.c.OS().CopyToClipboard(node.Path); err != nil { + return err + } + self.c.Toast(self.c.Tr.FilePathCopiedToast) + return nil + }, + DisabledReason: self.require(self.singleItemSelected())(), + Key: 'p', + } + copyFileDiffItem := &types.MenuItem{ + Label: self.c.Tr.CopySelectedDiff, + OnPress: func() error { + return self.copyDiffToClipboard(node.GetPath(), self.c.Tr.FileDiffCopiedToast) + }, + DisabledReason: self.require(self.singleItemSelected())(), + Key: 's', + } + copyAllDiff := &types.MenuItem{ + Label: self.c.Tr.CopyAllFilesDiff, + OnPress: func() error { + return self.copyDiffToClipboard(".", self.c.Tr.AllFilesDiffCopiedToast) + }, + DisabledReason: self.require(self.itemsSelected())(), + Key: 'a', + } + + return self.c.Menu(types.CreateMenuOptions{ + Title: self.c.Tr.CopyToClipboardMenu, + Items: []*types.MenuItem{ + copyNameItem, + copyPathItem, + copyFileDiffItem, + copyAllDiff, + }, + }) +} + func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error { self.c.LogAction(self.c.Tr.Actions.CheckoutFile) if err := self.c.Git().WorkingTree.CheckoutFile(self.context().GetRef().RefName(), node.GetPath()); err != nil { diff --git a/pkg/integration/tests/diff/copy_to_clipboard.go b/pkg/integration/tests/diff/copy_to_clipboard.go new file mode 100644 index 000000000..88c14cd48 --- /dev/null +++ b/pkg/integration/tests/diff/copy_to_clipboard.go @@ -0,0 +1,123 @@ +package diff + +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 CopyToClipboard = 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.GetUserConfig().OS.CopyToClipboardCmd = "printf '%s' {{text}} > clipboard" + }, + SetupRepo: func(shell *Shell) { + shell.CreateDir("dir") + shell.CreateFileAndAdd("dir/file1", "1st line\n") + shell.Commit("1") + shell.CreateFileAndAdd("dir/file1", "1st line\n2nd line\n") + shell.CreateFileAndAdd("dir/file2", "file2\n") + shell.Commit("2") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("2").IsSelected(), + Contains("1"), + ). + PressEnter() + + t.Views().CommitFiles(). + IsFocused(). + Lines( + Contains("dir").IsSelected(), + Contains("file1"), + Contains("file2"), + ). + NavigateToLine(Contains("file1")). + Press(keys.Files.CopyFileInfoToClipboard). + Tap(func() { + t.ExpectPopup().Menu(). + Title(Equals("Copy to clipboard")). + Select(Contains("File name")). + Confirm(). + Tap(func() { + t.ExpectToast(Equals("File name copied to clipboard")) + expectClipboard(t, Equals("file1")) + }) + }). + Press(keys.Files.CopyFileInfoToClipboard). + Tap(func() { + t.ExpectPopup().Menu(). + Title(Equals("Copy to clipboard")). + Select(Contains("Path")). + Confirm(). + Tap(func() { + t.ExpectToast(Equals("File path copied to clipboard")) + expectClipboard(t, Equals("dir/file1")) + }) + }). + Press(keys.Files.CopyFileInfoToClipboard). + Tap(func() { + t.ExpectPopup().Menu(). + Title(Equals("Copy to clipboard")). + Select(Contains("Diff of selected file")). + Confirm(). + Tap(func() { + t.ExpectToast(Equals("File diff copied to clipboard")) + expectClipboard(t, + Contains("diff --git a/dir/file1 b/dir/file1").Contains("+2nd line").DoesNotContain("+1st line"). + DoesNotContain("diff --git a/dir/file2 b/dir/file2").DoesNotContain("+file2")) + }) + }). + Press(keys.Files.CopyFileInfoToClipboard). + Tap(func() { + t.ExpectPopup().Menu(). + Title(Equals("Copy to clipboard")). + Select(Contains("Diff of all files")). + Confirm(). + Tap(func() { + t.ExpectToast(Equals("All files diff copied to clipboard")) + expectClipboard(t, + Contains("diff --git a/dir/file1 b/dir/file1").Contains("+2nd line").DoesNotContain("+1st line"). + Contains("diff --git a/dir/file2 b/dir/file2").Contains("+file2")) + }) + }) + + t.Views().Commits(). + Focus(). + // Select both commits + Press(keys.Universal.RangeSelectDown). + PressEnter() + + t.Views().CommitFiles(). + IsFocused(). + Lines( + Contains("dir").IsSelected(), + Contains("file1"), + Contains("file2"), + ). + NavigateToLine(Contains("file1")). + Press(keys.Files.CopyFileInfoToClipboard). + Tap(func() { + t.ExpectPopup().Menu(). + Title(Equals("Copy to clipboard")). + Select(Contains("Diff of selected file")). + Confirm(). + Tap(func() { + t.ExpectToast(Equals("File diff copied to clipboard")) + expectClipboard(t, + Contains("diff --git a/dir/file1 b/dir/file1").Contains("+1st line").Contains("+2nd line")) + }) + }) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 6a3830157..d0dc2a8a0 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -167,6 +167,7 @@ var tests = []*components.IntegrationTest{ demo.StageLines, demo.Undo, demo.WorktreeCreateFromBranches, + diff.CopyToClipboard, diff.Diff, diff.DiffAndApplyPatch, diff.DiffCommits,