1use crate::api;
2use crate::api::console::Style;
3use crate::api::process::ExitCode;
4use crate::api::prompt::Prompt;
5use crate::api::regex::Regex;
6use crate::api::{console, fs, io};
7
8use alloc::format;
9use alloc::string::{String, ToString};
10use alloc::vec;
11use alloc::vec::Vec;
12use core::cmp;
13
14enum Cmd {
15 Delete,
16 Open,
17 Quit,
18 Replace,
19 Save,
20}
21
22struct EditorConfig {
23 tab_size: usize,
24}
25
26#[derive(Clone)]
27struct Coords {
28 pub x: usize,
29 pub y: usize,
30}
31
32#[derive(Clone)]
33pub struct Buffer {
34 pathname: String,
35 lines: Vec<String>,
36 cursor: Coords,
37 offset: Coords,
38 highlighted: Vec<(usize, usize, char)>,
39}
40
41impl From<&str> for Buffer {
42 fn from(pathname: &str) -> Self {
43 let p: Vec<&str> = pathname.split(':').collect();
44 let pathname = p[0].to_string();
45 let y = p.get(1).and_then(|s| {
46 s.parse::<usize>().ok()
47 }).unwrap_or(1).saturating_sub(1);
48 let x = p.get(2).and_then(|s| {
49 s.parse::<usize>().ok()
50 }).unwrap_or(1).saturating_sub(1);
51
52 let cursor = Coords { x: x % cols(), y: y % rows() };
53 let offset = Coords { x: x - cursor.x, y: y - cursor.y };
54 let highlighted = Vec::new();
55 let mut lines = Vec::new();
56
57 match fs::read_to_string(&pathname) {
58 Ok(contents) => {
59 for line in contents.lines() {
60 lines.push(line.into());
61 }
62 if lines.is_empty() {
63 lines.push(String::new());
64 }
65 }
66 Err(_) => {
67 lines.push(String::new());
68 }
69 };
70
71 Self {
72 pathname,
73 lines,
74 cursor,
75 offset,
76 highlighted,
77 }
78 }
79}
80
81impl From<&Editor> for Buffer {
82 fn from(editor: &Editor) -> Self {
83 Buffer {
84 pathname: editor.pathname.clone(),
85 lines: editor.lines.clone(),
86 cursor: editor.cursor.clone(),
87 offset: editor.offset.clone(),
88 highlighted: editor.highlighted.clone(),
89 }
90 }
91}
92
93pub struct Editor {
94 buffer_prompt: Prompt,
95 buffers: Vec<Buffer>,
96 buf: usize,
97
98 pathname: String,
99 lines: Vec<String>,
100 cursor: Coords,
101 offset: Coords,
102 highlighted: Vec<(usize, usize, char)>,
103
104 clipboard: Option<String>,
105 config: EditorConfig,
106 search_prompt: Prompt,
107 search_query: String,
108 command_prompt: Prompt,
109 command_history: String,
110}
111
112impl Editor {
113 pub fn new(pathname: &str) -> Self {
114 let clipboard = None;
115 let config = EditorConfig { tab_size: 4 };
116
117 let search_query = String::new();
118 let mut search_prompt = Prompt::new();
119 search_prompt.eol = false;
120
121 let mut command_prompt = Prompt::new();
122 let command_history = "~/.edit-history".to_string();
123 command_prompt.history.load(&command_history);
124 command_prompt.eol = false;
125
126 let mut buffer_prompt = Prompt::new();
128 buffer_prompt.eol = false;
129
130 let buf = Buffer::from(pathname);
131
132 let pathname = buf.pathname.clone();
133 let lines = buf.lines.clone();
134 let cursor = buf.cursor.clone();
135 let offset = buf.offset.clone();
136 let highlighted = buf.highlighted.clone();
137
138 let buffers = vec![buf];
139 let buf = 0;
140
141 Self {
142 buffer_prompt,
143 buffers,
144 buf,
145 pathname,
146 clipboard,
147 lines,
148 cursor,
149 offset,
150 highlighted,
151 config,
152 search_prompt,
153 search_query,
154 command_prompt,
155 command_history,
156 }
157 }
158
159 pub fn save(&mut self, path: &str) -> Result<(), ExitCode> {
160 let contents = self.lines.join("\n") + "\n";
161
162 if fs::write(path, contents.as_bytes()).is_ok() {
163 self.pathname = path.into();
164 let n = self.lines.len();
165 let status = format!("Wrote {}L to '{}'", n, path);
166 self.print_status(&status, "yellow");
167 Ok(())
168 } else {
169 let status = format!("Could not write to '{}'", path);
170 self.print_status(&status, "red");
171 Err(ExitCode::Failure)
172 }
173 }
174
175 fn print_status(&mut self, status: &str, background: &str) {
176 print!("\x1b[{};1H", rows() + 1);
178
179 let color = Style::color("black").with_background(background);
180 let reset = Style::reset();
181 print!("{}{:cols$}{}", color, status, reset, cols = cols());
182
183 print!("\x1b[{};{}H", self.cursor.y + 1, self.cursor.x + 1);
185 }
186
187 fn print_editing_status(&mut self) {
188 let max = 50;
189 let mut path = self.pathname.clone();
190 if self.pathname.chars().count() > max {
191 path.truncate(max - 3);
192 path.push_str("...");
193 }
194 let start = format!("Editing '{}'", path);
195
196 let x = self.offset.x + self.cursor.x + 1;
197 let y = self.offset.y + self.cursor.y + 1;
198 let n = y * 100 / self.lines.len();
199 let end = format!("{},{} {:3}%", y, x, n);
200
201 let width = cols() - start.chars().count();
202 let status = format!("{}{:>width$}", start, end, width = width);
203
204 self.print_status(&status, "silver");
205 }
206
207 fn print_screen(&mut self) {
208 let mut lines: Vec<String> = Vec::new();
209 let a = self.offset.y;
210 let b = self.offset.y + rows();
211 for y in a..b {
212 lines.push(self.render_line(y));
213 }
214 println!("\x1b[1;1H{}", lines.join("\n"));
215 }
216
217 fn render_line(&self, y: usize) -> String {
218 let line = if y < self.lines.len() {
220 &self.lines[y]
221 } else {
222 ""
223 };
224
225 let s = format!("{:cols$}", line, cols = self.offset.x);
226 let mut row: Vec<char> = s.chars().collect();
227 let n = self.offset.x + cols();
228 let after = if row.len() > n {
229 row.truncate(n - 1);
230 truncated_line_indicator()
231 } else {
232 " ".repeat(n - row.len())
233 };
234 row.extend(after.chars());
235 row[self.offset.x..].iter().collect()
236 }
237
238 fn render_char(&self, c: char) -> Option<String> {
239 match c {
240 '\t' => Some(" ".repeat(self.config.tab_size)),
241 c if console::is_printable(c) => Some(c.to_string()),
242 _ => None,
243 }
244 }
245
246 fn match_chars(&mut self, opening: char, closing: char) {
247 let mut stack = Vec::new();
248 let ox = self.offset.x;
249 let oy = self.offset.y;
250 let cx = self.cursor.x;
251 let cy = self.cursor.y;
252 if let Some(cursor) = self.lines[oy + cy].chars().nth(ox + cx) {
253 if cursor == closing {
254 for (y, line) in self.lines.iter().enumerate() {
255 for (x, c) in line.chars().enumerate() {
256 if oy + cy == y && ox + cx == x {
257 if let Some((x, y)) = stack.pop() {
259 self.highlighted.push((cx, cy, closing));
260 let is_col = ox <= x && x < ox + cols();
261 let is_row = oy <= y && y < oy + rows();
262 if is_col && is_row {
263 self.highlighted.push(
264 (x - ox, y - oy, opening)
265 );
266 }
267 }
268 return;
269 }
270 if c == opening {
271 stack.push((x, y));
272 }
273 if c == closing {
274 stack.pop();
275 }
276 }
277 if oy + cy == y {
278 break;
279 }
280 }
281 }
282 if cursor == opening {
283 for (y, line) in self.lines.iter().enumerate().skip(oy + cy) {
284 for (x, c) in line.chars().enumerate() {
285 if y == oy + cy && x <= ox + cx {
286 continue; }
288 if c == opening {
289 stack.push((x, y));
290 }
291 if c == closing {
292 if stack.pop().is_none() {
293 self.highlighted.push((cx, cy, opening));
294 let is_col = ox <= x && x < ox + cols();
295 let is_row = oy <= y && y < oy + rows();
296 if is_col && is_row {
297 self.highlighted.push(
298 (x - ox, y - oy, closing)
299 );
300 }
301 return;
302 }
303 }
304 }
305 }
306 }
307 }
308 }
309
310 fn print_highlighted(&mut self) {
311 self.match_chars('(', ')');
312 self.match_chars('{', '}');
313 self.match_chars('[', ']');
314 let color = Style::color("red");
315 let reset = Style::reset();
316 for (x, y, c) in &self.highlighted {
317 if *x == cols() - 1 {
318 continue;
319 }
320 print!("\x1b[{};{}H", y + 1, x + 1);
321 print!("{}{}{}", color, c, reset);
322 }
323 }
324
325 fn clear_highlighted(&mut self) {
326 let reset = Style::reset();
327 for (x, y, c) in &self.highlighted {
328 if *x == cols() - 1 {
329 continue;
330 }
331 print!("\x1b[{};{}H", y + 1, x + 1);
332 print!("{}{}", reset, c);
333 }
334 self.highlighted.clear();
335 }
336
337 fn align_cursor(&mut self) {
352 let x = self.offset.x + self.cursor.x;
353 let y = self.offset.y + self.cursor.y;
354 let eol = self.lines[y].chars().count();
355 if x > eol {
356 let n = cols();
357 self.offset.x = (eol / n) * n;
358 self.cursor.x = eol % n;
359 }
360 }
361
362 pub fn run(&mut self) -> Result<(), ExitCode> {
363 print!("\x1b[2J\x1b[1;1H"); self.print_screen();
365 self.print_editing_status();
366 self.print_highlighted();
367 print!("\x1b[{};{}H", self.cursor.y + 1, self.cursor.x + 1);
368
369 let mut escape = false;
370 let mut csi = false;
371 let mut csi_params = String::new();
372 loop {
373 let c = io::stdin().read_char().unwrap_or('\0');
374 print!("\x1b[?25l"); self.clear_highlighted();
376 print!("\x1b[{};{}H", self.cursor.y + 1, self.cursor.x + 1);
377
378 match c {
379 '\x1B' => { escape = true;
381 continue;
382 }
383 '[' if escape => {
384 csi = true;
385 csi_params.clear();
386 continue;
387 }
388 '\0' => {
389 continue;
390 }
391 '\x11' | '\x03' => { print!("\x1b[2J\x1b[1;1H"); print!("\x1b[?25h"); break;
395 }
396 '\x17' => { self.save(&self.pathname.clone()).ok();
398 print!("\x1b[?25h"); continue;
400 }
401 '\x18' => { let res = self.save(&self.pathname.clone());
403 print!("\x1b[2J\x1b[1;1H"); print!("\x1b[?25h"); return res;
406 }
407 '\n' => { self.handle_newline();
409 }
410 '~' if csi && csi_params == "5" => { self.handle_page_up();
412 }
413 '~' if csi && csi_params == "6" => { self.handle_page_down();
415 }
416 'A' if csi => { self.handle_arrow_up();
418 }
419 'B' if csi => { self.handle_arrow_down();
421 }
422 'C' if csi => { let line = &self.lines[self.offset.y + self.cursor.y];
424 let x = self.cursor.x + self.offset.x;
425 let n = line.chars().count();
426 if line.is_empty() || x >= n {
427 print!("\x1b[?25h"); escape = false;
429 csi = false;
430 continue;
431 } else if self.cursor.x == cols() - 1 {
432 self.offset.x += cols();
433 self.cursor.x -= cols() - 1;
434 self.print_screen();
435 } else {
436 self.cursor.x += 1;
437 }
438 }
439 'D' if csi => { if self.cursor.x + self.offset.x == 0 {
441 print!("\x1b[?25h"); escape = false;
443 csi = false;
444 continue;
445 } else if self.cursor.x == 0 {
446 self.offset.x -= cols();
447 self.cursor.x += cols() - 1;
448 self.align_cursor();
449 self.print_screen();
450 } else {
451 self.cursor.x -= 1;
452 }
453 }
454 'Z' if csi => { }
457 'I' if csi && csi_params == "1;5" => { self.next_buffer();
459 self.print_screen();
460 }
461 'I' if csi && csi_params == "1;6" => { self.previous_buffer();
463 self.print_screen();
464 }
465 '\x14' => { self.cursor.x = 0;
467 self.cursor.y = 0;
468 self.offset.x = 0;
469 self.offset.y = 0;
470 self.print_screen();
471 }
472 '\x02' => { self.cursor.x = 0;
474 self.cursor.y = cmp::min(rows(), self.lines.len()) - 1;
475 self.offset.x = 0;
476 self.offset.y = self.lines.len() - 1 - self.cursor.y;
477 self.print_screen();
478 }
479 '\x01' => { self.cursor.x = 0;
481 self.offset.x = 0;
482 self.print_screen();
483 }
484 '\x05' => { let line = &self.lines[self.offset.y + self.cursor.y];
486 let n = line.chars().count();
487 let w = cols();
488 self.cursor.x = n % w;
489 self.offset.x = w * (n / w);
490 self.print_screen();
491 }
492 '\x04' => { self.cut_line();
494 }
495 '\x19' => { self.copy_line();
497 }
498 '\x10' => { self.paste_line();
500 }
501 '\x06' => { self.find();
503 self.print_screen();
504 }
505 '\x0E' => { self.find_next();
507 self.print_screen();
508 }
509 '\x0F' => { self.open();
511 self.print_screen();
512 }
513 '\x0B' => { self.kill_buffer();
515 self.print_screen();
516 }
517 '\x0C' => { match self.exec() {
519 Some(Cmd::Quit) => {
520 print!("\x1b[2J"); print!("\x1b[1;1H"); print!("\x1b[?25h"); break;
524 }
525 Some(Cmd::Save) => {
526 print!("\x1b[?25h"); continue;
528 }
529 Some(_) => {
530 self.print_screen();
531 }
532 None => {
533 }
534 }
535 }
536 '\x08' => { let y = self.offset.y + self.cursor.y;
538 if self.offset.x + self.cursor.x > 0 {
539 let mut row: Vec<_> = self.lines[y].chars().collect();
541 row.remove(self.offset.x + self.cursor.x - 1);
542 self.lines[y] = row.into_iter().collect();
543
544 if self.cursor.x == 0 {
545 self.offset.x -= cols();
546 self.cursor.x = cols() - 1;
547 self.print_screen();
548 } else {
549 self.cursor.x -= 1;
550 let line = self.render_line(y);
551 print!("\x1b[2K\x1b[1G{}", line);
552 }
553 } else {
554 if self.cursor.y == 0 && self.offset.y == 0 {
556 print!("\x1b[?25h"); escape = false;
558 csi = false;
559 continue;
560 }
561
562 let n = self.lines[y - 1].chars().count();
564 let w = cols();
565 self.cursor.x = n % w;
566 self.offset.x = w * (n / w);
567
568 let line = self.lines.remove(y);
570 self.lines[y - 1].push_str(&line);
571
572 if self.cursor.y > 0 {
574 self.cursor.y -= 1;
575 } else {
576 self.offset.y -= 1;
577 }
578
579 self.print_screen();
580 }
581 }
582 '\x7f' => {
583 let y = self.offset.y + self.cursor.y;
585 let n = self.lines[y].chars().count();
586 if self.offset.x + self.cursor.x >= n {
587 if y + 1 < self.lines.len() {
589 let line = self.lines.remove(y + 1);
590 self.lines[y].push_str(&line);
591 self.print_screen();
592 }
593 } else {
594 self.lines[y].remove(self.offset.x + self.cursor.x);
596 let line = self.render_line(y);
597 print!("\x1b[2K\x1b[1G{}", line);
598 }
599 }
600 c if csi => {
601 csi_params.push(c);
602 continue;
603 }
604 c => {
605 if let Some(s) = self.render_char(c) {
606 let y = self.offset.y + self.cursor.y;
607 let mut row: Vec<_> = self.lines[y].chars().collect();
608 for c in s.chars() {
609 row.insert(self.offset.x + self.cursor.x, c);
610 self.cursor.x += 1;
611 }
612 self.lines[y] = row.into_iter().collect();
613 if self.cursor.x >= cols() {
614 self.offset.x += cols();
615 self.cursor.x -= cols();
616 self.print_screen();
617 } else {
618 let line = self.render_line(y);
619 print!("\x1b[2K\x1b[1G{}", line);
620 }
621 }
622 }
623 }
624 self.print_editing_status();
625 self.print_highlighted();
626 print!("\x1b[{};{}H", self.cursor.y + 1, self.cursor.x + 1);
627 print!("\x1b[?25h"); escape = false;
629 csi = false;
630 }
631 Ok(())
632 }
633
634 fn handle_newline(&mut self) {
635 let x = self.offset.x + self.cursor.x;
636 let y = self.offset.y + self.cursor.y;
637
638 let old_line = self.lines[y].clone();
639 let mut row: Vec<char> = old_line.chars().collect();
640 let new_line = row.split_off(x).into_iter().collect();
641 self.lines[y] = row.into_iter().collect();
642 self.lines.insert(y + 1, new_line);
643 if self.cursor.y == rows() - 1 {
644 self.offset.y += 1;
645 } else {
646 self.cursor.y += 1;
647 }
648 self.cursor.x = 0;
649 self.offset.x = 0;
650 self.print_screen();
651 }
652
653 fn handle_page_up(&mut self) {
654 let scroll = rows() - 1; self.offset.y -= cmp::min(scroll, self.offset.y);
656 self.align_cursor();
657 self.print_screen();
658 }
659
660 fn handle_page_down(&mut self) {
661 let scroll = rows() - 1; let n = cmp::max(self.lines.len(), 1);
663 let remaining = n - self.offset.y - 1;
664 self.offset.y += cmp::min(scroll, remaining);
665 if self.cursor.y + scroll > remaining {
666 self.cursor.y = 0;
667 }
668 self.align_cursor();
669 self.print_screen();
670 }
671
672 fn handle_arrow_up(&mut self) {
673 if self.cursor.y > 0 {
674 self.cursor.y -= 1
675 } else if self.offset.y > 0 {
676 self.offset.y -= 1;
677 }
678 self.align_cursor();
679 self.print_screen();
680 }
681
682 fn handle_arrow_down(&mut self) {
683 let n = self.lines.len() - 1;
684 let is_eof = n == (self.offset.y + self.cursor.y);
685 let is_bottom = self.cursor.y == rows() - 1;
686 if self.cursor.y < cmp::min(rows(), n) {
687 if is_bottom || is_eof {
688 if !is_eof {
689 self.offset.y += 1;
690 }
691 } else {
692 self.cursor.y += 1;
693 }
694 self.align_cursor();
695 self.print_screen();
696 }
697 }
698
699 fn cut_line(&mut self) {
700 let i = self.offset.y + self.cursor.y;
701 self.clipboard = Some(self.lines.remove(i));
702 if self.lines.is_empty() {
703 self.lines.push(String::new());
704 }
705 if i == self.lines.len() {
706 self.handle_arrow_up();
707 } else {
708 self.align_cursor();
709 self.print_screen();
710 }
711 }
712
713 fn copy_line(&mut self) {
714 let i = self.offset.y + self.cursor.y;
715 self.clipboard = Some(self.lines[i].clone());
716 }
717
718 fn paste_line(&mut self) {
719 let i = self.offset.y + self.cursor.y;
720 if let Some(line) = self.clipboard.clone() {
721 self.lines.insert(i + 1, line);
722 self.cursor.x = 0;
723 self.offset.x = 0;
724 self.handle_arrow_down(); }
726 }
727
728 fn exec(&mut self) -> Option<Cmd> {
729 if let Some(cmd) = prompt(&mut self.command_prompt, ":") {
730 print!("\x1b[?25l");
735
736 self.exec_command(&cmd)
737 } else {
738 None
739 }
740 }
741
742 fn exec_command(&mut self, cmd: &str) -> Option<Cmd> {
743 let mut res = None;
744 let params: Vec<&str> = match cmd.chars().next() {
745 Some('w') | Some('o') => {
746 cmd.split(' ').collect()
747 }
748 _ => {
749 cmd.split('/').collect()
750 }
751 };
752 match params[0] {
754 "d" if params.len() == 1 => { let y = self.offset.y + self.cursor.y;
756 self.lines.remove(y);
757 res = Some(Cmd::Delete);
758 }
759 "%d" if params.len() == 1 => { self.lines = vec![String::new()];
761 res = Some(Cmd::Delete);
762 }
763 "g" if params.len() == 3 => { let re = Regex::new(params[1]);
765 if params[2] == "d" { self.lines.retain(|line| !re.is_match(line));
767 res = Some(Cmd::Delete);
768 }
769 }
770 "o" | "open" if params.len() == 2 => { self.open_buffer(params[1]);
772 res = Some(Cmd::Open);
773 }
774 "q" | "quit" if params.len() == 1 => { res = Some(Cmd::Quit);
776 }
777 "s" if params.len() == 4 => { let re = Regex::new(params[1]);
779 let s = params[2];
780 let y = self.offset.y + self.cursor.y;
781 if params[3] == "g" { self.lines[y] = re.replace_all(&self.lines[y], s);
783 } else {
784 self.lines[y] = re.replace(&self.lines[y], s);
785 }
786 res = Some(Cmd::Replace);
787 }
788 "%s" if params.len() == 4 => { let re = Regex::new(params[1]);
790 let s = params[2];
791 let n = self.lines.len();
792 for y in 0..n {
793 if params[3] == "g" { self.lines[y] = re.replace_all(&self.lines[y], s);
795 } else {
796 self.lines[y] = re.replace(&self.lines[y], s);
797 }
798 }
799 res = Some(Cmd::Replace);
800 }
801 "w" | "write" => { let path = if params.len() == 2 {
803 params[1]
804 } else {
805 &self.pathname.clone()
806 };
807 self.save(path).ok();
808 res = Some(Cmd::Save);
809 }
810 _ => {}
811 }
812
813 if res.is_some() {
814 let mut y = self.offset.y + self.cursor.y;
815 let n = self.lines.len() - 1;
816 if y > n {
817 self.cursor.y = n % rows();
818 self.offset.y = n - self.cursor.y;
819 y = n;
820 }
821 let n = self.lines[y].len();
822 if self.offset.x + self.cursor.x > n {
823 self.cursor.x = n % cols();
824 self.offset.x = n - self.cursor.x;
825 }
826
827 self.command_prompt.history.add(cmd);
828 self.command_prompt.history.save(&self.command_history);
829 }
830
831 res
832 }
833
834 pub fn find(&mut self) {
835 if let Some(query) = prompt(&mut self.search_prompt, "Find: ") {
836 if !query.is_empty() {
837 self.search_prompt.history.add(&query);
838 self.search_query = query;
839 self.find_next();
840 }
841 }
842 }
843
844 pub fn find_next(&mut self) {
845 let dx = self.offset.x + self.cursor.x;
846 let dy = self.offset.y + self.cursor.y;
847 for (y, line) in self.lines.iter().enumerate() {
848 let mut o = 0;
849 if y < dy {
850 continue;
851 }
852 if y == dy {
853 o = cmp::min(dx + 1, line.len());
854 }
855 if let Some(i) = line[o..].find(&self.search_query) {
856 let x = o + i;
857 self.cursor.x = x % cols();
858 self.cursor.y = y % rows();
859 self.offset.x = x - self.cursor.x;
860 self.offset.y = y - self.cursor.y;
861 break;
862 }
863 }
864 }
865
866 pub fn open(&mut self) {
867 if let Some(path) = prompt(&mut self.buffer_prompt, "Open: ") {
868 if !path.is_empty() {
869 self.buffer_prompt.history.add(&path);
870 self.open_buffer(&path);
871 }
872 }
873 }
874
875 pub fn open_buffer(&mut self, path: &str) {
876 self.buffers[self.buf] = Buffer::from(&*self);
878
879 let buffer = Buffer::from(path);
881 self.load_buffer(&buffer);
882 self.buf += 1;
883 self.buffers.insert(self.buf, buffer);
884 }
885
886 pub fn next_buffer(&mut self) {
887 self.buffers[self.buf] = Buffer::from(&*self);
888 self.buf = (self.buf + 1) % self.buffers.len();
889 self.load_buffer(&self.buffers[self.buf].clone());
890 }
891
892 pub fn previous_buffer(&mut self) {
893 self.buffers[self.buf] = Buffer::from(&*self);
894 if self.buffers.len() > 1 {
895 if self.buf == 0 {
896 self.buf = self.buffers.len();
897 }
898 self.buf -= 1;
899 }
900 self.load_buffer(&self.buffers[self.buf].clone());
901 }
902
903 pub fn kill_buffer(&mut self) {
904 if self.buffers.len() > 1 {
905 self.previous_buffer();
906 self.buffers.remove((self.buf + 1) % self.buffers.len());
907 }
908 }
909
910 pub fn load_buffer(&mut self, buffer: &Buffer) {
911 self.lines = buffer.lines.clone();
912 self.pathname = buffer.pathname.clone();
913 self.cursor = buffer.cursor.clone();
914 self.offset = buffer.offset.clone();
915 self.highlighted = buffer.highlighted.clone();
916 }
917}
918
919pub fn prompt(prompt: &mut Prompt, label: &str) -> Option<String> {
920 let color = Style::color("black").with_background("silver");
921 let reset = Style::reset();
922
923 print!("\x1b[{};1H", rows() + 1);
925 print!("{}{}", color, " ".repeat(cols()));
926 print!("\x1b[{};1H", rows() + 1);
927 print!("\x1b[?25h"); let res = prompt.input(label);
930 print!("{}", reset);
931 res
932}
933
934pub fn rows() -> usize {
935 api::console::rows() - 1 }
937
938pub fn cols() -> usize {
939 api::console::cols()
940}
941
942fn truncated_line_indicator() -> String {
943 let color = Style::color("black").with_background("silver");
944 let reset = Style::reset();
945 format!("{}>{}", color, reset)
946}
947
948fn help() {
949 let csi_option = Style::color("aqua");
950 let csi_title = Style::color("yellow");
951 let csi_reset = Style::reset();
952 println!(
953 "{}Usage:{} edit {}<options> (<path>[:row[:col]])+{1}",
954 csi_title, csi_reset, csi_option
955 );
956 println!();
957 println!("{}Options:{}", csi_title, csi_reset);
958 println!(
959 " {0}-c{1}, {0}--command <cmd>{1} Execute command",
960 csi_option, csi_reset
961 );
962}
963
964pub fn main(args: &[&str]) -> Result<(), ExitCode> {
965 let mut paths = Vec::new();
966 let mut cmd = "";
967 let mut i = 1;
968 let n = args.len();
969 while i < n {
970 match args[i] {
971 "-h" | "--help" => {
972 help();
973 return Ok(());
974 }
975 "-c" | "--command" => {
976 if i + 1 < n {
977 i += 1;
978 cmd = args[i];
979 } else {
980 error!("Missing command");
981 return Err(ExitCode::UsageError);
982 }
983 }
984 _ => {
985 if args[i].starts_with('-') {
986 error!("Invalid option '{}'", args[i]);
987 return Err(ExitCode::UsageError);
988 } else {
989 paths.push(args[i])
990 }
991 }
992 }
993 i += 1;
994 }
995 if paths.is_empty() {
996 help();
997 return Err(ExitCode::UsageError);
998 }
999
1000 let mut editor = Editor::new(paths[0]);
1001 let n = paths.len();
1002 for i in 1..n {
1003 editor.open_buffer(paths[i]);
1004 }
1005
1006 if !cmd.is_empty() {
1007 for _ in 0..n {
1008 editor.next_buffer();
1009 editor.exec_command(cmd);
1010 for line in &editor.lines {
1011 println!("{}", line);
1012 }
1013 }
1014 return Ok(());
1015 }
1016
1017 editor.run()
1018}