1package mergeconflicts 2 3import ( 4 "sync" 5 6 "github.com/golang-collections/collections/stack" 7 "github.com/jesseduffield/lazygit/pkg/utils" 8) 9 10type State struct { 11 sync.Mutex 12 13 conflicts []*mergeConflict 14 // this is the index of the above `conflicts` field which is currently selected 15 conflictIndex int 16 17 // this is the index of the selected conflict's available selections slice e.g. [TOP, MIDDLE, BOTTOM] 18 // We use this to know which hunk of the conflict is selected. 19 selectionIndex int 20 21 // this allows us to undo actions 22 EditHistory *stack.Stack 23} 24 25func NewState() *State { 26 return &State{ 27 Mutex: sync.Mutex{}, 28 conflictIndex: 0, 29 selectionIndex: 0, 30 conflicts: []*mergeConflict{}, 31 EditHistory: stack.New(), 32 } 33} 34 35func (s *State) setConflictIndex(index int) { 36 if len(s.conflicts) == 0 { 37 s.conflictIndex = 0 38 } else { 39 s.conflictIndex = clamp(index, 0, len(s.conflicts)-1) 40 } 41 s.setSelectionIndex(s.selectionIndex) 42} 43 44func (s *State) setSelectionIndex(index int) { 45 if selections := s.availableSelections(); len(selections) != 0 { 46 s.selectionIndex = clamp(index, 0, len(selections)-1) 47 } 48} 49 50func (s *State) SelectNextConflictHunk() { 51 s.setSelectionIndex(s.selectionIndex + 1) 52} 53 54func (s *State) SelectPrevConflictHunk() { 55 s.setSelectionIndex(s.selectionIndex - 1) 56} 57 58func (s *State) SelectNextConflict() { 59 s.setConflictIndex(s.conflictIndex + 1) 60} 61 62func (s *State) SelectPrevConflict() { 63 s.setConflictIndex(s.conflictIndex - 1) 64} 65 66func (s *State) PushFileSnapshot(content string) { 67 s.EditHistory.Push(content) 68} 69 70func (s *State) PopFileSnapshot() (string, bool) { 71 if s.EditHistory.Len() == 0 { 72 return "", false 73 } 74 75 return s.EditHistory.Pop().(string), true 76} 77 78func (s *State) currentConflict() *mergeConflict { 79 if len(s.conflicts) == 0 { 80 return nil 81 } 82 83 return s.conflicts[s.conflictIndex] 84} 85 86func (s *State) SetConflictsFromCat(cat string) { 87 s.setConflicts(findConflicts(cat)) 88} 89 90func (s *State) setConflicts(conflicts []*mergeConflict) { 91 s.conflicts = conflicts 92 s.setConflictIndex(s.conflictIndex) 93} 94 95func (s *State) NoConflicts() bool { 96 return len(s.conflicts) == 0 97} 98 99func (s *State) Selection() Selection { 100 if selections := s.availableSelections(); len(selections) > 0 { 101 return selections[s.selectionIndex] 102 } 103 return TOP 104} 105 106func (s *State) availableSelections() []Selection { 107 if conflict := s.currentConflict(); conflict != nil { 108 return availableSelections(conflict) 109 } 110 return nil 111} 112 113func (s *State) IsFinalConflict() bool { 114 return len(s.conflicts) == 1 115} 116 117func (s *State) Reset() { 118 s.EditHistory = stack.New() 119} 120 121func (s *State) GetConflictMiddle() int { 122 return s.currentConflict().target 123} 124 125func (s *State) ContentAfterConflictResolve( 126 path string, 127 selection Selection, 128) (bool, string, error) { 129 conflict := s.currentConflict() 130 if conflict == nil { 131 return false, "", nil 132 } 133 134 content := "" 135 err := utils.ForEachLineInFile(path, func(line string, i int) { 136 if selection.isIndexToKeep(conflict, i) { 137 content += line 138 } 139 }) 140 141 if err != nil { 142 return false, "", err 143 } 144 145 return true, content, nil 146} 147 148func clamp(x int, min int, max int) int { 149 if x < min { 150 return min 151 } else if x > max { 152 return max 153 } 154 return x 155} 156