/
githubmirror
/
lazygit
Обзор
Документация
Войти
/
githubmirror
/
lazygit
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
pkg/gui/controllers/patch_explorer_controller.go
416 строк
11 KB
Stefan Haller
Auto-scroll when dragging to create range selection in staging view
31 июл 2026, 09:26
31 июл 2026, 09:26
b682fb7
Код
Авторство
О чём код?
package controllers import ( "strings" "github.com/jesseduffield/lazygit/pkg/gocui" "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/samber/lo" ) type PatchExplorerControllerFactory struct { c *ControllerCommon } func NewPatchExplorerControllerFactory(c *ControllerCommon) *PatchExplorerControllerFactory { return &PatchExplorerControllerFactory{ c: c, } } func (self *PatchExplorerControllerFactory) Create(context types.IPatchExplorerContext) *PatchExplorerController { controller := &PatchExplorerController{ baseController: baseController{}, c: self.c, context: context, } controller.dragAutoscroller = helpers.NewDragAutoscroller( self.c.HelperCommon, context, controller.canDragAutoscroll, controller.handleDragAutoscroll, ) return controller } type PatchExplorerController struct { baseController c *ControllerCommon context types.IPatchExplorerContext dragAutoscroller *helpers.DragAutoscroller draggingWithMouse bool } func (self *PatchExplorerController) Context() types.Context { return self.context } func (self *PatchExplorerController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { return []*types.Binding{ { Tag: "navigation", Keys: opts.GetKeys(opts.Config.Universal.PrevItem), Handler: self.withRenderAndFocus(self.HandlePrevLine), }, { Tag: "navigation", Keys: opts.GetKeys(opts.Config.Universal.NextItem), Handler: self.withRenderAndFocus(self.HandleNextLine), }, { Tag: "navigation", Keys: opts.GetKeys(opts.Config.Universal.RangeSelectUp), Handler: self.withRenderAndFocus(self.HandlePrevLineRange), Description: self.c.Tr.RangeSelectUp, }, { Tag: "navigation", Keys: opts.GetKeys(opts.Config.Universal.RangeSelectDown), Handler: self.withRenderAndFocus(self.HandleNextLineRange), Description: self.c.Tr.RangeSelectDown, }, { Keys: opts.GetKeys(opts.Config.Main.PrevHunk), Handler: self.withRenderAndFocus(self.HandlePrevHunk), Description: self.c.Tr.PrevHunk, }, { Keys: opts.GetKeys(opts.Config.Main.NextHunk), Handler: self.withRenderAndFocus(self.HandleNextHunk), Description: self.c.Tr.NextHunk, }, { Keys: opts.GetKeys(opts.Config.Universal.ToggleRangeSelect), Handler: self.withRenderAndFocus(self.HandleToggleSelectRange), Description: self.c.Tr.ToggleRangeSelect, }, { Keys: opts.GetKeys(opts.Config.Main.ToggleSelectHunk), Handler: self.withRenderAndFocus(self.HandleToggleSelectHunk), Description: self.c.Tr.ToggleSelectHunk, DescriptionFunc: func() string { if state := self.context.GetState(); state != nil && state.SelectingHunk() { return self.c.Tr.SelectLineByLine } return self.c.Tr.SelectHunk }, Tooltip: self.c.Tr.ToggleSelectHunkTooltip, DisplayOnScreen: true, }, { Tag: "navigation", Keys: opts.GetKeys(opts.Config.Universal.PrevPage), Handler: self.withRenderAndFocus(self.HandlePrevPage), Description: self.c.Tr.PrevPage, }, { Tag: "navigation", Keys: opts.GetKeys(opts.Config.Universal.NextPage), Handler: self.withRenderAndFocus(self.HandleNextPage), Description: self.c.Tr.NextPage, }, { Tag: "navigation", Keys: opts.GetKeys(opts.Config.Universal.GotoTop), Handler: self.withRenderAndFocus(self.HandleGotoTop), Description: self.c.Tr.GotoTop, }, { Tag: "navigation", Keys: opts.GetKeys(opts.Config.Universal.GotoBottom), Description: self.c.Tr.GotoBottom, Handler: self.withRenderAndFocus(self.HandleGotoBottom), }, { Tag: "navigation", Keys: opts.GetKeys(opts.Config.Universal.ScrollLeft), Handler: self.withRenderAndFocus(self.HandleScrollLeft), }, { Tag: "navigation", Keys: opts.GetKeys(opts.Config.Universal.ScrollRight), Handler: self.withRenderAndFocus(self.HandleScrollRight), }, { Keys: opts.GetKeys(opts.Config.Universal.CopyToClipboard), Handler: self.withLock(self.CopySelectedToClipboard), Description: self.c.Tr.CopySelectedTextToClipboard, }, } } func (self *PatchExplorerController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { return []*gocui.ViewMouseBinding{ { ViewName: self.context.GetViewName(), Key: gocui.MouseLeft, Handler: func(opts gocui.ViewMouseBindingOpts) error { if self.isFocused() { return self.withRenderAndFocus(self.HandleMouseDown)() } self.c.Context().Push(self.context, types.OnFocusOpts{ ClickedWindowName: self.context.GetWindowName(), ClickedViewLineIdx: opts.Y, }) return nil }, }, { ViewName: self.context.GetViewName(), Key: gocui.MouseLeft, Modifier: gocui.ModMotion, Handler: self.handleMouseDrag, }, { ViewName: self.context.GetViewName(), Key: gocui.MouseRelease, Handler: func(gocui.ViewMouseBindingOpts) error { return self.handleDragRelease() }, }, } } func (self *PatchExplorerController) handleMouseDrag(opts gocui.ViewMouseBindingOpts) error { if err := self.withLock(func() error { self.context.GetState().DragSelectLine(opts.Y) self.renderDragSelection() return nil })(); err != nil { return err } self.draggingWithMouse = true originY, _ := self.context.GetViewTrait().ViewPortYBounds() self.dragAutoscroller.Update(opts.Y - originY) return nil } func (self *PatchExplorerController) canDragAutoscroll(int) bool { state := self.context.GetState() return state != nil && state.SelectingRange() } func (self *PatchExplorerController) handleDragAutoscroll(viewIndex int) bool { if !self.canDragAutoscroll(0) { return false } if err := self.withLock(func() error { self.context.GetState().DragSelectLine(viewIndex) self.renderDragSelection() return nil })(); err != nil { return false } return true } func (self *PatchExplorerController) renderDragSelection() { view := self.context.GetView() state := self.context.GetState() originY := view.OriginY() startIndex, _ := state.SelectedViewRange() view.SetRangeSelectStart(startIndex) view.SetCursorY(state.GetSelectedViewLineIdx() - originY) self.context.Render() } func (self *PatchExplorerController) handleDragRelease() error { self.draggingWithMouse = false self.dragAutoscroller.Cancel() return nil } func (self *PatchExplorerController) GetOnFocusLost() func(types.OnFocusLostOpts) { return func(types.OnFocusLostOpts) { self.dragAutoscroller.Cancel() if self.draggingWithMouse { self.draggingWithMouse = false self.c.GocuiGui().CancelMouseCapture() } } } func (self *PatchExplorerController) HandlePrevLine() error { before := self.context.GetState().GetSelectedViewLineIdx() self.context.GetState().CycleSelection(false) after := self.context.GetState().GetSelectedViewLineIdx() if self.context.GetState().SelectingLine() { checkScrollUp(self.context.GetViewTrait(), self.c.UserConfig(), before, after) } return nil } func (self *PatchExplorerController) HandleNextLine() error { before := self.context.GetState().GetSelectedViewLineIdx() self.context.GetState().CycleSelection(true) after := self.context.GetState().GetSelectedViewLineIdx() if self.context.GetState().SelectingLine() { checkScrollDown(self.context.GetViewTrait(), self.c.UserConfig(), before, after) } return nil } func (self *PatchExplorerController) HandlePrevLineRange() error { s := self.context.GetState() s.CycleRange(false) return nil } func (self *PatchExplorerController) HandleNextLineRange() error { s := self.context.GetState() s.CycleRange(true) return nil } func (self *PatchExplorerController) HandlePrevHunk() error { self.context.GetState().SelectPreviousHunk() return nil } func (self *PatchExplorerController) HandleNextHunk() error { self.context.GetState().SelectNextHunk() return nil } func (self *PatchExplorerController) HandleToggleSelectRange() error { self.context.GetState().ToggleStickySelectRange() return nil } func (self *PatchExplorerController) HandleToggleSelectHunk() error { self.context.GetState().ToggleSelectHunk() return nil } func (self *PatchExplorerController) HandleScrollLeft() error { self.context.GetViewTrait().ScrollLeft() return nil } func (self *PatchExplorerController) HandleScrollRight() error { self.context.GetViewTrait().ScrollRight() return nil } func (self *PatchExplorerController) HandlePrevPage() error { self.context.GetState().AdjustSelectedLineIdx(-self.context.GetViewTrait().PageDelta()) return nil } func (self *PatchExplorerController) HandleNextPage() error { self.context.GetState().AdjustSelectedLineIdx(self.context.GetViewTrait().PageDelta()) return nil } func (self *PatchExplorerController) HandleGotoTop() error { self.context.GetState().SelectTop() return nil } func (self *PatchExplorerController) HandleGotoBottom() error { self.context.GetState().SelectBottom() return nil } func (self *PatchExplorerController) HandleMouseDown() error { self.context.GetState().SelectNewLineForRange(self.context.GetViewTrait().SelectedLineIdx()) return nil } func (self *PatchExplorerController) CopySelectedToClipboard() error { selected := self.context.GetState().PlainRenderSelected() self.c.LogAction(self.c.Tr.Actions.CopySelectedTextToClipboard) if err := self.c.OS().CopyToClipboard(dropDiffPrefix(selected)); err != nil { return err } return nil } // Removes '+' or '-' from the beginning of each line in the diff string, except // when both '+' and '-' lines are present, or diff header lines, in which case // the diff is returned unchanged. This is useful for copying parts of diffs to // the clipboard in order to paste them into code. func dropDiffPrefix(diff string) string { lines := strings.Split(strings.TrimRight(diff, "\n"), "\n") const ( PLUS int = iota MINUS CONTEXT OTHER ) linesByType := lo.GroupBy(lines, func(line string) int { switch { case strings.HasPrefix(line, "+"): return PLUS case strings.HasPrefix(line, "-"): return MINUS case strings.HasPrefix(line, " "): return CONTEXT } return OTHER }) hasLinesOfType := func(lineType int) bool { return len(linesByType[lineType]) > 0 } keepPrefix := hasLinesOfType(OTHER) || (hasLinesOfType(PLUS) && hasLinesOfType(MINUS)) if keepPrefix { return diff } return strings.Join(lo.Map(lines, func(line string, _ int) string { return line[1:] + "\n" }), "") } func (self *PatchExplorerController) isFocused() bool { return self.c.Context().Current().GetKey() == self.context.GetKey() } func (self *PatchExplorerController) withRenderAndFocus(f func() error) func() error { return self.withLock(func() error { if err := f(); err != nil { return err } self.context.RenderAndFocus() return nil }) } func (self *PatchExplorerController) withLock(f func() error) func() error { return func() error { self.context.GetMutex().Lock() defer self.context.GetMutex().Unlock() if self.context.GetState() == nil { return nil } return f() } }