1use super::transact::ChangeSet;
4use super::{BlockRect, Editor, Mode, Register};
5use strop_core::id::DisplayColumn;
6use strop_core::layout::RopeGraphemes;
7use strop_core::{Range, Replacement};
8
9pub(crate) struct BlockInsertState {
10 rows: Vec<usize>,
11 column: DisplayColumn,
12 pad: bool,
13}
14struct RowPart {
15 range: Option<Range>,
16 selected: String,
17 remaining: String,
18}
19
20impl Editor {
21 pub fn enter_block_pub(&mut self) {
22 if self.buf().readonly {
23 self.message = "readonly buffer".into();
24 return;
25 }
26 let head = self.head();
27 self.sels_mut().stretch_primary(head, head);
28 self.view_mut().desired_column = None;
29 self.mode = Mode::VisualBlock;
30 }
31 pub fn block_rect_pub(&self) -> Option<BlockRect> {
32 self.block_rect()
33 }
34
35 fn cluster_cells(&self, byte: usize) -> (DisplayColumn, DisplayColumn) {
36 let line = self.buf().line_of(byte);
37 let start = self.buf().line_start(line);
38 let slice = self
39 .buf()
40 .text()
41 .byte_slice(start..self.buf().line_end(line));
42 let relative = byte.saturating_sub(start);
43 let mut end = DisplayColumn::new(0);
44 for (span, cluster) in RopeGraphemes::new(slice, self.cur_indent().width) {
45 end = span.cell + span.width;
46 if relative < span.byte + cluster.len() {
47 return (span.cell, span.cell + span.width.max(1));
48 }
49 }
50 (end, end + 1)
51 }
52
53 pub(crate) fn block_rect(&self) -> Option<BlockRect> {
54 if self.mode != Mode::VisualBlock {
55 return None;
56 }
57 let (anchor, head) = (self.anchor(), self.head());
58 let (a, a_end) = self.cluster_cells(anchor);
59 let (h, h_end) = self.cluster_cells(head);
60 Some(BlockRect {
61 first_line: self.buf().line_of(anchor).min(self.buf().line_of(head)),
62 last_line: self.buf().line_of(anchor).max(self.buf().line_of(head)),
63 left_cell: a.min(h),
64 right_cell: a_end.max(h_end) - 1,
65 })
66 }
67
68 pub(crate) fn block_vertical(&mut self, command: &strop_grammar::Command) -> bool {
71 if self.mode != Mode::VisualBlock {
72 return false;
73 }
74 let down = match command.target {
75 strop_grammar::Target::Motion(strop_grammar::Motion::Down) => true,
76 strop_grammar::Target::Motion(strop_grammar::Motion::Up) => false,
77 _ => return false,
78 };
79 let desired = self
80 .view()
81 .desired_column
82 .unwrap_or_else(|| self.cluster_cells(self.head()).1 - 1);
83 let line = self.buf().line_of(self.head());
84 let count = command.count.unwrap_or(1).max(1);
85 let target = if down {
86 line.saturating_add(count)
87 .min(self.buf().last_content_line())
88 } else {
89 line.saturating_sub(count)
90 };
91 self.land_at_cell(target, desired);
92 self.view_mut().desired_column = Some(desired);
93 true
94 }
95
96 fn row_part(&self, line: usize, left: DisplayColumn, right: DisplayColumn) -> RowPart {
97 let start = self.buf().line_start(line);
98 let slice = self
99 .buf()
100 .text()
101 .byte_slice(start..self.buf().line_end(line));
102 let mut first = None;
103 let mut last = start;
104 let mut selected = String::new();
105 let mut remaining = String::new();
106 for (span, text) in RopeGraphemes::new(slice, self.cur_indent().width) {
107 let end = span.cell + span.width;
108 if end <= left || span.cell >= right || span.width == 0 {
109 continue;
110 }
111 first.get_or_insert(start + span.byte);
112 last = start + span.byte + text.len();
113 let lo = span.cell.max(left);
114 let hi = end.min(right);
115 if lo == span.cell && hi == end {
116 selected.push_str(&text);
117 } else {
118 selected.push_str(&" ".repeat(hi - lo));
119 remaining.push_str(&" ".repeat(lo - span.cell));
120 remaining.push_str(&" ".repeat(end - hi));
121 }
122 }
123 if first.is_none() {
124 selected = " ".repeat(right - left);
125 }
126 RowPart {
127 range: first.map(|first| Range::charwise(first, last)),
128 selected,
129 remaining,
130 }
131 }
132
133 fn land_at_cell(&mut self, line: usize, column: DisplayColumn) {
134 let start = self.buf().line_start(line);
135 let end = self.buf().line_end(line);
136 let byte = RopeGraphemes::new(
137 self.buf().text().byte_slice(start..end),
138 self.cur_indent().width,
139 )
140 .find_map(|(span, _)| (span.cell + span.width > column).then_some(start + span.byte))
141 .unwrap_or(end);
142 self.set_head(byte);
143 self.clamp_cursor();
144 }
145
146 pub(crate) fn block_yank(&mut self) {
147 let Some(rect) = self.block_rect() else {
148 return;
149 };
150 let rows: Vec<_> = (rect.first_line..=rect.last_line)
151 .map(|line| {
152 self.row_part(line, rect.left_cell, rect.right_cell + 1)
153 .selected
154 })
155 .collect();
156 self.set_register(
157 None,
158 Register::blockwise(
159 rows.join("\n"),
160 DisplayColumn::new(rect.right_cell - rect.left_cell + 1),
161 ),
162 );
163 self.mode = Mode::Normal;
164 self.land_at_cell(rect.first_line, rect.left_cell);
165 }
166 pub(crate) fn block_delete(&mut self) {
167 self.block_edit(false);
168 }
169 pub(crate) fn block_change(&mut self) {
170 self.block_edit(true);
171 }
172 fn block_edit(&mut self, insert: bool) {
173 let Some(rect) = self.block_rect() else {
174 return;
175 };
176 let mut rows = Vec::new();
177 let mut selected = Vec::new();
178 let mut edits = Vec::new();
179 for line in rect.first_line..=rect.last_line {
180 let part = self.row_part(line, rect.left_cell, rect.right_cell + 1);
181 selected.push(part.selected);
182 if let Some(range) = part.range {
183 if line != rect.first_line {
184 rows.push(line);
185 }
186 edits.push(Replacement::new(range, part.remaining));
187 }
188 }
189 if let Err(error) = self.apply(
190 self.current(),
191 self.buf().revision(),
192 ChangeSet {
193 edits,
194 undo_open: insert,
195 },
196 ) {
197 self.message = error.to_string();
198 return;
199 }
200 self.set_register(
201 None,
202 Register::blockwise(
203 selected.join("\n"),
204 DisplayColumn::new(rect.right_cell - rect.left_cell + 1),
205 ),
206 );
207 self.mode = if insert { Mode::Insert } else { Mode::Normal };
208 self.land_at_cell(rect.first_line, rect.left_cell);
209 if insert {
210 self.block_insert_state = Some(BlockInsertState {
211 rows,
212 column: rect.left_cell,
213 pad: false,
214 });
215 self.enter_insert_from("<c-v>c");
216 }
217 }
218
219 fn insertion(
222 &self,
223 line: usize,
224 column: DisplayColumn,
225 text: &str,
226 pad: bool,
227 ) -> Option<Replacement> {
228 let start = self.buf().line_start(line);
229 let end = self.buf().line_end(line);
230 let mut width = DisplayColumn::new(0);
231 for (span, cluster) in RopeGraphemes::new(
232 self.buf().text().byte_slice(start..end),
233 self.cur_indent().width,
234 ) {
235 if span.cell == column {
236 return Some(Replacement::new(
237 Range::charwise(start + span.byte, start + span.byte),
238 text,
239 ));
240 }
241 width = span.cell + span.width;
242 if span.cell < column && column < width {
243 return Some(Replacement::new(
244 Range::charwise(start + span.byte, start + span.byte + cluster.len()),
245 format!(
246 "{}{}{}",
247 " ".repeat(column - span.cell),
248 text,
249 " ".repeat(width - column)
250 ),
251 ));
252 }
253 }
254 if column > width && !pad {
255 return None;
256 }
257 Some(Replacement::new(
258 Range::charwise(end, end),
259 format!(
260 "{}{text}",
261 " ".repeat(column.get().saturating_sub(width.get()))
262 ),
263 ))
264 }
265
266 pub(crate) fn block_insert(&mut self, right: bool) {
267 let Some(rect) = self.block_rect() else {
268 return;
269 };
270 let column = if right {
271 rect.right_cell + 1
272 } else {
273 rect.left_cell
274 };
275 let rows = (rect.first_line + 1..=rect.last_line)
276 .filter(|&line| self.insertion(line, column, "", right).is_some())
277 .collect();
278 if let Some(edit) = self.insertion(rect.first_line, column, "", true) {
279 if let Err(error) = self.apply(
280 self.current(),
281 self.buf().revision(),
282 ChangeSet {
283 edits: vec![edit],
284 undo_open: true,
285 },
286 ) {
287 self.message = error.to_string();
288 return;
289 }
290 }
291 self.mode = Mode::Insert;
292 self.land_at_cell(rect.first_line, column);
293 self.block_insert_state = Some(BlockInsertState {
294 rows,
295 column,
296 pad: right,
297 });
298 self.enter_insert_from("<c-v>I");
299 }
300
301 pub(crate) fn block_replicate(&mut self, typed: &str) {
302 let Some(state) = self.block_insert_state.take() else {
303 return;
304 };
305 if typed.is_empty() {
306 return;
307 }
308 let edits = state
309 .rows
310 .into_iter()
311 .filter_map(|line| {
312 if line > self.buf().last_content_line() {
313 return None;
314 }
315 self.insertion(line, state.column, typed, state.pad)
316 })
317 .collect();
318 if let Err(error) = self.apply(
319 self.current(),
320 self.buf().revision(),
321 ChangeSet {
322 edits,
323 undo_open: true,
324 },
325 ) {
326 self.message = error.to_string();
327 }
328 }
329}