mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 23:56:24 -04:00
Discard the selected diff line(s) from the focused main view
Wire the focused main view's `d` binding to a DiscardSelection action on the side panel beneath, the discard counterpart of the primary action. As with stage/toggle, what discard means is the panel's business: - the files panel discards from the working tree, mirroring the staging view's `d` — reverse-apply not cached on the unstaged side (destructive, so confirmed), reverse-apply cached on the staged side (i.e. unstage); - the commit panels (commit files, and the whole-commit diff of the local commits / sub-commits / stash panels) remove the selected lines from the commit via a rebase, mirroring the patch builder's "discard lines from commit" — building a one-off patch from the selection and deleting it. The commit-discard backend and its guard are shared free functions in patch_building_from_main_view.go, paralleling the patch toggle. Discarding from a commit is only possible on a local branch, so DiscardSelection grows a disabled-reason companion on the FocusedMainViewActions interface: the `d` binding is greyed with a reason on stash and other-branch sub-commits (never rebaseable) and while a rebase is in progress, exactly as the patch builder greys it. The files panel is always available (a zero-context diff is the one error it reports inline, matching the staging view). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
99f80b1bc8
commit
e9c4425df6
|
|
@ -225,6 +225,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <tab> `` | Switch view | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | 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. |
|
||||
| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. |
|
||||
| `` v `` | Toggle range select | |
|
||||
| `` <left>, h `` | Go to previous hunk | |
|
||||
|
|
@ -337,6 +338,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <tab> `` | Switch view | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | 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. |
|
||||
| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. |
|
||||
| `` v `` | Toggle range select | |
|
||||
| `` <left>, h `` | Go to previous hunk | |
|
||||
|
|
|
|||
|
|
@ -194,6 +194,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <tab> `` | ビューを切り替え | 他のビュー(ステージされた変更/ステージされていない変更)に切り替えます。 |
|
||||
| `` <esc> `` | サイドパネルに戻る | |
|
||||
| `` <space> `` | ステージ | 選択された部分のステージ / アンステージを切り替えます。 |
|
||||
| `` d `` | 破棄 | ステージされていない変更が選択されている場合、`git reset`を使用して変更を破棄します。ステージされた変更が選択されている場合、変更をアンステージします。 |
|
||||
| `` a `` | ハンクの選択を切り替える | Toggle line-by-line vs. hunk selection mode. |
|
||||
| `` v `` | 範囲選択を切り替え | |
|
||||
| `` <left>, h `` | 前のハンクに移動 | |
|
||||
|
|
@ -314,6 +315,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <tab> `` | ビューを切り替え | 他のビュー(ステージされた変更/ステージされていない変更)に切り替えます。 |
|
||||
| `` <esc> `` | サイドパネルに戻る | |
|
||||
| `` <space> `` | ステージ | 選択された部分のステージ / アンステージを切り替えます。 |
|
||||
| `` d `` | 破棄 | ステージされていない変更が選択されている場合、`git reset`を使用して変更を破棄します。ステージされた変更が選択されている場合、変更をアンステージします。 |
|
||||
| `` a `` | ハンクの選択を切り替える | Toggle line-by-line vs. hunk selection mode. |
|
||||
| `` v `` | 範囲選択を切り替え | |
|
||||
| `` <left>, h `` | 前のハンクに移動 | |
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <tab> `` | 패널 전환 | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | 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 `` | 드래그 선택 전환 | |
|
||||
| `` <left>, h `` | 이전 hunk를 선택 | |
|
||||
|
|
@ -171,6 +172,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <tab> `` | 패널 전환 | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | 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 `` | 드래그 선택 전환 | |
|
||||
| `` <left>, h `` | 이전 hunk를 선택 | |
|
||||
|
|
|
|||
|
|
@ -233,6 +233,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <tab> `` | Ga naar een ander paneel | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | 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. |
|
||||
| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. |
|
||||
| `` v `` | Toggle drag selecteer | |
|
||||
| `` <left>, h `` | Selecteer de vorige hunk | |
|
||||
|
|
@ -315,6 +316,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <tab> `` | Ga naar een ander paneel | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | 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. |
|
||||
| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. |
|
||||
| `` v `` | Toggle drag selecteer | |
|
||||
| `` <left>, h `` | Selecteer de vorige hunk | |
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <tab> `` | Przełącz widok | Przełącz na inny widok (zatwierdzone/niezatwierdzone zmiany). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | 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. |
|
||||
| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. |
|
||||
| `` v `` | Przełącz zaznaczenie zakresu | |
|
||||
| `` <left>, h `` | Idź do poprzedniego fragmentu | |
|
||||
|
|
@ -210,6 +211,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <tab> `` | Przełącz widok | Przełącz na inny widok (zatwierdzone/niezatwierdzone zmiany). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | 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. |
|
||||
| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. |
|
||||
| `` v `` | Przełącz zaznaczenie zakresu | |
|
||||
| `` <left>, h `` | Idź do poprzedniego fragmentu | |
|
||||
|
|
|
|||
|
|
@ -237,6 +237,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <tab> `` | Mudar de visão | Alternar para outra visão (staged/não processadas alterações). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | 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. |
|
||||
| `` a `` | Toggle hunk selection | Ativa/desativa modo linha por linha vs. modo de seleção por partes. |
|
||||
| `` v `` | Toggle range select | |
|
||||
| `` <left>, h `` | Ir para o local anterior | |
|
||||
|
|
@ -346,6 +347,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <tab> `` | Mudar de visão | Alternar para outra visão (staged/não processadas alterações). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | 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. |
|
||||
| `` a `` | Toggle hunk selection | Ativa/desativa modo linha por linha vs. modo de seleção por partes. |
|
||||
| `` v `` | Toggle range select | |
|
||||
| `` <left>, h `` | Ir para o local anterior | |
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <tab> `` | Переключиться на другую панель (проиндексированные/непроиндексированные изменения) | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | Переключить индекс | Переключить строку в проиндексированные / непроиндексированные |
|
||||
| `` 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 `` | Переключить выборку перетаскивания | |
|
||||
| `` <left>, h `` | Выбрать предыдущую часть | |
|
||||
|
|
@ -115,6 +116,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <tab> `` | Переключиться на другую панель (проиндексированные/непроиндексированные изменения) | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | Переключить индекс | Переключить строку в проиндексированные / непроиндексированные |
|
||||
| `` 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 `` | Переключить выборку перетаскивания | |
|
||||
| `` <left>, h `` | Выбрать предыдущую часть | |
|
||||
|
|
|
|||
|
|
@ -288,6 +288,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <tab> `` | 切换到其他面板 | 切换到其他视图(已暂存/未暂存的变更) |
|
||||
| `` <esc> `` | 退出回到侧边面板 | |
|
||||
| `` <space> `` | 切换暂存状态 | 切换行暂存状态 |
|
||||
| `` d `` | 取消变更(git reset) | 当选择未暂存的变更时,使用git reset丢弃该变更。当选择已暂存的变更时,取消暂存该变更 |
|
||||
| `` a `` | 切换代码块选择 | 切换逐行选择与代码块选择模式。 |
|
||||
| `` v `` | 切换拖动选择 | |
|
||||
| `` <left>, h `` | 选择上一个区块 | |
|
||||
|
|
@ -343,6 +344,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <tab> `` | 切换到其他面板 | 切换到其他视图(已暂存/未暂存的变更) |
|
||||
| `` <esc> `` | 退出回到侧边面板 | |
|
||||
| `` <space> `` | 切换暂存状态 | 切换行暂存状态 |
|
||||
| `` d `` | 取消变更(git reset) | 当选择未暂存的变更时,使用git reset丢弃该变更。当选择已暂存的变更时,取消暂存该变更 |
|
||||
| `` a `` | 切换代码块选择 | 切换逐行选择与代码块选择模式。 |
|
||||
| `` v `` | 切换拖动选择 | |
|
||||
| `` <left>, h `` | 选择上一个区块 | |
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | 切換預存 | 切換現有行的狀態 (已預存/未預存) |
|
||||
| `` 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 `` | 切換拖曳選擇 | |
|
||||
| `` <left>, h `` | 選擇上一段 | |
|
||||
|
|
@ -372,6 +373,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | 切換預存 | 切換現有行的狀態 (已預存/未預存) |
|
||||
| `` 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 `` | 切換拖曳選擇 | |
|
||||
| `` <left>, h `` | 選擇上一段 | |
|
||||
|
|
|
|||
|
|
@ -610,6 +610,18 @@ func (self *CommitFilesController) PrimaryAction(mainViewName string, firstLineI
|
|||
})
|
||||
}
|
||||
|
||||
// DiscardSelection removes the selected diff line(s) from the commit the focused main
|
||||
// view shows the files of, via a rebase. Same target derivation as the patch toggle.
|
||||
func (self *CommitFilesController) DiscardSelection(mainViewName string, firstLineIdx int, lastLineIdx int) error {
|
||||
from, to, reverse := self.c.Helpers().CommitFiles.CurrentFromToReverseForPatchBuilding()
|
||||
canRebase := self.context().GetCanRebase()
|
||||
return discardSelectionFromCommit(self.c, mainViewName, firstLineIdx, lastLineIdx, from, to, reverse, canRebase)
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) DiscardSelectionDisabledReason() *types.DisabledReason {
|
||||
return discardFromCommitDisabledReason(self.c, self.context().GetCanRebase())
|
||||
}
|
||||
|
||||
// pathsForDiff returns the file paths to use for a diff command. When a text
|
||||
// filter is active and the node is a directory, only the visible (filtered)
|
||||
// file paths are returned so the diff reflects what the user sees.
|
||||
|
|
|
|||
|
|
@ -467,23 +467,78 @@ func (self *FilesController) PrimaryAction(mainViewName string, firstLineIdx int
|
|||
self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)
|
||||
}
|
||||
|
||||
infos, onStagedSide, ok := self.diffLineSelection(mainViewName, firstLineIdx, lastLineIdx)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The whole diff shown in the main view is on one side; space unstages when that's
|
||||
// the staged side (reverse), otherwise it stages. Either way it applies to the index.
|
||||
return self.applyDiffLineSelection(mainViewName, firstLineIdx, infos, onStagedSide,
|
||||
git_commands.ApplyPatchOpts{Reverse: onStagedSide, Cached: true})
|
||||
}
|
||||
|
||||
// DiscardSelectionDisabledReason: discarding from the working tree is always available
|
||||
// (a zero-context diff is reported as an error from DiscardSelection itself, matching the
|
||||
// staging view).
|
||||
func (self *FilesController) DiscardSelectionDisabledReason() *types.DisabledReason {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *FilesController) DiscardSelection(mainViewName string, firstLineIdx int, lastLineIdx int) error {
|
||||
if self.c.UserConfig().Git.DiffContextSize == 0 {
|
||||
return fmt.Errorf(self.c.Tr.Actions.NotEnoughContextToDiscard,
|
||||
self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)
|
||||
}
|
||||
|
||||
infos, onStagedSide, ok := self.diffLineSelection(mainViewName, firstLineIdx, lastLineIdx)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Discarding always reverses the change. On the staged side that's just unstaging
|
||||
// (apply reverse to the index) — same as space there — so it follows the identical
|
||||
// path and isn't destructive, hence no confirm. On the unstaged side it removes the
|
||||
// change from the working tree (reverse, not cached), which is destructive.
|
||||
return self.c.ConfirmIf(!onStagedSide && !self.c.UserConfig().Gui.SkipDiscardChangeWarning,
|
||||
types.ConfirmOpts{
|
||||
Title: self.c.Tr.DiscardChangeTitle,
|
||||
Prompt: self.c.Tr.DiscardChangePrompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.applyDiffLineSelection(mainViewName, firstLineIdx, infos, onStagedSide,
|
||||
git_commands.ApplyPatchOpts{Reverse: true, Cached: onStagedSide})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// diffLineSelection resolves the change lines the user has selected in the focused main
|
||||
// view and which side of the diff they're on. onStagedSide is true when the diff shown
|
||||
// is the staged side — the secondary half of a split, or the main half of an only-staged
|
||||
// file; it's the same for every file in a multi-file (directory) diff. ok is false when
|
||||
// there's no file or the selection covers no change lines (the caller does nothing).
|
||||
func (self *FilesController) diffLineSelection(mainViewName string, firstLineIdx int, lastLineIdx int) (infos []types.DiffLineInfo, onStagedSide bool, ok bool) {
|
||||
node := self.context().GetSelected()
|
||||
if node == nil {
|
||||
return nil
|
||||
return nil, false, false
|
||||
}
|
||||
|
||||
infos := self.c.Helpers().Staging.ChangeLinesInViewRange(mainViewName, firstLineIdx, lastLineIdx)
|
||||
infos = self.c.Helpers().Staging.ChangeLinesInViewRange(mainViewName, firstLineIdx, lastLineIdx)
|
||||
if len(infos) == 0 {
|
||||
return nil
|
||||
return nil, false, false
|
||||
}
|
||||
|
||||
// The whole diff shown in the main view is on one side — the staged diff in
|
||||
// the secondary half of a split, and in the main half when there are only
|
||||
// staged changes; in those cases space unstages, otherwise it stages. The
|
||||
// direction is the same for every file in a multi-file (directory) diff.
|
||||
_, mainShowsStaged := self.diffSplitState(node)
|
||||
reverse := mainShowsStaged || mainViewName == self.c.Contexts().NormalSecondary.GetViewName()
|
||||
onStagedSide = mainShowsStaged || mainViewName == self.c.Contexts().NormalSecondary.GetViewName()
|
||||
return infos, onStagedSide, true
|
||||
}
|
||||
|
||||
// applyDiffLineSelection applies the selected change lines (grouped per file for a
|
||||
// multi-file diff) with opts, then re-establishes the selection and focus the way the
|
||||
// staging view does — shared by the primary action (stage/unstage) and discard, since
|
||||
// discarding on the staged side is just unstaging and must follow the identical path.
|
||||
// onStagedSide names which diff each file's patch is read from and drives the focus-
|
||||
// follow; opts says how to apply it.
|
||||
func (self *FilesController) applyDiffLineSelection(mainViewName string, firstLineIdx int, infos []types.DiffLineInfo, onStagedSide bool, opts git_commands.ApplyPatchOpts) error {
|
||||
// A directory diff spans several files; group the selected change lines by
|
||||
// file and apply one patch per file.
|
||||
infosByFile := lo.GroupBy(infos, func(info types.DiffLineInfo) string { return info.Path })
|
||||
|
|
@ -494,24 +549,21 @@ func (self *FilesController) PrimaryAction(mainViewName string, firstLineIdx int
|
|||
if file == nil {
|
||||
continue
|
||||
}
|
||||
// Staging reads the side being acted on (unstaged when staging, staged when
|
||||
// unstaging — both = reverse here) and applies to the index either way.
|
||||
if err := self.applyDiffLines(file, fileInfos, reverse, git_commands.ApplyPatchOpts{Reverse: reverse, Cached: true}); err != nil {
|
||||
if err := self.applyDiffLines(file, fileInfos, onStagedSide, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.STAGING}})
|
||||
|
||||
// Focus follows the side that was acted on. Staging keeps it in the main half
|
||||
// (which always holds the unstaged side, or the staged side once the file has
|
||||
// only staged changes). Unstaging keeps it on the staged side, which lives in
|
||||
// the secondary half once the file is split into staged + unstaged, and moves
|
||||
// back to the main half when the staged side empties and the split collapses.
|
||||
// The model is up to date now (Refresh above is synchronous), so the post-op
|
||||
// split is read from the freshly selected node.
|
||||
// Focus follows the side that was acted on. The unstaged side stays in the main half
|
||||
// (which always holds the unstaged side, or the staged side once the file has only
|
||||
// staged changes). The staged side lives in the secondary half once the file is split
|
||||
// into staged + unstaged, and moves back to the main half when the staged side empties
|
||||
// and the split collapses. The model is up to date now (Refresh above is synchronous),
|
||||
// so the post-op split is read from the freshly selected node.
|
||||
focusViewName := self.c.Contexts().Normal.GetViewName()
|
||||
if reverse {
|
||||
if onStagedSide {
|
||||
if node := self.context().GetSelected(); node != nil {
|
||||
if split, _ := self.diffSplitState(node); split {
|
||||
focusViewName = self.c.Contexts().NormalSecondary.GetViewName()
|
||||
|
|
@ -519,9 +571,9 @@ func (self *FilesController) PrimaryAction(mainViewName string, firstLineIdx int
|
|||
}
|
||||
}
|
||||
|
||||
// The staging Refresh above queued the main-view re-render; re-establish the
|
||||
// selection in whichever pane now holds the acted-on side once that render lands,
|
||||
// and focus that pane if staging moved it there.
|
||||
// The Refresh above queued the main-view re-render; re-establish the selection at its
|
||||
// change-line ordinal once that render lands, and focus the pane now holding the
|
||||
// acted-on side.
|
||||
revealSelectionAfterPrimaryAction(self.c, mainViewName, focusViewName, firstLineIdx)
|
||||
if focusViewName != mainViewName {
|
||||
self.c.Context().Push(mainContextForViewName(self.c, focusViewName), types.OnFocusOpts{})
|
||||
|
|
|
|||
|
|
@ -79,6 +79,14 @@ func (self *MainViewController) GetKeybindings(opts types.KeybindingsOpts) []*ty
|
|||
Tooltip: self.c.Tr.StageSelectionTooltip,
|
||||
DisplayOnScreen: selectionShown,
|
||||
},
|
||||
{
|
||||
Keys: opts.GetKeys(opts.Config.Universal.Remove),
|
||||
Handler: self.discardSelection,
|
||||
GetDisabledReason: self.discardSelectionDisabledReason,
|
||||
Description: self.c.Tr.DiscardSelection,
|
||||
Tooltip: self.c.Tr.DiscardSelectionTooltip,
|
||||
DisplayOnScreen: selectionShown,
|
||||
},
|
||||
{
|
||||
Keys: opts.GetKeys(opts.Config.Universal.GoInto),
|
||||
Handler: self.enter,
|
||||
|
|
@ -306,6 +314,29 @@ func (self *MainViewController) primaryAction() error {
|
|||
return actions.PrimaryAction(self.context.GetViewName(), first, last)
|
||||
}
|
||||
|
||||
// discardSelection discards the selected diff line(s), delegating to the panel beneath
|
||||
// (working-tree discard for files, line-removal from the commit for the commit panels).
|
||||
// As with the primary action, the handler does its own re-render. A no-op when the panel
|
||||
// beneath offers no actions; discardSelectionDisabledReason gates the cases where the
|
||||
// panel has discard but it isn't applicable right now.
|
||||
func (self *MainViewController) discardSelection() error {
|
||||
actions := self.focusedMainViewActions()
|
||||
if actions == nil {
|
||||
return nil
|
||||
}
|
||||
v := self.context.GetView()
|
||||
first, last := v.SelectedLineRange()
|
||||
return actions.DiscardSelection(self.context.GetViewName(), first, last)
|
||||
}
|
||||
|
||||
func (self *MainViewController) discardSelectionDisabledReason() *types.DisabledReason {
|
||||
actions := self.focusedMainViewActions()
|
||||
if actions == nil {
|
||||
return nil
|
||||
}
|
||||
return actions.DiscardSelectionDisabledReason()
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/patch"
|
||||
"github.com/jesseduffield/lazygit/pkg/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
|
@ -128,6 +130,102 @@ func togglePatchLines(c *ControllerCommon, infos []types.DiffLineInfo) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// discardFromCommitDisabledReason reports why discarding lines from the commit shown in
|
||||
// the focused main view is unavailable, or nil when it's available. It mirrors the patch
|
||||
// builder's own guard: the diff must be a local commit's (canRebase), no rebase may be in
|
||||
// progress, and the diff context size must be non-zero (a patch can't be built from a
|
||||
// zero-context diff). Stash and sub-commits of another branch are never rebaseable, so
|
||||
// they always get the local-commits reason.
|
||||
func discardFromCommitDisabledReason(c *ControllerCommon, canRebase bool) *types.DisabledReason {
|
||||
if !canRebase {
|
||||
return &types.DisabledReason{Text: c.Tr.CanOnlyDiscardFromLocalCommits}
|
||||
}
|
||||
if c.Git().Status.WorkingTreeState().Any() {
|
||||
return &types.DisabledReason{Text: c.Tr.CantPatchWhileRebasingError}
|
||||
}
|
||||
if c.UserConfig().Git.DiffContextSize == 0 {
|
||||
return &types.DisabledReason{Text: fmt.Sprintf(c.Tr.Actions.NotEnoughContextToRemoveLines,
|
||||
c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// discardSelectionFromCommit discards the selected diff line(s) — a single line, a
|
||||
// range, or a hunk — from the commit whose diff the focused main view shows, by building
|
||||
// a one-off patch from the selection and removing it from the commit via a rebase. It is
|
||||
// the patch-building counterpart of the working-tree discard the files panel does, and
|
||||
// mirrors the patch builder's own "discard lines from commit": any in-progress custom
|
||||
// patch is reset first (the confirm prompt warns when one exists), then a fresh patch
|
||||
// holding exactly the selection is built for (from, to, reverse) and removed from the
|
||||
// commit. The caller has already established (via canRebase) that the commit is on a
|
||||
// local branch; the target commit is the one identified by `to`.
|
||||
func discardSelectionFromCommit(
|
||||
c *ControllerCommon,
|
||||
mainViewName string,
|
||||
firstLineIdx int,
|
||||
lastLineIdx int,
|
||||
from string,
|
||||
to string,
|
||||
reverse bool,
|
||||
canRebase bool,
|
||||
) error {
|
||||
infos := c.Helpers().Staging.ChangeLinesInViewRange(mainViewName, firstLineIdx, lastLineIdx)
|
||||
if len(infos) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
commitIndex := -1
|
||||
for i, commit := range c.Model().Commits {
|
||||
if commit.Hash() == to {
|
||||
commitIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if commitIndex == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
patchBuilder := c.Git().Patch.PatchBuilder
|
||||
prompt := lo.Ternary(patchBuilder.Active(),
|
||||
c.Tr.DiscardLinesFromCommitPromptWithReset,
|
||||
c.Tr.DiscardLinesFromCommitPrompt)
|
||||
|
||||
c.Confirm(types.ConfirmOpts{
|
||||
Title: c.Tr.DiscardLinesFromCommitTitle,
|
||||
Prompt: prompt,
|
||||
HandleConfirm: func() error {
|
||||
// Build a fresh patch holding exactly the selection: reset any active patch,
|
||||
// then add the selected lines (togglePatchLines adds them, the patch being empty).
|
||||
if patchBuilder.Active() {
|
||||
patchBuilder.Reset()
|
||||
}
|
||||
patchBuilder.Start(from, to, reverse, canRebase)
|
||||
if err := togglePatchLines(c, infos); err != nil {
|
||||
return err
|
||||
}
|
||||
if patchBuilder.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
|
||||
Message: c.Tr.RebasingStatus,
|
||||
HideWorkingTreeState: true,
|
||||
}, func(gocui.Task) error {
|
||||
c.LogAction(c.Tr.Actions.RemovePatchFromCommit)
|
||||
err := c.Git().Patch.DeletePatchesFromCommit(c.Model().Commits, commitIndex)
|
||||
// The rebase removes the selected lines, so the commit's diff shrinks
|
||||
// there. Re-establish the selection at its change-line ordinal once the
|
||||
// refresh below re-renders the diff, advancing to the next surviving change
|
||||
// just like staging — installed before the refresh so the re-render rides
|
||||
// it. The diff stays in the same pane, so source and target are the same.
|
||||
revealSelectionAfterPrimaryAction(c, mainViewName, mainViewName, firstLineIdx)
|
||||
return c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// patchFilename maps a diff line's absolute path to the key the patch builder stores
|
||||
// the file under — its repo-relative, slash-separated path.
|
||||
func patchFilename(c *ControllerCommon, absPath string) string {
|
||||
|
|
|
|||
|
|
@ -190,6 +190,32 @@ func (self *SwitchToDiffFilesController) PrimaryAction(mainViewName string, firs
|
|||
})
|
||||
}
|
||||
|
||||
// DiscardSelection removes the selected line(s) of the whole-commit diff from the commit,
|
||||
// via a rebase. Same target derivation as the patch toggle; only enabled for local
|
||||
// commits (see DiscardSelectionDisabledReason), so stash and other-branch sub-commits
|
||||
// don't reach the rebase.
|
||||
func (self *SwitchToDiffFilesController) DiscardSelection(mainViewName string, firstLineIdx int, lastLineIdx int) error {
|
||||
ref := self.context.GetSelectedRef()
|
||||
if ref == nil {
|
||||
return nil
|
||||
}
|
||||
refsRange := self.context.GetSelectedRefRangeForDiffFiles()
|
||||
|
||||
from, to := context.FromAndToForDiff(ref, refsRange)
|
||||
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(from)
|
||||
canRebase := self.canRebase(ref, refsRange)
|
||||
|
||||
return discardSelectionFromCommit(self.c, mainViewName, firstLineIdx, lastLineIdx, from, to, reverse, canRebase)
|
||||
}
|
||||
|
||||
func (self *SwitchToDiffFilesController) DiscardSelectionDisabledReason() *types.DisabledReason {
|
||||
canRebase := false
|
||||
if ref := self.context.GetSelectedRef(); ref != nil {
|
||||
canRebase = self.canRebase(ref, self.context.GetSelectedRefRangeForDiffFiles())
|
||||
}
|
||||
return discardFromCommitDisabledReason(self.c, canRebase)
|
||||
}
|
||||
|
||||
func (self *SwitchToDiffFilesController) canEnter() *types.DisabledReason {
|
||||
refRange := self.context.GetSelectedRefRangeForDiffFiles()
|
||||
if refRange != nil {
|
||||
|
|
|
|||
|
|
@ -373,6 +373,14 @@ type FocusedMainViewActions interface {
|
|||
// the selection itself (staging can move the acted-on side to the other pane,
|
||||
// which the handler then focuses).
|
||||
PrimaryAction(mainViewName string, firstLineIdx int, lastLineIdx int) error
|
||||
|
||||
// DiscardSelection discards the selected diff line(s) when the user presses the
|
||||
// remove key: from the working tree for the files panel, or from the commit (via a
|
||||
// rebase) for the commit panels. DiscardSelectionDisabledReason reports why discard
|
||||
// is unavailable here — e.g. the diff isn't a local commit's, or a rebase is in
|
||||
// progress — or nil when it's available.
|
||||
DiscardSelection(mainViewName string, firstLineIdx int, lastLineIdx int) error
|
||||
DiscardSelectionDisabledReason() *DisabledReason
|
||||
}
|
||||
|
||||
type IController interface {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
package patch_building
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var DiscardLinesFromCommitMainView = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Discard a line from a local commit straight from the focused main view, without entering the commit files",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
config.GetUserConfig().Gui.UseHunkModeInStagingView = false
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.EmptyCommit("first commit")
|
||||
|
||||
shell.CreateFileAndAdd("file1", "1st line\n2nd line\n3rd line\n")
|
||||
shell.Commit("commit to remove from")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("commit to remove from").IsSelected(),
|
||||
Contains("first commit"),
|
||||
).
|
||||
// Focus the commit's diff straight from the commits panel, rather than
|
||||
// entering the commit files panel first.
|
||||
Press(keys.Universal.FocusMainView)
|
||||
|
||||
t.Views().Main().
|
||||
IsFocused().
|
||||
SelectedLines(
|
||||
Contains("+1st line"),
|
||||
).
|
||||
// Move down to the second line and discard just that one from the commit.
|
||||
Press(keys.Universal.NextItem).
|
||||
SelectedLines(
|
||||
Contains("+2nd line"),
|
||||
).
|
||||
Press(keys.Universal.Remove)
|
||||
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Discard lines from commit")).
|
||||
Content(Equals("Are you sure you want to discard the selected lines from this commit?")).
|
||||
Confirm()
|
||||
|
||||
// After the rebase the commit keeps only the 1st and 3rd lines, and the selection
|
||||
// advances to the next surviving change (the 3rd line, now at the discarded line's
|
||||
// ordinal) rather than staying stale — like staging or discarding in the files panel.
|
||||
t.Views().Main().
|
||||
ContainsLines(
|
||||
Equals("+1st line"),
|
||||
Equals("+3rd line"),
|
||||
).
|
||||
Content(DoesNotContain("2nd line")).
|
||||
SelectedLines(
|
||||
Contains("+3rd line"),
|
||||
)
|
||||
},
|
||||
})
|
||||
58
pkg/integration/tests/staging/discard_from_main_view.go
Normal file
58
pkg/integration/tests/staging/discard_from_main_view.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package staging
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var DiscardFromMainView = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Discard a hunk from the working tree in the focused main view, without diving into the staging view",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
config.GetUserConfig().Gui.UseHunkModeInStagingView = false
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("file1", "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\n")
|
||||
shell.Commit("one")
|
||||
|
||||
// Two separate change blocks, far enough apart to stay distinct hunks.
|
||||
shell.UpdateFile("file1", "one\ntwo\nTHREE\nfour\nfive\nsix\nseven\neight\nNINE\nten\n")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("file1").IsSelected(),
|
||||
).
|
||||
Press(keys.Universal.FocusMainView)
|
||||
|
||||
t.Views().Main().
|
||||
IsFocused().
|
||||
SelectedLines(
|
||||
Contains("-three"),
|
||||
).
|
||||
// `a` extends the selection to the whole change block around the cursor.
|
||||
Press(keys.Main.ToggleSelectHunk).
|
||||
SelectedLines(
|
||||
Contains("-three"),
|
||||
Contains("+THREE"),
|
||||
).
|
||||
Press(keys.Universal.Remove)
|
||||
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Discard change")).
|
||||
Content(Contains("Are you sure you want to discard this change")).
|
||||
Confirm()
|
||||
|
||||
// The first block was discarded from the working tree, leaving line 3 at its
|
||||
// committed value; the other block stays.
|
||||
t.Views().Main().
|
||||
IsFocused().
|
||||
Content(DoesNotContain("THREE")).
|
||||
ContainsLines(
|
||||
Contains("-nine"),
|
||||
Contains("+NINE"),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package staging
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var DiscardFromStagedMainView = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Discarding a hunk on the staged half of the focused main view just unstages it, advancing the selection to the next staged hunk like unstaging does",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
config.GetUserConfig().Gui.UseHunkModeInStagingView = true
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("file1", "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\neleven\n")
|
||||
shell.Commit("one")
|
||||
|
||||
// Two staged hunks...
|
||||
shell.UpdateFileAndAdd("file1", "one\ntwo\nTHREE\nfour\nfive\nsix\nseven\neight\nNINE\nten\neleven\n")
|
||||
// ...plus an unstaged change, so the main view splits into staged/unstaged.
|
||||
shell.UpdateFile("file1", "one\ntwo\nTHREE\nfour\nfive\nSIX\nseven\neight\nNINE\nten\neleven\n")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Press(keys.Universal.FocusMainView)
|
||||
|
||||
// The unstaged half is focused first; switch to the staged half.
|
||||
t.Views().Main().
|
||||
IsFocused().
|
||||
Press(keys.Universal.TogglePanel)
|
||||
|
||||
t.Views().Secondary().
|
||||
IsFocused().
|
||||
SelectedLines(
|
||||
Contains("-three"),
|
||||
Contains("+THREE"),
|
||||
).
|
||||
// Discarding on the staged side is just unstaging — no discard confirm — so the
|
||||
// selection stays in the staged half and advances to the next staged hunk
|
||||
// rather than getting lost or jumping to the unstaged half.
|
||||
Press(keys.Universal.Remove)
|
||||
|
||||
t.Views().Secondary().
|
||||
IsFocused().
|
||||
SelectedLines(
|
||||
Contains("-nine"),
|
||||
Contains("+NINE"),
|
||||
)
|
||||
|
||||
// The unstaged change is untouched.
|
||||
t.Views().Main().
|
||||
Content(Contains("-six")).
|
||||
Content(Contains("+SIX"))
|
||||
},
|
||||
})
|
||||
|
|
@ -366,6 +366,7 @@ var tests = []*components.IntegrationTest{
|
|||
patch_building.BuildMultiFileFromWholeCommitMainView,
|
||||
patch_building.CopyRenamedFileDiff,
|
||||
patch_building.DiscardLinesFromCommit,
|
||||
patch_building.DiscardLinesFromCommitMainView,
|
||||
patch_building.EditLineInPatchBuildingPanel,
|
||||
patch_building.MoveRangeToIndex,
|
||||
patch_building.MoveToEarlierCommit,
|
||||
|
|
@ -414,6 +415,8 @@ var tests = []*components.IntegrationTest{
|
|||
staging.DiffChangeScreenMode,
|
||||
staging.DiffContextChange,
|
||||
staging.DiscardAllChanges,
|
||||
staging.DiscardFromMainView,
|
||||
staging.DiscardFromStagedMainView,
|
||||
staging.FocusFollowsStagedSideToSecondaryAfterUnstaging,
|
||||
staging.FocusReturnsToMainAfterUnstagingLastStagedHunk,
|
||||
staging.Search,
|
||||
|
|
|
|||
Loading…
Reference in a new issue