diff --git a/docs-master/keybindings/Keybindings_en.md b/docs-master/keybindings/Keybindings_en.md index 57a34d9ef..205d66296 100644 --- a/docs-master/keybindings/Keybindings_en.md +++ b/docs-master/keybindings/Keybindings_en.md @@ -226,6 +226,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` `` | Exit back to side panel | | | `` `` | Stage | Toggle selection staged / unstaged. | | `` d `` | Discard | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | +| `` `` | Copy selected text to clipboard | | | `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` v `` | Toggle range select | | | `` , h `` | Go to previous hunk | | @@ -339,6 +340,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` `` | Exit back to side panel | | | `` `` | Stage | Toggle selection staged / unstaged. | | `` d `` | Discard | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | +| `` `` | Copy selected text to clipboard | | | `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` v `` | Toggle range select | | | `` , h `` | Go to previous hunk | | diff --git a/docs-master/keybindings/Keybindings_ja.md b/docs-master/keybindings/Keybindings_ja.md index 6c2c9f3ce..af291237b 100644 --- a/docs-master/keybindings/Keybindings_ja.md +++ b/docs-master/keybindings/Keybindings_ja.md @@ -195,6 +195,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` `` | サイドパネルに戻る | | | `` `` | ステージ | 選択された部分のステージ / アンステージを切り替えます。 | | `` d `` | 破棄 | ステージされていない変更が選択されている場合、`git reset`を使用して変更を破棄します。ステージされた変更が選択されている場合、変更をアンステージします。 | +| `` `` | 選択したテキストをクリップボードにコピー | | | `` a `` | ハンクの選択を切り替える | Toggle line-by-line vs. hunk selection mode. | | `` v `` | 範囲選択を切り替え | | | `` , h `` | 前のハンクに移動 | | @@ -316,6 +317,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` `` | サイドパネルに戻る | | | `` `` | ステージ | 選択された部分のステージ / アンステージを切り替えます。 | | `` d `` | 破棄 | ステージされていない変更が選択されている場合、`git reset`を使用して変更を破棄します。ステージされた変更が選択されている場合、変更をアンステージします。 | +| `` `` | 選択したテキストをクリップボードにコピー | | | `` a `` | ハンクの選択を切り替える | Toggle line-by-line vs. hunk selection mode. | | `` v `` | 範囲選択を切り替え | | | `` , h `` | 前のハンクに移動 | | diff --git a/docs-master/keybindings/Keybindings_ko.md b/docs-master/keybindings/Keybindings_ko.md index 85376e1e8..951d0bd4c 100644 --- a/docs-master/keybindings/Keybindings_ko.md +++ b/docs-master/keybindings/Keybindings_ko.md @@ -87,6 +87,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` `` | Exit back to side panel | | | `` `` | Staged 전환 | 선택한 행을 staged / unstaged | | `` d `` | 변경을 삭제 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | +| `` `` | 선택한 텍스트를 클립보드에 복사 | | | `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` v `` | 드래그 선택 전환 | | | `` , h `` | 이전 hunk를 선택 | | @@ -173,6 +174,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` `` | Exit back to side panel | | | `` `` | Staged 전환 | 선택한 행을 staged / unstaged | | `` d `` | 변경을 삭제 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | +| `` `` | 선택한 텍스트를 클립보드에 복사 | | | `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` v `` | 드래그 선택 전환 | | | `` , h `` | 이전 hunk를 선택 | | diff --git a/docs-master/keybindings/Keybindings_nl.md b/docs-master/keybindings/Keybindings_nl.md index a94e77195..f0db3fc22 100644 --- a/docs-master/keybindings/Keybindings_nl.md +++ b/docs-master/keybindings/Keybindings_nl.md @@ -234,6 +234,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` `` | Exit back to side panel | | | `` `` | Toggle staged | Toggle lijnen staged / unstaged | | `` d `` | Verwijdert change (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | +| `` `` | Copy selected text to clipboard | | | `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` v `` | Toggle drag selecteer | | | `` , h `` | Selecteer de vorige hunk | | @@ -317,6 +318,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` `` | Exit back to side panel | | | `` `` | Toggle staged | Toggle lijnen staged / unstaged | | `` d `` | Verwijdert change (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | +| `` `` | Copy selected text to clipboard | | | `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` v `` | Toggle drag selecteer | | | `` , h `` | Selecteer de vorige hunk | | diff --git a/docs-master/keybindings/Keybindings_pl.md b/docs-master/keybindings/Keybindings_pl.md index 3e1647e48..b70606981 100644 --- a/docs-master/keybindings/Keybindings_pl.md +++ b/docs-master/keybindings/Keybindings_pl.md @@ -102,6 +102,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` `` | Exit back to side panel | | | `` `` | Zatwierdź | Przełącz zaznaczenie zatwierdzone/niezatwierdzone. | | `` d `` | Odrzuć | Gdy zaznaczona jest niezatwierdzona zmiana, odrzuć ją używając `git reset`. Gdy zaznaczona jest zatwierdzona zmiana, cofnij zatwierdzenie. | +| `` `` | Kopiuj zaznaczony tekst do schowka | | | `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` v `` | Przełącz zaznaczenie zakresu | | | `` , h `` | Idź do poprzedniego fragmentu | | @@ -212,6 +213,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` `` | Exit back to side panel | | | `` `` | Zatwierdź | Przełącz zaznaczenie zatwierdzone/niezatwierdzone. | | `` d `` | Odrzuć | Gdy zaznaczona jest niezatwierdzona zmiana, odrzuć ją używając `git reset`. Gdy zaznaczona jest zatwierdzona zmiana, cofnij zatwierdzenie. | +| `` `` | Kopiuj zaznaczony tekst do schowka | | | `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` v `` | Przełącz zaznaczenie zakresu | | | `` , h `` | Idź do poprzedniego fragmentu | | diff --git a/docs-master/keybindings/Keybindings_pt.md b/docs-master/keybindings/Keybindings_pt.md index ba4bda91e..e84b4938c 100644 --- a/docs-master/keybindings/Keybindings_pt.md +++ b/docs-master/keybindings/Keybindings_pt.md @@ -238,6 +238,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` `` | Exit back to side panel | | | `` `` | Etapa | Ativar/desativar seleção em staged/unstaged | | `` d `` | Descartar | Quando a mudança não desejada for selecionada, descarte a mudança usando `git reset`. Quando a mudança em fase é selecionada, despare a mudança. | +| `` `` | Copiar texto selecionado para área de transferência | | | `` a `` | Toggle hunk selection | Ativa/desativa modo linha por linha vs. modo de seleção por partes. | | `` v `` | Toggle range select | | | `` , h `` | Ir para o local anterior | | @@ -348,6 +349,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` `` | Exit back to side panel | | | `` `` | Etapa | Ativar/desativar seleção em staged/unstaged | | `` d `` | Descartar | Quando a mudança não desejada for selecionada, descarte a mudança usando `git reset`. Quando a mudança em fase é selecionada, despare a mudança. | +| `` `` | Copiar texto selecionado para área de transferência | | | `` a `` | Toggle hunk selection | Ativa/desativa modo linha por linha vs. modo de seleção por partes. | | `` v `` | Toggle range select | | | `` , h `` | Ir para o local anterior | | diff --git a/docs-master/keybindings/Keybindings_ru.md b/docs-master/keybindings/Keybindings_ru.md index a1b18494c..823150e98 100644 --- a/docs-master/keybindings/Keybindings_ru.md +++ b/docs-master/keybindings/Keybindings_ru.md @@ -77,6 +77,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` `` | Exit back to side panel | | | `` `` | Переключить индекс | Переключить строку в проиндексированные / непроиндексированные | | `` d `` | Отменить изменение (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | +| `` `` | Скопировать выделенный текст в буфер обмена | | | `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` v `` | Переключить выборку перетаскивания | | | `` , h `` | Выбрать предыдущую часть | | @@ -117,6 +118,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` `` | Exit back to side panel | | | `` `` | Переключить индекс | Переключить строку в проиндексированные / непроиндексированные | | `` d `` | Отменить изменение (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | +| `` `` | Скопировать выделенный текст в буфер обмена | | | `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` v `` | Переключить выборку перетаскивания | | | `` , h `` | Выбрать предыдущую часть | | diff --git a/docs-master/keybindings/Keybindings_zh-CN.md b/docs-master/keybindings/Keybindings_zh-CN.md index 175d6afd2..eda608fca 100644 --- a/docs-master/keybindings/Keybindings_zh-CN.md +++ b/docs-master/keybindings/Keybindings_zh-CN.md @@ -289,6 +289,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` `` | 退出回到侧边面板 | | | `` `` | 切换暂存状态 | 切换行暂存状态 | | `` d `` | 取消变更(git reset) | 当选择未暂存的变更时,使用git reset丢弃该变更。当选择已暂存的变更时,取消暂存该变更 | +| `` `` | 复制选中文本到剪贴板 | | | `` a `` | 切换代码块选择 | 切换逐行选择与代码块选择模式。 | | `` v `` | 切换拖动选择 | | | `` , h `` | 选择上一个区块 | | @@ -345,6 +346,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` `` | 退出回到侧边面板 | | | `` `` | 切换暂存状态 | 切换行暂存状态 | | `` d `` | 取消变更(git reset) | 当选择未暂存的变更时,使用git reset丢弃该变更。当选择已暂存的变更时,取消暂存该变更 | +| `` `` | 复制选中文本到剪贴板 | | | `` a `` | 切换代码块选择 | 切换逐行选择与代码块选择模式。 | | `` v `` | 切换拖动选择 | | | `` , h `` | 选择上一个区块 | | diff --git a/docs-master/keybindings/Keybindings_zh-TW.md b/docs-master/keybindings/Keybindings_zh-TW.md index 08a000577..f7d20d1c8 100644 --- a/docs-master/keybindings/Keybindings_zh-TW.md +++ b/docs-master/keybindings/Keybindings_zh-TW.md @@ -85,6 +85,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` `` | Exit back to side panel | | | `` `` | 切換預存 | 切換現有行的狀態 (已預存/未預存) | | `` d `` | 刪除變更 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | +| `` `` | 複製所選文本至剪貼簿 | | | `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` v `` | 切換拖曳選擇 | | | `` , h `` | 選擇上一段 | | @@ -374,6 +375,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` `` | Exit back to side panel | | | `` `` | 切換預存 | 切換現有行的狀態 (已預存/未預存) | | `` d `` | 刪除變更 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | +| `` `` | 複製所選文本至剪貼簿 | | | `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` v `` | 切換拖曳選擇 | | | `` , h `` | 選擇上一段 | | diff --git a/pkg/gui/controllers/main_view_controller.go b/pkg/gui/controllers/main_view_controller.go index 3cadd118a..cc3181c19 100644 --- a/pkg/gui/controllers/main_view_controller.go +++ b/pkg/gui/controllers/main_view_controller.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "path/filepath" + "strings" "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/gocui" @@ -87,6 +88,12 @@ func (self *MainViewController) GetKeybindings(opts types.KeybindingsOpts) []*ty Tooltip: self.c.Tr.DiscardSelectionTooltip, DisplayOnScreen: selectionShown, }, + { + Keys: opts.GetKeys(opts.Config.Universal.CopyToClipboard), + Handler: self.copySelection, + Description: self.c.Tr.CopySelectedTextToClipboard, + DisplayOnScreen: selectionShown, + }, { Keys: opts.GetKeys(opts.Config.Universal.GoInto), Handler: self.enter, @@ -362,6 +369,66 @@ func (self *MainViewController) discardSelectionDisabledReason() *types.Disabled return actions.DiscardSelectionDisabledReason() } +// copySelection copies the selected diff line(s) to the clipboard. Unlike the primary +// action and discard, this is the same for every diff panel (it just reads the text the +// focused main view shows), so it lives here rather than on FocusedMainViewActions — and +// so it works over panels with no actions, like the reflog. A no-op when there's no +// selection (non-diff content). +// +// With no pager the main view shows the raw diff, so the +/-/space column is stripped +// from a homogeneous selection (dropDiffPrefix) to ease pasting into code, mirroring the +// staging view's copy. With a pager configured we can't assume that column is present — +// some pagers keep it and only colorize (e.g. ydiff), others drop or restructure it (e.g. +// delta) — and we can't tell which, so we conservatively don't strip and copy verbatim. +// (The cost is missing the convenience for column-preserving pagers.) +func (self *MainViewController) copySelection() error { + v := self.context.GetView() + if !v.Highlight { + return nil + } + + // The selection is in (wrapped) view-line space; map it to the unwrapped buffer lines + // it covers and copy those, so a wrapped line is copied whole and once — not indexed + // into the buffer by a view-line number (which copies the wrong line, or panics when + // the view line is past the buffer's length). + firstView, lastView := v.SelectedLineRange() + firstBuffer, ok := v.BufferLineForViewLine(firstView) + if !ok { + return nil + } + lastBuffer, ok := v.BufferLineForViewLine(lastView) + if !ok { + return nil + } + + contents := v.DiffLineContents() + lastBuffer = min(lastBuffer, len(contents)-1) + if firstBuffer > lastBuffer { + return nil + } + lines := make([]string, 0, lastBuffer-firstBuffer+1) + for i := firstBuffer; i <= lastBuffer; i++ { + lines = append(lines, contents[i].Text) + } + + // Trailing newline included so the last line is terminated too (dropDiffPrefix keeps + // it; the pager-verbatim path needs it added here). + selected := strings.Join(lines, "\n") + "\n" + if !self.usingExternalDiff() { + selected = dropDiffPrefix(selected) + } + + self.c.LogAction(self.c.Tr.Actions.CopySelectedTextToClipboard) + return self.c.OS().CopyToClipboard(selected) +} + +// usingExternalDiff reports whether the focused main view's diff is produced by an +// external diff command (a pager), in which case the rendered lines may not carry the raw +// +/-/space column (see copySelection). +func (self *MainViewController) usingExternalDiff() bool { + return self.c.State().GetDiffRendererConfigManager().GetDiffRendererType() == config.DiffRendererType_RawGit +} + // revealSelectionAfterPrimaryAction re-establishes the focused-main-view selection after a // primary action (staging or a patch toggle) re-renders the diff. The selection's // change-line ordinal is read from the source pane (still showing the pre-action diff diff --git a/pkg/integration/tests/staging/copy_from_main_view.go b/pkg/integration/tests/staging/copy_from_main_view.go new file mode 100644 index 000000000..9fc3f2d86 --- /dev/null +++ b/pkg/integration/tests/staging/copy_from_main_view.go @@ -0,0 +1,41 @@ +package staging + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var CopyFromMainView = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Copy a hunk from the focused main view to the clipboard, with the raw diff's +/- prefix stripped", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + config.GetUserConfig().Gui.UseHunkModeInStagingView = true + // Emulate the clipboard by writing to a file. + config.GetUserConfig().OS.CopyToClipboardCmd = "printf '%s' {{text}} > clipboard" + }, + SetupRepo: func(shell *Shell) { + shell.CreateFileAndAdd("file1", "one\ntwo\n") + shell.Commit("one") + + // Two consecutive added lines, so the selected hunk is a homogeneous addition. + shell.UpdateFile("file1", "one\nADD1\nADD2\ntwo\n") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + IsFocused(). + Press(keys.Universal.FocusMainView) + + t.Views().Main(). + IsFocused(). + SelectedLines( + Contains("+ADD1"), + Contains("+ADD2"), + ). + Press(keys.Universal.CopyToClipboard) + + // With no pager the main view shows the raw diff, so a homogeneous addition is + // copied with the '+' column stripped — ready to paste into code. + t.FileSystem().FileContent("clipboard", Equals("ADD1\nADD2\n")) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index cab022e53..fc72a4a85 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -412,6 +412,7 @@ var tests = []*components.IntegrationTest{ shell_commands.History, shell_commands.OmitFromHistory, staging.AdvanceToNextHunkAfterStagingShiftsLineNumbers, + staging.CopyFromMainView, staging.DiffChangeScreenMode, staging.DiffContextChange, staging.DiscardAllChanges,