1use std::io::{self, Write};
6
7use super::main::Zle;
8
9#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
11pub struct TextAttr {
12 pub bold: bool,
13 pub underline: bool,
14 pub standout: bool,
15 pub blink: bool,
16 pub fg_color: Option<u8>,
17 pub bg_color: Option<u8>,
18}
19
20impl TextAttr {
21 pub fn to_ansi(&self) -> String {
22 let mut codes = Vec::new();
23 if self.bold {
24 codes.push("1".to_string());
25 }
26 if self.underline {
27 codes.push("4".to_string());
28 }
29 if self.standout {
30 codes.push("7".to_string());
31 }
32 if self.blink {
33 codes.push("5".to_string());
34 }
35 if let Some(fg) = self.fg_color {
36 codes.push(format!("38;5;{}", fg));
37 }
38 if let Some(bg) = self.bg_color {
39 codes.push(format!("48;5;{}", bg));
40 }
41 if codes.is_empty() {
42 String::new()
43 } else {
44 format!("\x1b[{}m", codes.join(";"))
45 }
46 }
47}
48
49#[derive(Debug, Clone, Default)]
51pub struct RefreshElement {
52 pub chr: char,
53 pub atr: TextAttr,
54 pub width: u8,
55}
56
57impl RefreshElement {
58 pub fn new(chr: char) -> Self {
59 let width = unicode_width::UnicodeWidthChar::width(chr).unwrap_or(1) as u8;
60 RefreshElement {
61 chr,
62 atr: TextAttr::default(),
63 width,
64 }
65 }
66
67 pub fn with_attr(chr: char, atr: TextAttr) -> Self {
68 let width = unicode_width::UnicodeWidthChar::width(chr).unwrap_or(1) as u8;
69 RefreshElement { chr, atr, width }
70 }
71}
72
73#[derive(Debug, Clone)]
75pub struct VideoBuffer {
76 pub lines: Vec<Vec<RefreshElement>>,
78 pub cols: usize,
80 pub rows: usize,
82}
83
84impl VideoBuffer {
85 pub fn new(cols: usize, rows: usize) -> Self {
86 let lines = vec![vec![RefreshElement::new(' '); cols]; rows];
87 VideoBuffer { lines, cols, rows }
88 }
89
90 pub fn clear(&mut self) {
91 for line in &mut self.lines {
92 for elem in line.iter_mut() {
93 *elem = RefreshElement::new(' ');
94 }
95 }
96 }
97
98 pub fn resize(&mut self, cols: usize, rows: usize) {
99 self.cols = cols;
100 self.rows = rows;
101 self.lines
102 .resize(rows, vec![RefreshElement::new(' '); cols]);
103 for line in &mut self.lines {
104 line.resize(cols, RefreshElement::new(' '));
105 }
106 }
107
108 pub fn set(&mut self, row: usize, col: usize, elem: RefreshElement) {
109 if row < self.rows && col < self.cols {
110 self.lines[row][col] = elem;
111 }
112 }
113
114 pub fn get(&self, row: usize, col: usize) -> Option<&RefreshElement> {
115 self.lines.get(row).and_then(|line| line.get(col))
116 }
117}
118
119#[derive(Debug, Clone, Default)]
121pub struct RefreshState {
122 pub columns: usize,
124 pub lines: usize,
126 pub vln: usize,
128 pub vcs: usize,
130 pub lpromptw: usize,
132 pub rpromptw: usize,
134 pub scrolloff: usize,
136 pub region_highlight_start: Option<usize>,
138 pub region_highlight_end: Option<usize>,
140 pub old_video: Option<VideoBuffer>,
142 pub new_video: Option<VideoBuffer>,
144 pub lpromptbuf: String,
146 pub rpromptbuf: String,
148 pub need_full_redraw: bool,
150 pub predisplay: String,
152 pub postdisplay: String,
154}
155
156impl RefreshState {
157 pub fn new() -> Self {
158 let (cols, rows) = get_terminal_size();
159 RefreshState {
160 columns: cols,
161 lines: rows,
162 old_video: Some(VideoBuffer::new(cols, rows)),
163 new_video: Some(VideoBuffer::new(cols, rows)),
164 need_full_redraw: true,
165 ..Default::default()
166 }
167 }
168
169 pub fn reset_video(&mut self) {
170 let (cols, rows) = get_terminal_size();
171 self.columns = cols;
172 self.lines = rows;
173 self.old_video = Some(VideoBuffer::new(cols, rows));
174 self.new_video = Some(VideoBuffer::new(cols, rows));
175 self.need_full_redraw = true;
176 }
177
178 pub fn free_video(&mut self) {
179 self.old_video = None;
180 self.new_video = None;
181 }
182
183 pub fn swap_buffers(&mut self) {
184 std::mem::swap(&mut self.old_video, &mut self.new_video);
185 if let Some(ref mut new) = self.new_video {
186 new.clear();
187 }
188 }
189}
190
191impl Zle {
192 pub fn zrefresh(&mut self) {
195 let stdout = io::stdout();
196 let mut handle = stdout.lock();
197
198 let (cols, _rows) = get_terminal_size();
200
201 let prompt = self.prompt();
203 let buffer: String = self.zleline.iter().collect();
204 let cursor = self.zlecs;
205
206 let prompt_width = visible_width(prompt);
208 let buffer_before_cursor: String = self.zleline[..cursor.min(self.zleline.len())]
209 .iter()
210 .collect();
211 let cursor_col = prompt_width + visible_width(&buffer_before_cursor);
212
213 let scroll_margin = 8;
215 let effective_cols = cols.saturating_sub(1);
216
217 let scroll_offset = if cursor_col >= effective_cols.saturating_sub(scroll_margin) {
218 cursor_col.saturating_sub(effective_cols / 2)
219 } else {
220 0
221 };
222
223 let _ = write!(handle, "\r\x1b[K");
225
226 if scroll_offset < prompt_width {
228 let visible_prompt = skip_chars(prompt, scroll_offset);
229 let _ = write!(handle, "{}", visible_prompt);
230 }
231
232 let buffer_start = scroll_offset.saturating_sub(prompt_width);
234 let visible_buffer = skip_chars(&buffer, buffer_start);
235 let truncated = truncate_to_width(
236 &visible_buffer,
237 effective_cols.saturating_sub(prompt_width.saturating_sub(scroll_offset)),
238 );
239 let _ = write!(handle, "{}", truncated);
240
241 let display_cursor_col = cursor_col.saturating_sub(scroll_offset);
243 let _ = write!(handle, "\r\x1b[{}C", display_cursor_col);
244
245 let _ = handle.flush();
246 }
247
248 pub fn full_refresh(&mut self) -> io::Result<()> {
250 print!("\x1b[2J\x1b[H");
251 self.zrefresh();
252 io::stdout().flush()
253 }
254
255 pub fn partial_refresh(&mut self) -> io::Result<()> {
257 self.zrefresh();
258 io::stdout().flush()
259 }
260
261 pub fn clearscreen(&mut self) {
264 print!("\x1b[2J\x1b[H");
265 let _ = io::stdout().flush();
266 self.zrefresh();
267 }
268
269 pub fn redisplay(&mut self) {
272 self.zrefresh();
273 }
274
275 pub fn moveto(&mut self, row: usize, col: usize) {
278 print!("\x1b[{};{}H", row + 1, col + 1);
280 let _ = io::stdout().flush();
281 }
282
283 pub fn tc_downcurs(&mut self, count: usize) {
286 if count > 0 {
287 print!("\x1b[{}B", count);
288 let _ = io::stdout().flush();
289 }
290 }
291
292 pub fn tc_rightcurs(&mut self, count: usize) {
295 if count > 0 {
296 print!("\x1b[{}C", count);
297 let _ = io::stdout().flush();
298 }
299 }
300
301 pub fn scrollwindow(&mut self, lines: i32) {
304 if lines > 0 {
305 print!("\x1b[{}S", lines);
307 } else if lines < 0 {
308 print!("\x1b[{}T", -lines);
310 }
311 let _ = io::stdout().flush();
312 }
313
314 pub fn singlerefresh(&mut self) {
317 self.zrefresh();
318 }
319
320 pub fn refreshline(&mut self, _line: usize) {
323 self.zrefresh();
324 }
325
326 pub fn zwcputc(&self, c: char) {
329 print!("{}", c);
330 }
331
332 pub fn zwcwrite(&self, s: &str) {
335 print!("{}", s);
336 }
337}
338
339pub fn get_terminal_size() -> (usize, usize) {
341 unsafe {
342 let mut ws: libc::winsize = std::mem::zeroed();
343 if libc::ioctl(0, libc::TIOCGWINSZ, &mut ws) == 0 {
344 (ws.ws_col as usize, ws.ws_row as usize)
345 } else {
346 (80, 24) }
348 }
349}
350
351fn visible_width(s: &str) -> usize {
353 let mut width = 0;
354 let mut in_escape = false;
355
356 for c in s.chars() {
357 if in_escape {
358 if c.is_ascii_alphabetic() {
359 in_escape = false;
360 }
361 } else if c == '\x1b' {
362 in_escape = true;
363 } else {
364 width += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
365 }
366 }
367
368 width
369}
370
371fn skip_chars(s: &str, n: usize) -> &str {
373 let mut width = 0;
374 let mut byte_idx = 0;
375 let mut in_escape = false;
376
377 for (i, c) in s.char_indices() {
378 if width >= n {
379 byte_idx = i;
380 break;
381 }
382
383 if in_escape {
384 if c.is_ascii_alphabetic() {
385 in_escape = false;
386 }
387 } else if c == '\x1b' {
388 in_escape = true;
389 } else {
390 width += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
391 }
392 byte_idx = i + c.len_utf8();
393 }
394
395 &s[byte_idx..]
396}
397
398fn truncate_to_width(s: &str, max_width: usize) -> &str {
400 let mut width = 0;
401 let mut byte_idx = s.len();
402 let mut in_escape = false;
403
404 for (i, c) in s.char_indices() {
405 if in_escape {
406 if c.is_ascii_alphabetic() {
407 in_escape = false;
408 }
409 } else if c == '\x1b' {
410 in_escape = true;
411 } else {
412 let char_width = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
413 if width + char_width > max_width {
414 byte_idx = i;
415 break;
416 }
417 width += char_width;
418 }
419 }
420
421 &s[..byte_idx]
422}
423
424#[derive(Debug, Clone)]
426pub struct RegionHighlight {
427 pub start: usize,
428 pub end: usize,
429 pub attr: TextAttr,
430 pub memo: Option<String>,
431}
432
433#[derive(Debug, Default)]
435pub struct HighlightManager {
436 pub regions: Vec<RegionHighlight>,
437}
438
439impl HighlightManager {
440 pub fn new() -> Self {
441 HighlightManager {
442 regions: Vec::new(),
443 }
444 }
445
446 pub fn set_region_highlight(&mut self, start: usize, end: usize, attr: TextAttr) {
449 self.regions.push(RegionHighlight {
450 start,
451 end,
452 attr,
453 memo: None,
454 });
455 }
456
457 pub fn get_region_highlight(&self, pos: usize) -> Option<&RegionHighlight> {
460 self.regions.iter().find(|r| pos >= r.start && pos < r.end)
461 }
462
463 pub fn unset_region_highlight(&mut self) {
466 self.regions.clear();
467 }
468
469 pub fn free(&mut self) {
472 self.regions.clear();
473 }
474}
475
476pub fn tcout(cap: &str) {
480 print!("{}", cap);
481}
482
483pub fn tcoutarg(cap: &str, arg: i32) {
484 let s = cap.replace("%d", &arg.to_string());
486 print!("{}", s);
487}
488
489pub fn tcmultout(cap: &str, count: i32) {
490 for _ in 0..count {
491 print!("{}", cap);
492 }
493}
494
495pub fn tcoutclear(to_end: bool) {
496 if to_end {
497 print!("\x1b[J"); } else {
499 print!("\x1b[2J"); }
501}
502
503pub fn zle_refresh_boot() -> RefreshState {
506 RefreshState::new()
507}
508
509pub fn zle_refresh_finish(state: &mut RefreshState) {
512 state.free_video();
513}
514
515pub fn zle_set_highlight(_highlight: &str) {
518 }
521
522#[cfg(test)]
523mod tests {
524 use super::*;
525
526 #[test]
527 fn test_visible_width() {
528 assert_eq!(visible_width("hello"), 5);
529 assert_eq!(visible_width("\x1b[31mhello\x1b[0m"), 5);
530 assert_eq!(visible_width("日本語"), 6); }
532
533 #[test]
534 fn test_video_buffer() {
535 let mut buf = VideoBuffer::new(80, 24);
536 assert_eq!(buf.cols, 80);
537 assert_eq!(buf.rows, 24);
538
539 buf.set(0, 0, RefreshElement::new('A'));
540 assert_eq!(buf.get(0, 0).map(|e| e.chr), Some('A'));
541
542 buf.clear();
543 assert_eq!(buf.get(0, 0).map(|e| e.chr), Some(' '));
544 }
545
546 #[test]
547 fn test_refresh_state() {
548 let mut state = RefreshState::new();
549 assert!(state.old_video.is_some());
550 assert!(state.new_video.is_some());
551
552 state.swap_buffers();
553 state.free_video();
554 assert!(state.old_video.is_none());
555 }
556}