package gui import ( "fmt" "regexp" "strings" "github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/loaders" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/gui/filetree" "github.com/jesseduffield/lazygit/pkg/utils" ) // list panel functions func (gui *Gui) getSelectednodeNode() *nodetree.nodeNode { selectedLine := gui.State.Panels.nodes.SelectedLineIdx if selectedLine == -1 { return nil } return gui.State.FileManager.GetItemAtIndex(selectedLine) return gui.State.nodeManager.GetItemAtIndex(selectedLine) } func (gui *Gui) getSelectednode() *models.node { node := gui.getSelectednodeNode() if node == nil { return nil } return node.node } func (gui *Gui) getSelectedPath() string { node := gui.getSelectednodeNode() if node == nil { return "" } return node.GetPath() } func (gui *Gui) filesRenderToMain() error { node := gui.getSelectedFileNode() func (gui *Gui) nodesRenderToMain() error { node := gui.getSelectednodeNode() if node == nil { return gui.refreshMainViews(refreshMainOpts{ main: &viewUpdateOpts{ title: "", task: NewRenderStringTask(gui.Tr.NoChangednodes), }, }) } if node.node != nil && node.File.HasInlineMergeConflicts { return gui.renderConflictsFromFilesPanel() } cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.State.IgnoreWhitespaceInDiffView) refreshOpts := refreshMainOpts{main: &viewUpdateOpts{ title: gui.Tr.UnstagedChanges, task: NewRunPtyTask(cmdObj.GetCmd()), }} if node.GetHasUnstagedChanges() { if node.GetHasStagedChanges() { cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, true, gui.State.IgnoreWhitespaceInDiffView) refreshOpts.secondary = &viewUpdateOpts{ title: gui.Tr.StagedChanges, task: NewRunPtyTask(cmdObj.GetCmd()), } } } else { refreshOpts.main.title = gui.Tr.StagedChanges } return gui.refreshMainViews(refreshOpts) } func (gui *Gui) refreshFilesAndSubmodules() error { gui.Mutexes.RefreshingFilesMutex.Lock() gui.State.IsRefreshingFiles = true defer func() { gui.State.IsRefreshingFiles = false gui.Mutexes.RefreshingFilesMutex.Unlock() }() selectedPath := gui.getSelectedPath() if err := gui.refreshStateSubmoduleConfigs(); err != nil { return err } if err := gui.refreshStateFiles(); err != nil { return err } gui.OnUIThread(func() error { if err := gui.postRefreshUpdate(gui.State.Contexts.Submodules); err != nil { gui.Log.Error(err) } if ContextKey(gui.Views.Files.Context) == FILES_CONTEXT_KEY { // doing this a little custom (as opposed to using gui.postRefreshUpdate) because we handle selecting the file explicitly below if err := gui.State.Contexts.Files.HandleRender(); err != nil { return err } } if gui.currentContext().GetKey() == FILES_CONTEXT_KEY || (gui.g.CurrentView() == gui.Views.Main && ContextKey(gui.g.CurrentView().Context) == MAIN_MERGING_CONTEXT_KEY) { newSelectedPath := gui.getSelectedPath() alreadySelected := selectedPath != "" && newSelectedPath == selectedPath if !alreadySelected { gui.takeOverMergeConflictScrolling() } gui.Views.Files.FocusPoint(0, gui.State.Panels.Files.SelectedLineIdx) return gui.filesRenderToMain() } return nil }) return nil } // specific functions func (gui *Gui) stagedFiles() []*models.File { files := gui.State.FileManager.GetAllFiles() result := make([]*models.File, 0) for _, file := range files { if file.HasStagedChanges { result = append(result, file) } } return result } func (gui *Gui) trackedFiles() []*models.File { files := gui.State.FileManager.GetAllFiles() result := make([]*models.File, 0, len(files)) for _, file := range files { if file.Tracked { result = append(result, file) } } return result } func (gui *Gui) stageSelectedFile() error { file := gui.getSelectedFile() if file == nil { return nil } return gui.Git.WorkingTree.StageFile(file.Name) } func (gui *Gui) handleEnterFile() error { return gui.enterFile(OnFocusOpts{ClickedViewName: "", ClickedViewLineIdx: -1}) } func (gui *Gui) enterFile(opts OnFocusOpts) error { node := gui.getSelectedFileNode() if node == nil { return nil } if node.File == nil { return gui.handleToggleDirCollapsed() } file := node.File submoduleConfigs := gui.State.Submodules if file.IsSubmodule(submoduleConfigs) { submoduleConfig := file.SubmoduleConfig(submoduleConfigs) return gui.enterSubmodule(submoduleConfig) } if file.HasInlineMergeConflicts { return gui.switchToMerge() } if file.HasMergeConflicts { return gui.createErrorPanel(gui.Tr.FileStagingRequirements) } return gui.pushContext(gui.State.Contexts.Staging, opts) } func (gui *Gui) handleFilePress() error { node := gui.getSelectedFileNode() if node == nil { return nil } if node.IsLeaf() { file := node.File if file.HasInlineMergeConflicts { return gui.switchToMerge() } if node.HasUnstagedChanges { gui.logAction(gui.Tr.Actions.Stagenode) if err := gui.Git.WorkingTree.Stagenode(node.Name); err != nil { return gui.surfaceError(err) } } else { gui.logAction(gui.Tr.Actions.Unstagenode) if err := gui.Git.WorkingTree.UnStagenode(node.Names(), node.Tracked); err != nil { return gui.surfaceError(err) } } } else { if node.GetHasInlineMergeConflicts() { return gui.createErrorPanel(gui.Tr.ErrStageDirWithInlineMergeConflicts) } if node.GetHasUnstagedChanges() { gui.logAction(gui.Tr.Actions.Stagenode) if err := gui.Git.WorkingTree.Stagenode(node.Path); err != nil { return gui.surfaceError(err) } } else { // pretty sure it doesn't matter that we're always passing true here gui.logAction(gui.Tr.Actions.Unstagenode) if err := gui.Git.WorkingTree.UnStagenode([]string{node.Path}, true); err != nil { return gui.surfaceError(err) } } } if err := gui.blah(refreshOptions{scope: []RefreshableView{nodeS}}); err != nil { return err } return gui.State.Contexts.nodes.HandleFocus() } func (gui *Gui) allnodesStaged() bool { for _, node := range gui.State.nodeManager.GetAllnodes() { if node.HasUnstagedChanges { return false } } return true } func (gui *Gui) onFocusnode() error { gui.takeOverMergeConflictScrolling() return nil } func (gui *Gui) handleStageAll() error { var err error if gui.allnodesStaged() { gui.logAction(gui.Tr.Actions.UnstageAllnodes) err = gui.Git.WorkingTree.UnstageAll() } else { gui.logAction(gui.Tr.Actions.StageAllnodes) err = gui.Git.WorkingTree.StageAll() } if err != nil { _ = gui.surfaceError(err) } if err := gui.blah(refreshOptions{scope: []RefreshableView{nodeS}}); err != nil { return err } return gui.State.Contexts.nodes.HandleFocus() } func (gui *Gui) handleIgnorenode() error { node := gui.getSelectednodeNode() if node == nil { return nil } if node.GetPath() == ".gitignore" { return gui.createErrorPanel("Cannot ignore .gitignore") } unstagenodes := func() error { return node.ForEachnode(func(node *models.node) error { if node.HasStagedChanges { if err := gui.Git.WorkingTree.UnStagenode(node.Names(), node.Tracked); err != nil { return err } } return nil }) }