mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
Stage the selected diff line from the focused main view
The focused main view already lets you point at a diff line and act on it (dive into staging, edit it, open it in a PR). This makes it a staging surface in its own right: press space to stage — or unstage — the selected line without diving into the separate staging view. To make that usable, the selection is now shown automatically whenever the main view holds a diff, anchored on the first change line already visible (so the view doesn't jump), rather than being toggled on demand from the middle of the view. That frees space for staging and means there's always a line to act on. Which contexts show a diff is marked by a new types.DiffMainViewContext, because "shows a diff" is the right signal, not "is stageable": reflog shows a diff (its selection drives edit/PR/navigation) but can't be staged, while a branch's log or the status dashboard show no diff and get no selection at all. Staging is delegated to the side panel beneath via a GetOnStageFocusedMainView handler mirroring GetOnClickFocusedMainView, so what "stage" means stays the panel's concern (the working tree stages/unstages; commits will later add to a custom patch). The files handler resolves the line's patch identity from the diff-line metadata, maps it to a patch line, and applies a one-line patch, choosing stage vs unstage from whether the shown diff is the unstaged or staged side (diffSplitState). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
d23fe24100
commit
494b9ce7cf
|
|
@ -224,7 +224,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <mouse wheel up> (fn+down) `` | Scroll up | |
|
||||
| `` <tab> `` | Switch view | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | Show/hide selection | |
|
||||
| `` <space> `` | Stage | Toggle selection staged / unstaged. |
|
||||
| `` <left>, h `` | Go to previous hunk | |
|
||||
| `` <right>, l `` | Go to next hunk | |
|
||||
| `` N `` | Go to previous file | |
|
||||
|
|
@ -334,7 +334,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> `` | Show/hide selection | |
|
||||
| `` <space> `` | Stage | Toggle selection staged / unstaged. |
|
||||
| `` <left>, h `` | Go to previous hunk | |
|
||||
| `` <right>, l `` | Go to next hunk | |
|
||||
| `` N `` | Go to previous file | |
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
|-----|--------|-------------|
|
||||
| `` <tab> `` | ビューを切り替え | 他のビュー(ステージされた変更/ステージされていない変更)に切り替えます。 |
|
||||
| `` <esc> `` | サイドパネルに戻る | |
|
||||
| `` <space> `` | Show/hide selection | |
|
||||
| `` <space> `` | ステージ | 選択された部分のステージ / アンステージを切り替えます。 |
|
||||
| `` <left>, h `` | 前のハンクに移動 | |
|
||||
| `` <right>, l `` | 次のハンクに移動 | |
|
||||
| `` N `` | Go to previous file | |
|
||||
|
|
@ -311,7 +311,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <mouse wheel up> (fn+down) `` | 上にスクロール | |
|
||||
| `` <tab> `` | ビューを切り替え | 他のビュー(ステージされた変更/ステージされていない変更)に切り替えます。 |
|
||||
| `` <esc> `` | サイドパネルに戻る | |
|
||||
| `` <space> `` | Show/hide selection | |
|
||||
| `` <space> `` | ステージ | 選択された部分のステージ / アンステージを切り替えます。 |
|
||||
| `` <left>, h `` | 前のハンクに移動 | |
|
||||
| `` <right>, l `` | 次のハンクに移動 | |
|
||||
| `` N `` | Go to previous file | |
|
||||
|
|
|
|||
|
|
@ -85,7 +85,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> `` | Show/hide selection | |
|
||||
| `` <space> `` | Staged 전환 | 선택한 행을 staged / unstaged |
|
||||
| `` <left>, h `` | 이전 hunk를 선택 | |
|
||||
| `` <right>, l `` | 다음 hunk를 선택 | |
|
||||
| `` N `` | Go to previous file | |
|
||||
|
|
@ -168,7 +168,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <mouse wheel up> (fn+down) `` | 위로 스크롤 | |
|
||||
| `` <tab> `` | 패널 전환 | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | Show/hide selection | |
|
||||
| `` <space> `` | Staged 전환 | 선택한 행을 staged / unstaged |
|
||||
| `` <left>, h `` | 이전 hunk를 선택 | |
|
||||
| `` <right>, l `` | 다음 hunk를 선택 | |
|
||||
| `` N `` | Go to previous file | |
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <mouse wheel up> (fn+down) `` | Scroll omhoog | |
|
||||
| `` <tab> `` | Ga naar een ander paneel | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | Show/hide selection | |
|
||||
| `` <space> `` | Toggle staged | Toggle lijnen staged / unstaged |
|
||||
| `` <left>, h `` | Selecteer de vorige hunk | |
|
||||
| `` <right>, l `` | Selecteer de volgende hunk | |
|
||||
| `` N `` | Go to previous file | |
|
||||
|
|
@ -312,7 +312,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> `` | Show/hide selection | |
|
||||
| `` <space> `` | Toggle staged | Toggle lijnen staged / unstaged |
|
||||
| `` <left>, h `` | Selecteer de vorige hunk | |
|
||||
| `` <right>, l `` | Selecteer de volgende hunk | |
|
||||
| `` N `` | Go to previous file | |
|
||||
|
|
|
|||
|
|
@ -100,7 +100,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> `` | Show/hide selection | |
|
||||
| `` <space> `` | Zatwierdź | Przełącz zaznaczenie zatwierdzone/niezatwierdzone. |
|
||||
| `` <left>, h `` | Idź do poprzedniego fragmentu | |
|
||||
| `` <right>, l `` | Idź do następnego fragmentu | |
|
||||
| `` N `` | Go to previous file | |
|
||||
|
|
@ -207,7 +207,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <mouse wheel up> (fn+down) `` | Przewiń w górę | |
|
||||
| `` <tab> `` | Przełącz widok | Przełącz na inny widok (zatwierdzone/niezatwierdzone zmiany). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | Show/hide selection | |
|
||||
| `` <space> `` | Zatwierdź | Przełącz zaznaczenie zatwierdzone/niezatwierdzone. |
|
||||
| `` <left>, h `` | Idź do poprzedniego fragmentu | |
|
||||
| `` <right>, l `` | Idź do następnego fragmentu | |
|
||||
| `` N `` | Go to previous file | |
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <mouse wheel up> (fn+down) `` | Rolar para cima | |
|
||||
| `` <tab> `` | Mudar de visão | Alternar para outra visão (staged/não processadas alterações). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | Show/hide selection | |
|
||||
| `` <space> `` | Etapa | Ativar/desativar seleção em staged/unstaged |
|
||||
| `` <left>, h `` | Ir para o local anterior | |
|
||||
| `` <right>, l `` | Ir para o próximo trecho | |
|
||||
| `` N `` | Go to previous file | |
|
||||
|
|
@ -343,7 +343,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> `` | Show/hide selection | |
|
||||
| `` <space> `` | Etapa | Ativar/desativar seleção em staged/unstaged |
|
||||
| `` <left>, h `` | Ir para o local anterior | |
|
||||
| `` <right>, l `` | Ir para o próximo trecho | |
|
||||
| `` N `` | Go to previous file | |
|
||||
|
|
|
|||
|
|
@ -75,7 +75,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> `` | Show/hide selection | |
|
||||
| `` <space> `` | Переключить индекс | Переключить строку в проиндексированные / непроиндексированные |
|
||||
| `` <left>, h `` | Выбрать предыдущую часть | |
|
||||
| `` <right>, l `` | Выбрать следующую часть | |
|
||||
| `` N `` | Go to previous file | |
|
||||
|
|
@ -112,7 +112,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <mouse wheel up> (fn+down) `` | Прокрутить вверх | |
|
||||
| `` <tab> `` | Переключиться на другую панель (проиндексированные/непроиндексированные изменения) | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | Show/hide selection | |
|
||||
| `` <space> `` | Переключить индекс | Переключить строку в проиндексированные / непроиндексированные |
|
||||
| `` <left>, h `` | Выбрать предыдущую часть | |
|
||||
| `` <right>, l `` | Выбрать следующую часть | |
|
||||
| `` N `` | Go to previous file | |
|
||||
|
|
|
|||
|
|
@ -287,7 +287,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
|-----|--------|-------------|
|
||||
| `` <tab> `` | 切换到其他面板 | 切换到其他视图(已暂存/未暂存的变更) |
|
||||
| `` <esc> `` | 退出回到侧边面板 | |
|
||||
| `` <space> `` | Show/hide selection | |
|
||||
| `` <space> `` | 切换暂存状态 | 切换行暂存状态 |
|
||||
| `` <left>, h `` | 选择上一个区块 | |
|
||||
| `` <right>, l `` | 选择下一个区块 | |
|
||||
| `` N `` | Go to previous file | |
|
||||
|
|
@ -340,7 +340,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <mouse wheel up> (fn+down) `` | 向上滚动 | |
|
||||
| `` <tab> `` | 切换到其他面板 | 切换到其他视图(已暂存/未暂存的变更) |
|
||||
| `` <esc> `` | 退出回到侧边面板 | |
|
||||
| `` <space> `` | Show/hide selection | |
|
||||
| `` <space> `` | 切换暂存状态 | 切换行暂存状态 |
|
||||
| `` <left>, h `` | 选择上一个区块 | |
|
||||
| `` <right>, l `` | 选择下一个区块 | |
|
||||
| `` N `` | Go to previous file | |
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
| `` <mouse wheel up> (fn+down) `` | 向上捲動 | |
|
||||
| `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` <space> `` | Show/hide selection | |
|
||||
| `` <space> `` | 切換預存 | 切換現有行的狀態 (已預存/未預存) |
|
||||
| `` <left>, h `` | 選擇上一段 | |
|
||||
| `` <right>, l `` | 選擇下一段 | |
|
||||
| `` N `` | Go to previous file | |
|
||||
|
|
@ -369,7 +369,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> `` | Show/hide selection | |
|
||||
| `` <space> `` | 切換預存 | 切換現有行的狀態 (已預存/未預存) |
|
||||
| `` <left>, h `` | 選擇上一段 | |
|
||||
| `` <right>, l `` | 選擇下一段 | |
|
||||
| `` N `` | Go to previous file | |
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ type BaseContext struct {
|
|||
onDoubleClickFn func() error
|
||||
onClickFn func(opts gocui.ViewMouseBindingOpts) error
|
||||
onClickFocusedMainViewFn onClickFocusedMainViewFn
|
||||
onStageFocusedMainViewFn onStageFocusedMainViewFn
|
||||
onRenderToMainFn func()
|
||||
onFocusFns []onFocusFn
|
||||
onFocusLostFns []onFocusLostFn
|
||||
|
|
@ -37,6 +38,7 @@ type (
|
|||
onFocusFn = func(types.OnFocusOpts)
|
||||
onFocusLostFn = func(types.OnFocusLostOpts)
|
||||
onClickFocusedMainViewFn = func(mainViewName string, clickedLineIdx int) error
|
||||
onStageFocusedMainViewFn = func(mainViewName string, viewLineIdx int) error
|
||||
)
|
||||
|
||||
var _ types.IBaseContext = &BaseContext{}
|
||||
|
|
@ -146,6 +148,7 @@ func (self *BaseContext) ClearAllAttachedControllerFunctions() {
|
|||
self.onDoubleClickFn = nil
|
||||
self.onClickFn = nil
|
||||
self.onClickFocusedMainViewFn = nil
|
||||
self.onStageFocusedMainViewFn = nil
|
||||
self.onRenderToMainFn = nil
|
||||
}
|
||||
|
||||
|
|
@ -188,6 +191,19 @@ func (self *BaseContext) GetOnClickFocusedMainView() onClickFocusedMainViewFn {
|
|||
return self.onClickFocusedMainViewFn
|
||||
}
|
||||
|
||||
func (self *BaseContext) AddOnStageFocusedMainViewFn(fn onStageFocusedMainViewFn) {
|
||||
if fn != nil {
|
||||
if self.onStageFocusedMainViewFn != nil {
|
||||
panic("only one controller is allowed to set an onStageFocusedMainViewFn")
|
||||
}
|
||||
self.onStageFocusedMainViewFn = fn
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetOnStageFocusedMainView() onStageFocusedMainViewFn {
|
||||
return self.onStageFocusedMainViewFn
|
||||
}
|
||||
|
||||
func (self *BaseContext) AddOnRenderToMainFn(fn func()) {
|
||||
if fn != nil {
|
||||
if self.onRenderToMainFn != nil {
|
||||
|
|
|
|||
|
|
@ -19,11 +19,14 @@ type CommitFilesContext struct {
|
|||
}
|
||||
|
||||
var (
|
||||
_ types.IListContext = (*CommitFilesContext)(nil)
|
||||
_ types.DiffableContext = (*CommitFilesContext)(nil)
|
||||
_ types.IFilterableContext = (*CommitFilesContext)(nil)
|
||||
_ types.IListContext = (*CommitFilesContext)(nil)
|
||||
_ types.DiffableContext = (*CommitFilesContext)(nil)
|
||||
_ types.IFilterableContext = (*CommitFilesContext)(nil)
|
||||
_ types.DiffMainViewContext = (*CommitFilesContext)(nil)
|
||||
)
|
||||
|
||||
func (self *CommitFilesContext) IsDiffMainViewContext() {}
|
||||
|
||||
func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
|
||||
viewModel := filetree.NewCommitFileTreeViewModel(
|
||||
func() []*models.CommitFile { return c.Model().CommitFiles },
|
||||
|
|
|
|||
|
|
@ -31,11 +31,14 @@ type commitDropIndicator struct {
|
|||
}
|
||||
|
||||
var (
|
||||
_ types.IListContext = (*LocalCommitsContext)(nil)
|
||||
_ types.DiffableContext = (*LocalCommitsContext)(nil)
|
||||
_ types.ISearchableContext = (*LocalCommitsContext)(nil)
|
||||
_ types.IListContext = (*LocalCommitsContext)(nil)
|
||||
_ types.DiffableContext = (*LocalCommitsContext)(nil)
|
||||
_ types.ISearchableContext = (*LocalCommitsContext)(nil)
|
||||
_ types.DiffMainViewContext = (*LocalCommitsContext)(nil)
|
||||
)
|
||||
|
||||
func (self *LocalCommitsContext) IsDiffMainViewContext() {}
|
||||
|
||||
func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
|
||||
dropIndicator := &commitDropIndicator{insertionIndex: -1}
|
||||
viewModel := NewLocalCommitsViewModel(
|
||||
|
|
|
|||
|
|
@ -14,10 +14,13 @@ type ReflogCommitsContext struct {
|
|||
}
|
||||
|
||||
var (
|
||||
_ types.IListContext = (*ReflogCommitsContext)(nil)
|
||||
_ types.DiffableContext = (*ReflogCommitsContext)(nil)
|
||||
_ types.IListContext = (*ReflogCommitsContext)(nil)
|
||||
_ types.DiffableContext = (*ReflogCommitsContext)(nil)
|
||||
_ types.DiffMainViewContext = (*ReflogCommitsContext)(nil)
|
||||
)
|
||||
|
||||
func (self *ReflogCommitsContext) IsDiffMainViewContext() {}
|
||||
|
||||
func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
|
||||
viewModel := NewFilteredListViewModel(
|
||||
func() []*models.Commit { return c.Model().FilteredReflogCommits },
|
||||
|
|
|
|||
|
|
@ -12,10 +12,13 @@ type StashContext struct {
|
|||
}
|
||||
|
||||
var (
|
||||
_ types.IListContext = (*StashContext)(nil)
|
||||
_ types.DiffableContext = (*StashContext)(nil)
|
||||
_ types.IListContext = (*StashContext)(nil)
|
||||
_ types.DiffableContext = (*StashContext)(nil)
|
||||
_ types.DiffMainViewContext = (*StashContext)(nil)
|
||||
)
|
||||
|
||||
func (self *StashContext) IsDiffMainViewContext() {}
|
||||
|
||||
func NewStashContext(
|
||||
c *ContextCommon,
|
||||
) *StashContext {
|
||||
|
|
|
|||
|
|
@ -21,11 +21,14 @@ type SubCommitsContext struct {
|
|||
}
|
||||
|
||||
var (
|
||||
_ types.IListContext = (*SubCommitsContext)(nil)
|
||||
_ types.DiffableContext = (*SubCommitsContext)(nil)
|
||||
_ types.ISearchableContext = (*SubCommitsContext)(nil)
|
||||
_ types.IListContext = (*SubCommitsContext)(nil)
|
||||
_ types.DiffableContext = (*SubCommitsContext)(nil)
|
||||
_ types.ISearchableContext = (*SubCommitsContext)(nil)
|
||||
_ types.DiffMainViewContext = (*SubCommitsContext)(nil)
|
||||
)
|
||||
|
||||
func (self *SubCommitsContext) IsDiffMainViewContext() {}
|
||||
|
||||
func NewSubCommitsContext(
|
||||
c *ContextCommon,
|
||||
) *SubCommitsContext {
|
||||
|
|
|
|||
|
|
@ -15,10 +15,13 @@ type WorkingTreeContext struct {
|
|||
}
|
||||
|
||||
var (
|
||||
_ types.IListContext = (*WorkingTreeContext)(nil)
|
||||
_ types.IFilterableContext = (*WorkingTreeContext)(nil)
|
||||
_ types.IListContext = (*WorkingTreeContext)(nil)
|
||||
_ types.IFilterableContext = (*WorkingTreeContext)(nil)
|
||||
_ types.DiffMainViewContext = (*WorkingTreeContext)(nil)
|
||||
)
|
||||
|
||||
func (self *WorkingTreeContext) IsDiffMainViewContext() {}
|
||||
|
||||
func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
|
||||
viewModel := filetree.NewFileTreeViewModel(
|
||||
func() []*models.File { return c.Model().Files },
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ func AttachControllers(context types.Context, controllers ...types.IController)
|
|||
context.AddOnDoubleClickFn(controller.GetOnDoubleClick())
|
||||
context.AddOnClickFn(controller.GetOnClick())
|
||||
context.AddOnClickFocusedMainViewFn(controller.GetOnClickFocusedMainView())
|
||||
context.AddOnStageFocusedMainViewFn(controller.GetOnStageFocusedMainView())
|
||||
context.AddOnRenderToMainFn(controller.GetOnRenderToMain())
|
||||
context.AddOnFocusFn(controller.GetOnFocus())
|
||||
context.AddOnFocusLostFn(controller.GetOnFocusLost())
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ func (self *baseController) GetOnClickFocusedMainView() func(mainViewName string
|
|||
return nil
|
||||
}
|
||||
|
||||
func (self *baseController) GetOnStageFocusedMainView() func(mainViewName string, viewLineIdx int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *baseController) GetOnClick() func(opts gocui.ViewMouseBindingOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/jesseduffield/generics/set"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/patch"
|
||||
"github.com/jesseduffield/lazygit/pkg/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||
|
|
@ -456,6 +457,72 @@ func (self *FilesController) diffSplitState(node *filetree.FileNode) (split bool
|
|||
return split, mainShowsStaged
|
||||
}
|
||||
|
||||
func (self *FilesController) GetOnStageFocusedMainView() func(mainViewName string, viewLineIdx int) error {
|
||||
return func(mainViewName string, viewLineIdx int) error {
|
||||
if self.c.UserConfig().Git.DiffContextSize == 0 {
|
||||
return fmt.Errorf(self.c.Tr.Actions.NotEnoughContextToStage,
|
||||
self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)
|
||||
}
|
||||
|
||||
node := self.context().GetSelected()
|
||||
if node == nil || !node.IsFile() {
|
||||
// Staging a line of a multi-file (directory) diff is a later step; for
|
||||
// now only single-file diffs are stageable from the focused main view.
|
||||
return nil
|
||||
}
|
||||
|
||||
info, ok := self.c.Helpers().Staging.GetDiffLineInfo(mainViewName, viewLineIdx)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The staged diff is shown in the secondary half of a split, and in the main
|
||||
// half when the file has only staged changes; in those cases space unstages,
|
||||
// otherwise it stages.
|
||||
_, mainShowsStaged := self.diffSplitState(node)
|
||||
staged := mainShowsStaged || mainViewName == self.c.Contexts().NormalSecondary.GetViewName()
|
||||
|
||||
return self.stageDiffLine(node.File, info, staged)
|
||||
}
|
||||
}
|
||||
|
||||
// stageDiffLine stages, or when reverse is true unstages, the single diff line
|
||||
// identified by info. It builds a one-line patch from the file's diff and applies
|
||||
// it the same way the staging view does, but resolves the patch line from the
|
||||
// diff-line metadata rather than from a patch-explorer selection. A context or
|
||||
// header line yields an empty patch and is a no-op.
|
||||
func (self *FilesController) stageDiffLine(file *models.File, info types.DiffLineInfo, reverse bool) error {
|
||||
parsedPatch := patch.Parse(self.c.Git().WorkingTree.WorktreeFileDiff(file, true, reverse))
|
||||
|
||||
lineNumber, isDeletion := info.PatchSelectLine()
|
||||
patchLineIdx := parsedPatch.PatchLineForLineNumber(lineNumber)
|
||||
if isDeletion {
|
||||
patchLineIdx = parsedPatch.PatchLineForOldLineNumber(lineNumber)
|
||||
}
|
||||
|
||||
patchToApply := parsedPatch.
|
||||
Transform(patch.TransformOpts{
|
||||
Reverse: reverse,
|
||||
IncludedLineIndices: []int{patchLineIdx},
|
||||
FileNameOverride: file.GetPath(),
|
||||
}).
|
||||
FormatPlain()
|
||||
if patchToApply == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.ApplyPatch)
|
||||
if err := self.c.Git().Patch.ApplyPatch(patchToApply, git_commands.ApplyPatchOpts{
|
||||
Reverse: reverse,
|
||||
Cached: true,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.STAGING}})
|
||||
return nil
|
||||
}
|
||||
|
||||
// if we are dealing with a status for which there is no key in this map,
|
||||
// then we won't optimistically render: we'll just let `git status` tell
|
||||
// us what the new status is.
|
||||
|
|
|
|||
|
|
@ -58,6 +58,29 @@ func (self *StagingHelper) AdjacentFile(view *gocui.View, anchorViewLine int, fo
|
|||
return view.ViewLineForBufferLine(target)
|
||||
}
|
||||
|
||||
// FirstChangeLineInView returns the view line of the first change line at or below
|
||||
// the top of the viewport, for placing the initial selection when focusing the main
|
||||
// view: we select the first change the user can already see rather than jumping to
|
||||
// the top of the diff (which would be jarring when the view is scrolled down). If
|
||||
// the top visible line is itself mid-change-block, that line is returned, so the
|
||||
// selection stays put. ok is false when no change line is loaded at or below the top
|
||||
// (e.g. scrolled into trailing context, or the diff isn't loaded that far yet),
|
||||
// leaving the caller to fall back.
|
||||
func (self *StagingHelper) FirstChangeLineInView(view *gocui.View) (int, bool) {
|
||||
top, ok := view.BufferLineForViewLine(view.OriginY())
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
resolved := self.resolveDiffLines(view.DiffLineContents())
|
||||
for i := top; i < len(resolved); i++ {
|
||||
if resolved[i].ok && resolved[i].info.IsChange() {
|
||||
return view.ViewLineForBufferLine(i)
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// changeBlockStart finds, in a diff whose lines are flagged by isChange, the first
|
||||
// line of the change block adjacent to `from` in the given direction. It is the pure
|
||||
// index arithmetic behind AdjacentChangeBlock, mirroring the staging view's
|
||||
|
|
|
|||
|
|
@ -38,9 +38,10 @@ func NewMainViewController(
|
|||
}
|
||||
|
||||
func (self *MainViewController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
// When a selection is shown, we surface the bindings that act on it
|
||||
// A selection is shown whenever the main view holds a diff (see
|
||||
// sidePanelShowsDiff); we surface the bindings that act on it
|
||||
// (enter to dive into staging, e to edit the selected line, G to open the
|
||||
// line in the branch's pull request, escape to hide the selection).
|
||||
// line in the branch's pull request).
|
||||
selectionShown := self.context.GetView().Highlight
|
||||
|
||||
var enterDescription string
|
||||
|
|
@ -73,9 +74,10 @@ func (self *MainViewController) GetKeybindings(opts types.KeybindingsOpts) []*ty
|
|||
},
|
||||
{
|
||||
Keys: opts.GetKeys(opts.Config.Universal.Select),
|
||||
Handler: self.toggleSelection,
|
||||
Description: self.c.Tr.ToggleSelectionInFocusedMainView,
|
||||
DisplayOnScreen: !selectionShown,
|
||||
Handler: self.stageSelectedLine,
|
||||
Description: self.c.Tr.Stage,
|
||||
Tooltip: self.c.Tr.StageSelectionTooltip,
|
||||
DisplayOnScreen: selectionShown,
|
||||
},
|
||||
{
|
||||
Keys: opts.GetKeys(opts.Config.Universal.GoInto),
|
||||
|
|
@ -180,32 +182,57 @@ func (self *MainViewController) GetOnFocus() func(types.OnFocusOpts) {
|
|||
}
|
||||
|
||||
func (self *MainViewController) togglePanel() error {
|
||||
if self.otherContext.GetView().Visible {
|
||||
self.c.Context().Push(self.otherContext, types.OnFocusOpts{})
|
||||
if !self.otherContext.GetView().Visible {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Capture diff-view-ness while our context is still the focused main view (so
|
||||
// NextInStack finds the side panel beneath it), before pushing the other pane.
|
||||
isDiff := self.isDiffView()
|
||||
self.c.Context().Push(self.otherContext, types.OnFocusOpts{})
|
||||
if isDiff {
|
||||
showInitialDiffSelection(self.c, self.otherContext.GetView())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *MainViewController) escape() error {
|
||||
v := self.context.GetView()
|
||||
if v.Highlight {
|
||||
v.Highlight = false
|
||||
return nil
|
||||
// showInitialDiffSelection turns on the focused main view's selection when entering
|
||||
// a diff view without pointing at a specific line: on the first change line already
|
||||
// visible (so the view doesn't jump), falling back to the current top line when none
|
||||
// is visible (scrolled into trailing context, or not loaded that far yet).
|
||||
func showInitialDiffSelection(c *ControllerCommon, view *gocui.View) {
|
||||
target, ok := c.Helpers().Staging.FirstChangeLineInView(view)
|
||||
if !ok {
|
||||
target = view.OriginY()
|
||||
}
|
||||
showSelectionAtLine(view, target, true)
|
||||
}
|
||||
|
||||
func (self *MainViewController) escape() error {
|
||||
self.c.Context().Pop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *MainViewController) toggleSelection() error {
|
||||
v := self.context.GetView()
|
||||
if v.Highlight {
|
||||
v.Highlight = false
|
||||
// isDiffView reports whether the focused main view currently shows a diff (so we
|
||||
// show a selection in it). See sidePanelShowsDiff.
|
||||
func (self *MainViewController) isDiffView() bool {
|
||||
return sidePanelShowsDiff(self.c.Context().NextInStack(self.context))
|
||||
}
|
||||
|
||||
// stageSelectedLine stages (or unstages) the selected diff line, delegating to the
|
||||
// side panel beneath the focused main view since what "stage" means is the panel's
|
||||
// business (the working tree stages; later, commits add to a custom patch). Panels
|
||||
// whose diff isn't stageable register no handler, so this is a no-op there.
|
||||
func (self *MainViewController) stageSelectedLine() error {
|
||||
sidePanelContext := self.c.Context().NextInStack(self.context)
|
||||
if sidePanelContext == nil {
|
||||
return nil
|
||||
}
|
||||
// Start the selection in the middle of the visible content.
|
||||
showSelectionAtLine(v, v.MiddleVisibleLineIdx(), false)
|
||||
return nil
|
||||
handler := sidePanelContext.GetOnStageFocusedMainView()
|
||||
if handler == nil {
|
||||
return nil
|
||||
}
|
||||
return handler(self.context.GetViewName(), self.context.GetView().SelectedLineIdx())
|
||||
}
|
||||
|
||||
func (self *MainViewController) enter() error {
|
||||
|
|
@ -279,6 +306,16 @@ func (self *MainViewController) prevFile() error {
|
|||
return self.navigate(self.c.Helpers().Staging.AdjacentFile, false)
|
||||
}
|
||||
|
||||
// sidePanelShowsDiff reports whether the given side panel's focused main view
|
||||
// shows a diff, which is when we show a selection in it (so the user can stage a
|
||||
// line, edit it, jump by hunk/file, or open it in a PR). Panels whose main view
|
||||
// shows non-diff content (a branch's commit log, the status dashboard, …) show no
|
||||
// selection because there's nothing to act on. See types.DiffMainViewContext.
|
||||
func sidePanelShowsDiff(sidePanel types.Context) bool {
|
||||
_, ok := sidePanel.(types.DiffMainViewContext)
|
||||
return ok
|
||||
}
|
||||
|
||||
// focusedMainViewContextForViewName maps a focused main view's view name (as
|
||||
// passed to GetOnClickFocusedMainView) to its context.
|
||||
func focusedMainViewContextForViewName(c *ControllerCommon, viewName string) types.Context {
|
||||
|
|
@ -422,6 +459,9 @@ func githubPullRequestLineURL(prURL string, commitSha string, relativePath strin
|
|||
}
|
||||
|
||||
func (self *MainViewController) onClickInAlreadyFocusedView(opts gocui.ViewMouseBindingOpts) error {
|
||||
if !self.isDiffView() {
|
||||
return nil
|
||||
}
|
||||
// A click points at a line, so it sets the selection there; a double-click
|
||||
// additionally dives into staging/patch-building for that line.
|
||||
showSelectionAtLine(self.context.GetView(), opts.Y, false)
|
||||
|
|
@ -437,6 +477,9 @@ func (self *MainViewController) editClickedLine(opts gocui.ViewMouseBindingOpts)
|
|||
|
||||
func (self *MainViewController) onClickInOtherViewOfMainViewPair(opts gocui.ViewMouseBindingOpts) error {
|
||||
self.c.Context().Push(self.context, types.OnFocusOpts{})
|
||||
if !self.isDiffView() {
|
||||
return nil
|
||||
}
|
||||
showSelectionAtLine(self.context.GetView(), opts.Y, false)
|
||||
if opts.IsDoubleClick {
|
||||
return self.enterForLine(opts.Y)
|
||||
|
|
|
|||
|
|
@ -69,9 +69,11 @@ func (self *SwitchToFocusedMainViewController) onClickSecondary(opts gocui.ViewM
|
|||
}
|
||||
|
||||
func (self *SwitchToFocusedMainViewController) handleFocusMainView() error {
|
||||
// Focusing by keyboard doesn't point at any particular line, so we don't
|
||||
// show a selection; the user is free to scroll. Clicking does point at a
|
||||
// line, so it selects it (see focusMainView's clickedLineIdx).
|
||||
// Focusing by keyboard doesn't point at any particular line: in a diff view
|
||||
// we start at the first change block (like entering the staging view), in a
|
||||
// non-diff view we show no selection and the user just scrolls. Clicking does
|
||||
// point at a line, so it selects that line instead (focusMainView's
|
||||
// clickedLineIdx).
|
||||
return self.focusMainView(self.c.Contexts().Normal, -1)
|
||||
}
|
||||
|
||||
|
|
@ -80,8 +82,18 @@ func (self *SwitchToFocusedMainViewController) focusMainView(mainViewContext typ
|
|||
context.ClearSearchString()
|
||||
}
|
||||
self.c.Context().Push(mainViewContext, types.OnFocusOpts{})
|
||||
|
||||
if !sidePanelShowsDiff(self.context) {
|
||||
// Non-diff main content (e.g. a branch's commit log): focus only, no
|
||||
// selection, since there's nothing to act on.
|
||||
return nil
|
||||
}
|
||||
|
||||
view := mainViewContext.GetView()
|
||||
if clickedLineIdx >= 0 {
|
||||
showSelectionAtLine(mainViewContext.GetView(), clickedLineIdx, false)
|
||||
showSelectionAtLine(view, clickedLineIdx, false)
|
||||
} else {
|
||||
showInitialDiffSelection(self.c, view)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,9 @@ type IBaseContext interface {
|
|||
// Likewise for the focused main view: we need this to communicate between a
|
||||
// side panel controller and the focused main view controller.
|
||||
AddOnClickFocusedMainViewFn(func(mainViewName string, clickedLineIdx int) error)
|
||||
// And for staging the selected line directly from the focused main view (space),
|
||||
// delegated to the side panel that owns the diff being shown.
|
||||
AddOnStageFocusedMainViewFn(func(mainViewName string, viewLineIdx int) error)
|
||||
// Adding on to the above, this is so that a list-specific handler can register
|
||||
// a hook for doing additional click handling
|
||||
AddOnClickFn(func(opts gocui.ViewMouseBindingOpts) error)
|
||||
|
|
@ -174,6 +177,20 @@ type DiffableContext interface {
|
|||
RefForAdjustingLineNumberInDiff() string
|
||||
}
|
||||
|
||||
// DiffMainViewContext is implemented by the side panel contexts whose focused
|
||||
// main view shows a unified diff — files, local commits, sub-commits, reflog,
|
||||
// stash, and commit files — as opposed to a commit log or other non-diff content
|
||||
// (branches, tags, status, …). It is distinct from DiffableContext, which is
|
||||
// about producing a diff between two refs for the diff menu. This is the signal
|
||||
// for whether to show a selection in the focused main view: a selection is only
|
||||
// meaningful where there are diff lines to act on (stage, edit, jump by hunk,
|
||||
// open in a pull request).
|
||||
type DiffMainViewContext interface {
|
||||
Context
|
||||
|
||||
IsDiffMainViewContext()
|
||||
}
|
||||
|
||||
type IListContext interface {
|
||||
Context
|
||||
|
||||
|
|
@ -314,6 +331,11 @@ type HasKeybindings interface {
|
|||
// Implement this in a side-panel controller to get called when there's a click in the main view
|
||||
// that belongs to your panel while the main view is already focused.
|
||||
GetOnClickFocusedMainView() func(mainViewName string, clickedLineIdx int) error
|
||||
|
||||
// Implement this in a side-panel controller to stage/unstage (or, later, add to
|
||||
// the custom patch) the selected diff line when the user presses space in the
|
||||
// focused main view. Return nil to do nothing.
|
||||
GetOnStageFocusedMainView() func(mainViewName string, viewLineIdx int) error
|
||||
}
|
||||
|
||||
type IController interface {
|
||||
|
|
|
|||
Loading…
Reference in a new issue