1use crate::error::{Error, Result};
2use crate::highlight::Highlighting;
3use crate::input::{InputSeq, KeySeq};
4use crate::row::Row;
5use crate::signal::SigwinchWatcher;
6use crate::status_bar::StatusBar;
7use crate::term_color::{Color, TermColor};
8use crate::text_buffer::TextBuffer;
9use std::cmp;
10use std::io::Write;
11use std::time::SystemTime;
12use unicode_width::UnicodeWidthChar;
13
14pub const VERSION: &str = env!("CARGO_PKG_VERSION");
15pub const HELP: &str = "\
16 Ctrl-Q : Quit
17 Ctrl-S : Save to file
18 Ctrl-O : Open text buffer
19 Ctrl-X : Next text buffer
20 Alt-X : Previous text buffer
21 Ctrl-P or UP : Move cursor up
22 Ctrl-N or DOWN : Move cursor down
23 Ctrl-F or RIGHT : Move cursor right
24 Ctrl-B or LEFT : Move cursor left
25 Ctrl-A or Alt-LEFT or HOME : Move cursor to head of line
26 Ctrl-E or Alt-RIGHT or END : Move cursor to end of line
27 Ctrl-[ or Ctrl-V or PAGE DOWN : Next page
28 Ctrl-] or Alt-V or PAGE UP : Previous page
29 Alt-F or Ctrl-RIGHT : Move cursor to next word
30 Alt-B or Ctrl-LEFT : Move cursor to previous word
31 Alt-N or Ctrl-DOWN : Move cursor to next paragraph
32 Alt-P or Ctrl-UP : Move cursor to previous paragraph
33 Alt-< : Move cursor to top of file
34 Alt-> : Move cursor to bottom of file
35 Ctrl-H or BACKSPACE : Delete character
36 Ctrl-D or DELETE : Delete next character
37 Ctrl-W : Delete a word
38 Ctrl-J : Delete until head of line
39 Ctrl-K : Delete until end of line
40 Ctrl-U : Undo last change
41 Ctrl-R : Redo last undo change
42 Ctrl-G : Search text
43 Ctrl-M : New line
44 Ctrl-L : Refresh screen
45 Ctrl-? : Show this help";
46
47#[derive(PartialEq)]
48enum StatusMessageKind {
49 Info,
50 Error,
51}
52
53struct StatusMessage {
54 text: String,
55 timestamp: SystemTime,
56 kind: StatusMessageKind,
57}
58
59impl StatusMessage {
60 fn new<S: Into<String>>(message: S, kind: StatusMessageKind) -> StatusMessage {
61 StatusMessage {
62 text: message.into(),
63 timestamp: SystemTime::now(),
64 kind,
65 }
66 }
67}
68
69fn get_window_size<I, W>(input: I, mut output: W) -> Result<(usize, usize)>
70where
71 I: Iterator<Item = Result<InputSeq>>,
72 W: Write,
73{
74 if let Some(s) = term_size::dimensions_stdout() {
75 return Ok(s);
76 }
77
78 output.write(b"\x1b[9999C\x1b[9999B\x1b[6n")?;
82 output.flush()?;
83
84 for seq in input {
86 if let KeySeq::Cursor(r, c) = seq?.key {
87 return Ok((c, r));
88 }
89 }
90
91 Err(Error::UnknownWindowSize) }
93
94fn too_small_window(width: usize, height: usize) -> bool {
95 width < 1 || height < 3
96}
97
98#[derive(PartialEq, Clone, Copy, Debug)]
99enum DrawMessage {
100 Open,
101 Close,
102 Update,
103 DoNothing,
104}
105
106impl DrawMessage {
107 fn fold(self, rhs: Self) -> Self {
108 use DrawMessage::*;
111 match (self, rhs) {
112 (Open, Open) => unreachable!(),
113 (Open, Close) => DoNothing,
114 (Open, Update) => Open,
115 (Close, Open) => Update,
116 (Close, Close) => unreachable!(),
117 (Close, Update) => unreachable!(),
118 (Update, Open) => unreachable!(),
119 (Update, Close) => Close,
120 (Update, Update) => Update,
121 (lhs, DoNothing) => lhs,
122 (DoNothing, rhs) => rhs,
123 }
124 }
125}
126
127pub struct Screen<W: Write> {
128 output: W,
129 rx: usize,
131 num_cols: usize,
133 num_rows: usize,
134 message: Option<StatusMessage>,
135 draw_message: DrawMessage,
136 dirty_start: Option<usize>,
139 sigwinch: SigwinchWatcher,
141 term_color: TermColor,
142 pub cursor_moved: bool,
143 pub rowoff: usize, pub coloff: usize, }
146
147impl<W: Write> Screen<W> {
148 pub fn new<I>(size: Option<(usize, usize)>, input: I, mut output: W) -> Result<Self>
149 where
150 I: Iterator<Item = Result<InputSeq>>,
151 {
152 let (w, h) = if let Some(s) = size {
153 s
154 } else {
155 get_window_size(input, &mut output)?
156 };
157
158 if too_small_window(w, h) {
159 return Err(Error::TooSmallWindow(w, h));
160 }
161
162 output.write(b"\x1b[?1049h")?;
167
168 Ok(Self {
169 output,
170 rx: 0,
171 num_cols: w,
172 num_rows: h.saturating_sub(2),
174 message: Some(StatusMessage::new(
175 "Ctrl-? for help",
176 StatusMessageKind::Info,
177 )),
178 draw_message: DrawMessage::Open,
179 dirty_start: Some(0), sigwinch: SigwinchWatcher::new()?,
181 term_color: TermColor::from_env(),
182 cursor_moved: true,
183 rowoff: 0,
184 coloff: 0,
185 })
186 }
187
188 fn write_flush(&mut self, bytes: &[u8]) -> Result<()> {
189 self.output.write(bytes)?;
190 self.output.flush()?;
191 Ok(())
192 }
193
194 fn trim_line<S: AsRef<str>>(&self, line: &S) -> String {
195 let line = line.as_ref();
196 if line.len() <= self.coloff {
197 return "".to_string();
198 }
199 line.chars().skip(self.coloff).take(self.num_cols).collect()
200 }
201
202 fn draw_status_bar<B: Write>(&self, mut buf: B, status_bar: &StatusBar) -> Result<()> {
203 write!(buf, "\x1b[{}H", self.rows() + 1)?;
204
205 buf.write(self.term_color.sequence(Color::Invert))?;
206
207 let left = status_bar.left();
208 let left = &left[..cmp::min(left.len(), self.num_cols)];
210 buf.write(left.as_bytes())?; let rest_len = self.num_cols - left.len();
213 if rest_len == 0 {
214 buf.write(self.term_color.sequence(Color::Reset))?;
215 return Ok(());
216 }
217
218 let right = status_bar.right();
219 if right.len() > rest_len {
220 for _ in 0..rest_len {
221 buf.write(b" ")?;
222 }
223 buf.write(self.term_color.sequence(Color::Reset))?;
224 return Ok(());
225 }
226
227 for _ in 0..rest_len - right.len() {
228 buf.write(b" ")?; }
230 buf.write(right.as_bytes())?;
231
232 buf.write(self.term_color.sequence(Color::Reset))?;
233 Ok(())
234 }
235
236 fn draw_message_bar<B: Write>(&self, mut buf: B, message: &StatusMessage) -> Result<()> {
237 let text = &message.text[..cmp::min(message.text.len(), self.num_cols)];
239
240 write!(buf, "\x1b[{}H", self.num_rows + 2)?;
241
242 if message.kind == StatusMessageKind::Error {
243 buf.write(self.term_color.sequence(Color::RedBG))?;
244 }
245
246 buf.write(text.as_bytes())?;
247
248 if message.kind != StatusMessageKind::Info {
249 buf.write(self.term_color.sequence(Color::Reset))?;
250 }
251
252 buf.write(b"\x1b[K")?;
253 Ok(())
254 }
255
256 pub fn render_welcome(&mut self, status_bar: &StatusBar) -> Result<()> {
257 self.write_flush(b"\x1b[?25l")?; let mut buf = Vec::with_capacity((self.rows() + 2 + self.num_cols) * 3);
260 buf.write(self.term_color.sequence(Color::Reset))?;
261
262 for y in 0..self.rows() {
263 write!(buf, "\x1b[{}H", y + 1)?;
264
265 if y == self.rows() / 3 {
266 let msg_buf = format!("Kiro editor -- version {}", VERSION);
267 let welcome = self.trim_line(&msg_buf);
268 let padding = (self.num_cols - welcome.len()) / 2;
269 if padding > 0 {
270 buf.write(self.term_color.sequence(Color::NonText))?;
271 buf.write(b"~")?;
272 buf.write(self.term_color.sequence(Color::Reset))?;
273 for _ in 0..padding - 1 {
274 buf.write(b" ")?;
275 }
276 }
277 buf.write(welcome.as_bytes())?;
278 } else {
279 buf.write(self.term_color.sequence(Color::NonText))?;
280 buf.write(b"~")?;
281 }
282
283 buf.write(b"\x1b[K")?;
284 }
285
286 buf.write(self.term_color.sequence(Color::Reset))?;
287 self.draw_status_bar(&mut buf, status_bar)?;
288 if let Some(message) = &self.message {
289 self.draw_message_bar(&mut buf, message)?;
290 }
291
292 write!(buf, "\x1b[H")?; buf.write(b"\x1b[?25h")?; self.write_flush(&buf)?;
295
296 self.after_render();
297 Ok(())
298 }
299
300 fn draw_rows<B: Write>(
301 &self,
302 mut buf: B,
303 dirty_start: usize,
304 rows: &[Row],
305 hl: &Highlighting,
306 ) -> Result<()> {
307 let row_len = rows.len();
308
309 buf.write(self.term_color.sequence(Color::Reset))?;
310
311 for y in 0..self.rows() {
312 let file_row = y + self.rowoff;
313
314 if file_row < dirty_start {
315 continue;
316 }
317
318 write!(buf, "\x1b[{}H", y + 1)?;
320
321 if file_row >= row_len {
322 buf.write(self.term_color.sequence(Color::NonText))?;
323 buf.write(b"~")?;
324 } else {
325 let row = &rows[file_row];
326
327 let mut col = 0;
328 let mut prev_color = Color::Reset;
329 for (c, hl) in row.render_text().chars().zip(hl.lines[file_row].iter()) {
330 col += c.width_cjk().unwrap_or(1);
331 if col <= self.coloff {
332 continue;
333 } else if col > self.num_cols + self.coloff {
334 break;
335 }
336
337 let color = hl.color();
338 if color != prev_color {
339 if prev_color.has_bg_color() {
340 buf.write(self.term_color.sequence(Color::Reset))?;
341 }
342 buf.write(self.term_color.sequence(color))?;
343 prev_color = color;
344 }
345
346 write!(buf, "{}", c)?;
347 }
348 }
349
350 buf.write(self.term_color.sequence(Color::Reset))?;
353
354 buf.write(b"\x1b[K")?;
356 }
357
358 Ok(())
359 }
360
361 fn redraw(
362 &mut self,
363 text_buf: &TextBuffer,
364 hl: &Highlighting,
365 status_bar: &StatusBar,
366 ) -> Result<()> {
367 let cursor_row = text_buf.cy() - self.rowoff + 1;
368 let cursor_col = self.rx - self.coloff + 1;
369 let draw_message = self.draw_message;
370
371 if self.dirty_start.is_none()
372 && !status_bar.redraw
373 && draw_message == DrawMessage::DoNothing
374 {
375 if self.cursor_moved {
376 write!(self.output, "\x1b[{};{}H", cursor_row, cursor_col)?;
377 self.output.flush()?;
378 }
379 return Ok(());
380 }
381
382 self.write_flush(b"\x1b[?25l")?;
386
387 let mut buf = Vec::with_capacity((self.rows() + 2) * self.num_cols);
388 if let Some(s) = self.dirty_start {
389 self.draw_rows(&mut buf, s, text_buf.rows(), hl)?;
390 }
391
392 if status_bar.redraw
394 || draw_message == DrawMessage::Open
395 || draw_message == DrawMessage::Close
396 {
397 self.draw_status_bar(&mut buf, status_bar)?;
398 }
399
400 if draw_message == DrawMessage::Update || draw_message == DrawMessage::Open {
402 if let Some(message) = &self.message {
403 self.draw_message_bar(&mut buf, message)?;
404 }
405 }
406
407 write!(buf, "\x1b[{};{}H", cursor_row, cursor_col)?;
409
410 buf.write(b"\x1b[?25h")?;
412
413 self.write_flush(&buf)?;
414
415 Ok(())
416 }
417
418 fn next_coloff(&self, want_stop: usize, row: &Row) -> usize {
419 let mut coloff = 0;
420 for c in row.render_text().chars() {
421 coloff += c.width_cjk().unwrap_or(1);
422 if coloff >= want_stop {
423 break;
425 }
426 }
427 coloff
428 }
429
430 fn do_scroll(&mut self, rows: &[Row], (cx, cy): (usize, usize)) {
431 let prev_rowoff = self.rowoff;
432 let prev_coloff = self.coloff;
433
434 if cy < rows.len() {
436 self.rx = rows[cy].rx_from_cx(cx);
437 } else {
438 self.rx = 0;
439 }
440
441 if cy < self.rowoff {
443 self.rowoff = cy;
445 }
446 if cy >= self.rowoff + self.rows() {
447 self.rowoff = cy - self.rows() + 1;
449 }
450 if self.rx < self.coloff {
451 self.coloff = self.rx;
452 }
453 if self.rx >= self.coloff + self.num_cols {
454 self.coloff = self.next_coloff(self.rx - self.num_cols + 1, &rows[cy]);
455 }
456
457 if prev_rowoff != self.rowoff || prev_coloff != self.coloff {
458 self.set_dirty_start(self.rowoff);
463 }
464 }
465
466 fn update_message_bar(&mut self) -> Result<()> {
467 if let Some(m) = &self.message {
468 if SystemTime::now().duration_since(m.timestamp)?.as_secs() > 5 {
469 self.unset_message();
470 }
471 }
472 if self.draw_message == DrawMessage::Close {
473 self.set_dirty_start(self.num_rows); }
475 Ok(())
476 }
477
478 fn after_render(&mut self) {
479 self.dirty_start = None;
481 self.cursor_moved = false;
482 self.draw_message = DrawMessage::DoNothing;
483 }
484
485 pub fn render(
486 &mut self,
487 buf: &TextBuffer,
488 hl: &mut Highlighting,
489 status_bar: &StatusBar,
490 ) -> Result<()> {
491 self.do_scroll(buf.rows(), buf.cursor());
492 self.update_message_bar()?; hl.update(buf.rows(), self.rowoff + self.rows());
494 self.redraw(buf, hl, status_bar)?;
495 self.after_render();
496 Ok(())
497 }
498
499 pub fn render_help(&mut self) -> Result<()> {
500 let help: Vec<_> = HELP
501 .split('\n')
502 .skip_while(|s| !s.contains(':'))
503 .map(str::trim_start)
504 .collect();
505 let rows = self.rows();
506
507 let vertical_margin = if help.len() < rows {
508 (rows - help.len()) / 2
509 } else {
510 0
511 };
512 let help_max_width = help.iter().map(|l| l.len()).max().unwrap();
513 let left_margin = if help_max_width < self.num_cols {
514 (self.num_cols - help_max_width) / 2
515 } else {
516 0
517 };
518
519 let mut buf = Vec::with_capacity(rows * self.num_cols);
520
521 for y in 0..vertical_margin {
522 write!(buf, "\x1b[{}H", y + 1)?;
523 buf.write(b"\x1b[K")?;
524 }
525
526 let left_pad = " ".repeat(left_margin);
527 let help_height = cmp::min(vertical_margin + help.len(), rows);
528 for y in vertical_margin..help_height {
529 let idx = y - vertical_margin;
530 write!(buf, "\x1b[{}H", y + 1)?;
531 buf.write(left_pad.as_bytes())?;
532
533 let help = &help[idx][..cmp::min(help[idx].len(), self.num_cols)];
534 buf.write(self.term_color.sequence(Color::Cyan))?;
535 let mut cols = help.split(':');
536 if let Some(col) = cols.next() {
537 buf.write(col.as_bytes())?;
538 }
539 buf.write(self.term_color.sequence(Color::Reset))?;
540 if let Some(col) = cols.next() {
541 write!(buf, ":{}", col)?;
542 }
543
544 buf.write(b"\x1b[K")?;
545 }
546
547 for y in help_height..rows {
548 write!(buf, "\x1b[{}H", y + 1)?;
549 buf.write(b"\x1b[K")?;
550 }
551
552 self.write_flush(&buf)
553 }
554
555 pub fn set_dirty_start(&mut self, start: usize) {
556 if let Some(s) = self.dirty_start {
557 if s < start {
558 return;
559 }
560 }
561 self.dirty_start = Some(start);
562 }
563
564 pub fn maybe_resize<I>(&mut self, input: I) -> Result<bool>
565 where
566 I: Iterator<Item = Result<InputSeq>>,
567 {
568 if !self.sigwinch.notified() {
569 return Ok(false); }
571
572 let (w, h) = get_window_size(input, &mut self.output)?;
573 if too_small_window(w, h) {
574 return Err(Error::TooSmallWindow(w, h));
575 }
576
577 self.num_rows = h.saturating_sub(2);
578 self.num_cols = w;
579 self.dirty_start = Some(0);
580
581 Ok(true)
582 }
583
584 fn set_message(&mut self, m: Option<StatusMessage>) {
585 let op = match (&self.message, &m) {
586 (Some(p), Some(n)) if p.text == n.text => DrawMessage::DoNothing,
587 (Some(_), Some(_)) => DrawMessage::Update,
588 (None, Some(_)) => DrawMessage::Open,
589 (Some(_), None) => DrawMessage::Close,
590 (None, None) => DrawMessage::DoNothing,
591 };
592 self.draw_message = self.draw_message.fold(op);
594 self.message = m;
595 }
596
597 pub fn set_info_message<S: Into<String>>(&mut self, message: S) {
598 self.set_message(Some(StatusMessage::new(message, StatusMessageKind::Info)));
599 }
600
601 pub fn set_error_message<S: Into<String>>(&mut self, message: S) {
602 self.set_message(Some(StatusMessage::new(message, StatusMessageKind::Error)));
603 }
604
605 pub fn unset_message(&mut self) {
606 self.set_message(None);
607 }
608
609 pub fn rows(&self) -> usize {
610 if self.message.is_some() {
611 self.num_rows
612 } else {
613 self.num_rows + 1
614 }
615 }
616
617 pub fn cols(&self) -> usize {
618 self.num_cols
619 }
620
621 pub fn message_text(&self) -> &'_ str {
622 self.message.as_ref().map(|m| m.text.as_str()).unwrap_or("")
623 }
624
625 pub fn force_set_cursor(&mut self, row: usize, col: usize) -> Result<()> {
626 write!(self.output, "\x1b[{};{}H", row, col)?;
627 self.output.flush()?;
628 Ok(())
629 }
630}
631
632impl<W: Write> Drop for Screen<W> {
633 fn drop(&mut self) {
634 self.write_flush(b"\x1b[?1049l\x1b[H")
647 .expect("Back to normal screen buffer");
648 }
649}