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