Fortran · 18894 bytes Raw Blame History
1 module conflict_parser
2 implicit none
3 private
4
5 public :: conflict_t, parse_conflict_file, conflict_count, get_file_view, get_full_preview, get_side_view
6
7 ! Type to hold a single conflict
8 type :: conflict_t
9 integer :: start_line ! Line number where conflict starts
10 integer :: middle_line ! Line number of ======= separator
11 integer :: end_line ! Line number where conflict ends
12 character(len=:), allocatable :: incoming_branch ! Branch name from <<<<<<< HEAD
13 character(len=:), allocatable :: local_branch ! Branch name from >>>>>>>
14 character(len=:), allocatable, dimension(:) :: context_before ! Lines before conflict (context)
15 character(len=:), allocatable, dimension(:) :: incoming_lines ! Lines from incoming
16 character(len=:), allocatable, dimension(:) :: local_lines ! Lines from local
17 character(len=:), allocatable, dimension(:) :: context_after ! Lines after conflict (context)
18 character(len=:), allocatable, dimension(:) :: full_file ! All lines from the file
19 integer :: total_file_lines ! Total number of lines in file
20 integer :: choice ! 0=none, 1=incoming, 2=local, 3=both
21 end type conflict_t
22
23 ! Number of context lines to show before and after conflict
24 integer, parameter :: CONTEXT_LINES = 5
25
26 contains
27
28 ! Count the number of conflicts in a file
29 function conflict_count(filename) result(count)
30 character(len=*), intent(in) :: filename
31 integer :: count
32 integer :: unit, ios
33 character(len=1024) :: line
34
35 count = 0
36 open(newunit=unit, file=filename, status='old', action='read', iostat=ios)
37 if (ios /= 0) return
38
39 do
40 read(unit, '(A)', iostat=ios) line
41 if (ios /= 0) exit
42
43 if (index(trim(line), '<<<<<<<') == 1) then
44 count = count + 1
45 end if
46 end do
47
48 close(unit)
49 end function conflict_count
50
51 ! Parse a file with conflicts and return array of conflict_t
52 subroutine parse_conflict_file(filename, conflicts, n_conflicts, success)
53 character(len=*), intent(in) :: filename
54 type(conflict_t), allocatable, intent(out) :: conflicts(:)
55 integer, intent(out) :: n_conflicts
56 logical, intent(out) :: success
57
58 integer :: unit, ios, line_num, i
59 character(len=1024) :: line
60 character(len=1024), allocatable :: file_lines(:)
61 integer :: n_lines, capacity
62 logical :: in_conflict
63 integer :: conflict_start, conflict_middle
64 character(len=256) :: branch_name
65
66 success = .false.
67 n_conflicts = 0
68
69 ! First pass: read all lines into memory
70 capacity = 100
71 allocate(file_lines(capacity))
72 n_lines = 0
73
74 open(newunit=unit, file=filename, status='old', action='read', iostat=ios)
75 if (ios /= 0) return
76
77 do
78 read(unit, '(A)', iostat=ios) line
79 if (ios /= 0) exit
80
81 n_lines = n_lines + 1
82 if (n_lines > capacity) then
83 ! Grow array
84 call grow_array(file_lines, capacity)
85 end if
86 file_lines(n_lines) = line
87 end do
88
89 close(unit)
90
91 ! Count conflicts
92 n_conflicts = 0
93 do i = 1, n_lines
94 if (index(trim(file_lines(i)), '<<<<<<<') == 1) then
95 n_conflicts = n_conflicts + 1
96 end if
97 end do
98
99 if (n_conflicts == 0) then
100 success = .true.
101 return
102 end if
103
104 ! Allocate conflicts array
105 allocate(conflicts(n_conflicts))
106
107 ! Second pass: parse conflicts
108 n_conflicts = 0
109 in_conflict = .false.
110
111 do line_num = 1, n_lines
112 line = file_lines(line_num)
113
114 if (index(trim(line), '<<<<<<<') == 1) then
115 ! Start of conflict
116 in_conflict = .true.
117 n_conflicts = n_conflicts + 1
118 conflict_start = line_num
119 conflicts(n_conflicts)%start_line = line_num
120 conflicts(n_conflicts)%choice = 0
121
122 ! Extract branch name (after <<<<<<<)
123 branch_name = adjustl(line(8:))
124 conflicts(n_conflicts)%incoming_branch = trim(branch_name)
125
126 ! Extract context before conflict (up to CONTEXT_LINES)
127 call extract_lines(file_lines, max(1, conflict_start - CONTEXT_LINES), &
128 conflict_start - 1, conflicts(n_conflicts)%context_before)
129
130 else if (index(trim(line), '=======') == 1 .and. in_conflict) then
131 ! Middle of conflict
132 conflict_middle = line_num
133 conflicts(n_conflicts)%middle_line = line_num
134
135 ! Extract incoming lines
136 call extract_lines(file_lines, conflict_start + 1, conflict_middle - 1, &
137 conflicts(n_conflicts)%incoming_lines)
138
139 else if (index(trim(line), '>>>>>>>') == 1 .and. in_conflict) then
140 ! End of conflict
141 conflicts(n_conflicts)%end_line = line_num
142
143 ! Extract branch name
144 branch_name = adjustl(line(8:))
145 conflicts(n_conflicts)%local_branch = trim(branch_name)
146
147 ! Extract local lines
148 call extract_lines(file_lines, conflict_middle + 1, line_num - 1, &
149 conflicts(n_conflicts)%local_lines)
150
151 ! Extract context after conflict (up to CONTEXT_LINES)
152 call extract_lines(file_lines, line_num + 1, &
153 min(n_lines, line_num + CONTEXT_LINES), &
154 conflicts(n_conflicts)%context_after)
155
156 in_conflict = .false.
157 end if
158 end do
159
160 ! Store full file content in each conflict for scrolling
161 do i = 1, n_conflicts
162 conflicts(i)%total_file_lines = n_lines
163 allocate(character(len=1024) :: conflicts(i)%full_file(n_lines))
164 do line_num = 1, n_lines
165 conflicts(i)%full_file(line_num) = file_lines(line_num)
166 end do
167 end do
168
169 success = .true.
170
171 end subroutine parse_conflict_file
172
173 ! Helper: extract lines from array
174 subroutine extract_lines(source, start_idx, end_idx, dest)
175 character(len=*), intent(in) :: source(:)
176 integer, intent(in) :: start_idx, end_idx
177 character(len=:), allocatable, intent(out) :: dest(:)
178 integer :: n, i
179
180 n = end_idx - start_idx + 1
181 if (n <= 0) then
182 allocate(character(len=0) :: dest(0))
183 return
184 end if
185
186 allocate(character(len=1024) :: dest(n))
187 do i = 1, n
188 dest(i) = source(start_idx + i - 1)
189 end do
190 end subroutine extract_lines
191
192 ! Helper: grow an array
193 subroutine grow_array(array, capacity)
194 character(len=*), allocatable, intent(inout) :: array(:)
195 integer, intent(inout) :: capacity
196 character(len=:), allocatable :: temp(:)
197 integer :: old_capacity, i
198
199 old_capacity = capacity
200 capacity = capacity * 2
201
202 allocate(character(len=len(array)) :: temp(capacity))
203 do i = 1, old_capacity
204 temp(i) = array(i)
205 end do
206
207 call move_alloc(temp, array)
208 end subroutine grow_array
209
210 ! Get a view of the file with conflicts resolved
211 ! view_type: 1=incoming, 2=local, 3=preview (uses conflict%choice)
212 subroutine get_file_view(conflict, view_type, file_view, n_lines, conflict_start, conflict_end)
213 type(conflict_t), intent(in) :: conflict
214 integer, intent(in) :: view_type
215 character(len=:), allocatable, dimension(:), intent(out) :: file_view
216 integer, intent(out) :: n_lines
217 integer, intent(out) :: conflict_start, conflict_end
218 integer :: i, j, view_idx, n_conflict_lines
219 character(len=:), allocatable, dimension(:) :: resolution_lines
220
221 ! Start with full file capacity
222 allocate(character(len=1024) :: file_view(conflict%total_file_lines * 2))
223 view_idx = 0
224
225 ! Determine which resolution to use
226 select case (view_type)
227 case (1) ! Incoming
228 resolution_lines = conflict%incoming_lines
229 case (2) ! Local
230 resolution_lines = conflict%local_lines
231 case (3) ! Preview - use chosen resolution
232 select case (conflict%choice)
233 case (1)
234 resolution_lines = conflict%incoming_lines
235 case (2)
236 resolution_lines = conflict%local_lines
237 case (3) ! Both
238 if (allocated(conflict%incoming_lines) .and. allocated(conflict%local_lines)) then
239 n_conflict_lines = size(conflict%incoming_lines) + size(conflict%local_lines)
240 allocate(character(len=1024) :: resolution_lines(n_conflict_lines))
241 j = 1
242 do i = 1, size(conflict%incoming_lines)
243 resolution_lines(j) = conflict%incoming_lines(i)
244 j = j + 1
245 end do
246 do i = 1, size(conflict%local_lines)
247 resolution_lines(j) = conflict%local_lines(i)
248 j = j + 1
249 end do
250 end if
251 end select
252 end select
253
254 ! Build the view: lines before + resolution + lines after
255 conflict_start = 0
256 conflict_end = 0
257
258 ! Copy lines before conflict
259 do i = 1, conflict%start_line - 1
260 view_idx = view_idx + 1
261 file_view(view_idx) = conflict%full_file(i)
262 end do
263
264 ! Insert resolution (mark where conflict region starts)
265 conflict_start = view_idx + 1
266 if (allocated(resolution_lines)) then
267 do i = 1, size(resolution_lines)
268 view_idx = view_idx + 1
269 file_view(view_idx) = resolution_lines(i)
270 end do
271 end if
272 conflict_end = view_idx
273
274 ! Copy lines after conflict
275 do i = conflict%end_line + 1, conflict%total_file_lines
276 view_idx = view_idx + 1
277 file_view(view_idx) = conflict%full_file(i)
278 end do
279
280 n_lines = view_idx
281
282 end subroutine get_file_view
283
284 ! Get a full preview with ALL conflicts resolved
285 subroutine get_full_preview(conflicts, n_conflicts, current_idx, file_view, n_lines, conflict_start, conflict_end)
286 type(conflict_t), intent(in) :: conflicts(:)
287 integer, intent(in) :: n_conflicts, current_idx
288 character(len=:), allocatable, dimension(:), intent(out) :: file_view
289 integer, intent(out) :: n_lines
290 integer, intent(out) :: conflict_start, conflict_end
291 integer :: i, j, view_idx, line_num, n_res_lines
292 character(len=:), allocatable, dimension(:) :: resolution_lines
293 logical :: in_conflict
294 integer :: conflict_idx
295
296 if (n_conflicts == 0 .or. .not. allocated(conflicts(1)%full_file)) return
297
298 ! Allocate output
299 allocate(character(len=1024) :: file_view(conflicts(1)%total_file_lines * 2))
300 view_idx = 0
301 conflict_start = 0
302 conflict_end = 0
303
304 ! Walk through the file line by line
305 line_num = 1
306 do while (line_num <= conflicts(1)%total_file_lines)
307 ! Check if this line starts a conflict
308 in_conflict = .false.
309 do i = 1, n_conflicts
310 if (line_num == conflicts(i)%start_line) then
311 in_conflict = .true.
312 conflict_idx = i
313
314 ! Mark if this is the current conflict
315 if (i == current_idx) then
316 conflict_start = view_idx + 1
317 end if
318
319 ! Get resolution for this conflict
320 select case (conflicts(i)%choice)
321 case (1) ! Incoming
322 resolution_lines = conflicts(i)%incoming_lines
323 case (2) ! Local
324 resolution_lines = conflicts(i)%local_lines
325 case (3) ! Both
326 if (allocated(conflicts(i)%incoming_lines) .and. allocated(conflicts(i)%local_lines)) then
327 n_res_lines = size(conflicts(i)%incoming_lines) + size(conflicts(i)%local_lines)
328 allocate(character(len=1024) :: resolution_lines(n_res_lines))
329 do j = 1, size(conflicts(i)%incoming_lines)
330 resolution_lines(j) = conflicts(i)%incoming_lines(j)
331 end do
332 do j = 1, size(conflicts(i)%local_lines)
333 resolution_lines(size(conflicts(i)%incoming_lines) + j) = conflicts(i)%local_lines(j)
334 end do
335 end if
336 case default ! No choice yet - show incoming
337 resolution_lines = conflicts(i)%incoming_lines
338 end select
339
340 ! Add resolution lines
341 if (allocated(resolution_lines)) then
342 do j = 1, size(resolution_lines)
343 view_idx = view_idx + 1
344 file_view(view_idx) = resolution_lines(j)
345 end do
346 deallocate(resolution_lines)
347 end if
348
349 ! Mark end if this is the current conflict
350 if (i == current_idx) then
351 conflict_end = view_idx
352 end if
353
354 ! Skip to end of conflict
355 line_num = conflicts(i)%end_line + 1
356 exit
357 end if
358 end do
359
360 ! If not in conflict, copy the line
361 if (.not. in_conflict) then
362 view_idx = view_idx + 1
363 file_view(view_idx) = conflicts(1)%full_file(line_num)
364 line_num = line_num + 1
365 end if
366 end do
367
368 n_lines = view_idx
369
370 end subroutine get_full_preview
371
372 ! Get a view showing a specific side (incoming/local) for current conflict, all others resolved
373 subroutine get_side_view(conflicts, n_conflicts, current_idx, side, file_view, n_lines, conflict_start, conflict_end)
374 type(conflict_t), intent(in) :: conflicts(:)
375 integer, intent(in) :: n_conflicts, current_idx
376 integer, intent(in) :: side ! 1=incoming, 2=local
377 character(len=:), allocatable, dimension(:), intent(out) :: file_view
378 integer, intent(out) :: n_lines
379 integer, intent(out) :: conflict_start, conflict_end
380 integer :: i, j, view_idx, line_num, n_res_lines
381 character(len=:), allocatable, dimension(:) :: resolution_lines
382 logical :: in_conflict
383
384 if (n_conflicts == 0 .or. .not. allocated(conflicts(1)%full_file)) return
385
386 ! Allocate output
387 allocate(character(len=1024) :: file_view(conflicts(1)%total_file_lines * 2))
388 view_idx = 0
389 conflict_start = 0
390 conflict_end = 0
391
392 ! Walk through the file line by line
393 line_num = 1
394 do while (line_num <= conflicts(1)%total_file_lines)
395 ! Check if this line starts a conflict
396 in_conflict = .false.
397 do i = 1, n_conflicts
398 if (line_num == conflicts(i)%start_line) then
399 in_conflict = .true.
400
401 ! Mark if this is the current conflict
402 if (i == current_idx) then
403 conflict_start = view_idx + 1
404 end if
405
406 ! Get resolution for this conflict
407 if (i == current_idx) then
408 ! For current conflict, use specified side
409 if (side == 1) then
410 resolution_lines = conflicts(i)%incoming_lines
411 else
412 resolution_lines = conflicts(i)%local_lines
413 end if
414 else
415 ! For other conflicts, use their choice (or incoming if no choice)
416 select case (conflicts(i)%choice)
417 case (1)
418 resolution_lines = conflicts(i)%incoming_lines
419 case (2)
420 resolution_lines = conflicts(i)%local_lines
421 case (3) ! Both
422 if (allocated(conflicts(i)%incoming_lines) .and. allocated(conflicts(i)%local_lines)) then
423 n_res_lines = size(conflicts(i)%incoming_lines) + size(conflicts(i)%local_lines)
424 allocate(character(len=1024) :: resolution_lines(n_res_lines))
425 do j = 1, size(conflicts(i)%incoming_lines)
426 resolution_lines(j) = conflicts(i)%incoming_lines(j)
427 end do
428 do j = 1, size(conflicts(i)%local_lines)
429 resolution_lines(size(conflicts(i)%incoming_lines) + j) = conflicts(i)%local_lines(j)
430 end do
431 end if
432 case default ! No choice yet - show incoming
433 resolution_lines = conflicts(i)%incoming_lines
434 end select
435 end if
436
437 ! Add resolution lines
438 if (allocated(resolution_lines)) then
439 do j = 1, size(resolution_lines)
440 view_idx = view_idx + 1
441 file_view(view_idx) = resolution_lines(j)
442 end do
443 deallocate(resolution_lines)
444 end if
445
446 ! Mark end if this is the current conflict
447 if (i == current_idx) then
448 conflict_end = view_idx
449 end if
450
451 ! Skip to end of conflict
452 line_num = conflicts(i)%end_line + 1
453 exit
454 end if
455 end do
456
457 ! If not in conflict, copy the line
458 if (.not. in_conflict) then
459 view_idx = view_idx + 1
460 file_view(view_idx) = conflicts(1)%full_file(line_num)
461 line_num = line_num + 1
462 end if
463 end do
464
465 n_lines = view_idx
466
467 end subroutine get_side_view
468
469 end module conflict_parser
470