Fortran · 8759 bytes Raw Blame History
1 module undo_stack_module
2 use text_buffer_module
3 use editor_state_module, only: cursor_t
4 implicit none
5 private
6
7 public :: undo_stack_t, init_undo_stack, cleanup_undo_stack
8 public :: push_undo_state, perform_undo, perform_redo, can_undo, can_redo
9 public :: save_initial_undo_state
10
11 integer, parameter :: MAX_UNDO_LEVELS = 100
12
13 type :: undo_state_t
14 character(len=:), allocatable :: buffer_data
15 integer :: gap_start
16 integer :: gap_end
17 integer :: size
18 logical :: modified
19 type(cursor_t) :: cursor_state
20 end type undo_state_t
21
22 type :: undo_stack_t
23 type(undo_state_t), allocatable :: states(:)
24 integer :: current_pos = 0
25 integer :: stack_size = 0
26 end type undo_stack_t
27
28 contains
29
30 subroutine init_undo_stack(stack)
31 type(undo_stack_t), intent(out) :: stack
32
33 ! Allocate from 0 to support initial state at position 0
34 allocate(stack%states(0:MAX_UNDO_LEVELS))
35 stack%current_pos = 0
36 stack%stack_size = 0
37 end subroutine init_undo_stack
38
39 subroutine save_initial_undo_state(stack, buffer, cursor)
40 type(undo_stack_t), intent(inout) :: stack
41 type(buffer_t), intent(in) :: buffer
42 type(cursor_t), intent(in) :: cursor
43
44 ! Don't save initial cursor position - just buffer state
45 ! We'll save cursor from the first edit location
46 if (stack%stack_size == 0) then
47 call save_buffer_state(stack%states(0), buffer, cursor)
48 ! Mark that we don't have a valid cursor state at position 0
49 stack%states(0)%cursor_state%line = -1
50 stack%stack_size = 0 ! Keep at 0, first edit will go to position 1
51 end if
52 end subroutine save_initial_undo_state
53
54 subroutine cleanup_undo_stack(stack)
55 type(undo_stack_t), intent(inout) :: stack
56 integer :: i
57
58 if (allocated(stack%states)) then
59 do i = 0, stack%stack_size
60 if (allocated(stack%states(i)%buffer_data)) then
61 deallocate(stack%states(i)%buffer_data)
62 end if
63 end do
64 deallocate(stack%states)
65 end if
66 end subroutine cleanup_undo_stack
67
68 subroutine push_undo_state(stack, buffer, cursor)
69 type(undo_stack_t), intent(inout) :: stack
70 type(buffer_t), intent(in) :: buffer
71 type(cursor_t), intent(in) :: cursor
72 integer :: i
73
74 ! If we're not at the end of the stack (due to undo), clear redo states
75 if (stack%current_pos < stack%stack_size) then
76 do i = stack%current_pos + 1, stack%stack_size
77 if (allocated(stack%states(i)%buffer_data)) then
78 deallocate(stack%states(i)%buffer_data)
79 end if
80 end do
81 stack%stack_size = stack%current_pos
82 end if
83
84 ! Move position forward
85 if (stack%stack_size < MAX_UNDO_LEVELS) then
86 stack%current_pos = stack%current_pos + 1
87 stack%stack_size = stack%current_pos
88 else
89 ! Shift everything down and add at the end
90 do i = 1, MAX_UNDO_LEVELS - 1
91 if (allocated(stack%states(i)%buffer_data)) then
92 deallocate(stack%states(i)%buffer_data)
93 end if
94 stack%states(i) = stack%states(i + 1)
95 end do
96 stack%current_pos = MAX_UNDO_LEVELS
97 stack%stack_size = MAX_UNDO_LEVELS
98 end if
99
100 ! Store the current state
101 call save_buffer_state(stack%states(stack%current_pos), buffer, cursor)
102 end subroutine push_undo_state
103
104 subroutine save_buffer_state(state, buffer, cursor)
105 type(undo_state_t), intent(out) :: state
106 type(buffer_t), intent(in) :: buffer
107 type(cursor_t), intent(in) :: cursor
108
109 ! Save buffer data
110 if (allocated(state%buffer_data)) deallocate(state%buffer_data)
111 allocate(character(len=len(buffer%data)) :: state%buffer_data)
112 state%buffer_data = buffer%data
113
114 ! Save buffer metadata
115 state%gap_start = buffer%gap_start
116 state%gap_end = buffer%gap_end
117 state%size = buffer%size
118 state%modified = buffer%modified
119
120 ! Save cursor state
121 state%cursor_state = cursor
122 end subroutine save_buffer_state
123
124 subroutine restore_buffer_state(buffer, cursor, state)
125 type(buffer_t), intent(inout) :: buffer
126 type(cursor_t), intent(inout) :: cursor
127 type(undo_state_t), intent(in) :: state
128
129 ! Restore buffer data
130 if (allocated(buffer%data)) deallocate(buffer%data)
131 allocate(character(len=len(state%buffer_data)) :: buffer%data)
132 buffer%data = state%buffer_data
133
134 ! Restore buffer metadata
135 buffer%gap_start = state%gap_start
136 buffer%gap_end = state%gap_end
137 buffer%size = state%size
138 buffer%modified = state%modified
139
140 ! Validate buffer integrity - size must match actual data length
141 if (buffer%size /= len(buffer%data)) then
142 buffer%size = len(buffer%data)
143 end if
144
145 ! Validate gap buffer integrity
146 if (buffer%gap_start < 1) buffer%gap_start = 1
147 if (buffer%gap_end > buffer%size + 1) buffer%gap_end = buffer%size + 1
148 if (buffer%gap_start > buffer%gap_end) then
149 ! Gap is invalid - reset to empty gap at end
150 buffer%gap_start = buffer%size + 1
151 buffer%gap_end = buffer%size + 1
152 end if
153
154 ! Restore selection state properly
155 if (state%cursor_state%has_selection) then
156 ! If the saved state had a selection, restore it completely
157 cursor%has_selection = .true.
158 cursor%selection_start_line = state%cursor_state%selection_start_line
159 cursor%selection_start_col = state%cursor_state%selection_start_col
160 ! Also restore cursor position for the other end of selection
161 cursor%line = state%cursor_state%line
162 cursor%column = state%cursor_state%column
163 cursor%desired_column = cursor%column
164 else
165 ! No selection in saved state - keep cursor where it is
166 cursor%has_selection = .false.
167 end if
168 end subroutine restore_buffer_state
169
170 function can_undo(stack) result(can)
171 type(undo_stack_t), intent(in) :: stack
172 logical :: can
173
174 can = (stack%current_pos >= 1)
175 end function can_undo
176
177 function can_redo(stack) result(can)
178 type(undo_stack_t), intent(in) :: stack
179 logical :: can
180
181 can = (stack%current_pos < stack%stack_size)
182 end function can_redo
183
184 subroutine perform_undo(stack, buffer, cursor)
185 type(undo_stack_t), intent(inout) :: stack
186 type(buffer_t), intent(inout) :: buffer
187 type(cursor_t), intent(inout) :: cursor
188 type(undo_state_t) :: temp_state
189
190 if (can_undo(stack)) then
191 ! If we're at the end of the stack (haven't undone yet),
192 ! save current state for redo
193 if (stack%current_pos == stack%stack_size .and. &
194 stack%current_pos < MAX_UNDO_LEVELS) then
195 ! Save current state at next position for redo
196 call save_buffer_state(temp_state, buffer, cursor)
197 stack%stack_size = stack%current_pos + 1
198 stack%states(stack%stack_size) = temp_state
199 end if
200
201 ! Restore the state at the CURRENT position (the saved state before the edit)
202 call restore_buffer_state(buffer, cursor, stack%states(stack%current_pos))
203
204 ! Move position back AFTER restoring
205 stack%current_pos = stack%current_pos - 1
206 end if
207 end subroutine perform_undo
208
209 subroutine perform_redo(stack, buffer, cursor)
210 type(undo_stack_t), intent(inout) :: stack
211 type(buffer_t), intent(inout) :: buffer
212 type(cursor_t), intent(inout) :: cursor
213
214 if (can_redo(stack)) then
215 ! Move forward to next state
216 ! Special case: if at position 0 and stack_size > 1, we should jump to the
217 ! last saved state (which was saved during undo), not position 1
218 if (stack%current_pos == 0 .and. stack%stack_size > 1) then
219 ! Jump to the state saved during undo (skip intermediate states)
220 stack%current_pos = stack%stack_size
221 else
222 ! Normal forward movement
223 stack%current_pos = stack%current_pos + 1
224 end if
225
226 ! Restore that state
227 call restore_buffer_state(buffer, cursor, stack%states(stack%current_pos))
228 end if
229 end subroutine perform_redo
230
231 end module undo_stack_module