Backspace sends DEL instead of ^H.
[spft.git] / History.cpp
blobd32944135d0f5009e1f2baf4c41dfcbc1ff28a57
1 #include "History.h"
2 #include "Line.h"
3 #include "Colors.h"
4 #include "UTF8.h"
5 #include "Terminal.h"
6 #include "ElasticTabs.h"
7 #include <sstream>
8 #ifdef PRINT_UNIMPLEMENTED_ESCAPES
9 #include <stdio.h>
10 #endif
13 History::History() :
14 cursor_enabled(true), use_bracketed_paste(false),
15 application_cursor_keys(false),
16 terminal(nullptr)
18 at_end_of_line = true;
19 capacity = 10000;
20 first_line = last_line = first_line_index = 0;
21 current_line = 0;
22 current_column = 0;
23 lines = new Line*[capacity];
24 for (int64_t i = 0; i < capacity; ++i)
25 lines[i] = nullptr;
26 lines[0] = new Line();
27 top_margin = 0;
28 bottom_margin = -1;
29 alternate_screen_top_line = -1;
30 current_elastic_tabs = nullptr;
31 g0_character_set = 'B';
32 insert_mode = false;
33 auto_wrap = settings.default_auto_wrap;
34 characters_per_line = 80;
38 History::~History()
40 for (int i = 0; i < capacity; ++i)
41 delete lines[i];
42 delete[] lines;
44 if (current_elastic_tabs)
45 current_elastic_tabs->release();
49 void History::set_lines_on_screen(int new_lines_on_screen)
51 if (is_in_alternate_screen()) {
52 // We assume the "alternate screen" is a "full screen" mode. Make sure
53 // it's got the exact correct number of lines.
54 if (new_lines_on_screen > lines_on_screen) {
55 // Adding lines.
56 int delta = new_lines_on_screen - lines_on_screen;
57 for (int i = delta; i > 0; --i)
58 allocate_new_line();
59 if (bottom_margin >= 0)
60 bottom_margin += delta;
62 else {
63 // (Potentially) deleting lines.
64 last_line -= lines_on_screen - new_lines_on_screen;
65 if (current_line > last_line)
66 current_line = last_line;
70 lines_on_screen = new_lines_on_screen;
74 void History::set_characters_per_line(int new_characters_per_line)
76 characters_per_line = new_characters_per_line;
80 int64_t History::num_lines()
82 return last_line;
86 int History::add_input(const char* input, int length)
88 const char* p = input;
89 const char* end = input + length;
90 char c;
92 while (p < end) {
93 const char* run_start = p;
94 switch (*p++) {
95 case '\x7F': // DEL.
96 case '\x00': // NULL
97 case '\x05': // ENQ
98 case '\x11': // DC1 / XON
99 case '\x12': // DC2
100 case '\x13': // DC3 / XOFF
101 case '\x14': // DC4
102 // Ignore all of these.
103 break;
105 case '\x1B':
106 // ESC.
107 if (p >= end)
108 goto unfinished_run;
109 c = *p++;
110 if (c >= 0x40 && c <= 0x5F) {
111 // "Fe" escape sequence.
112 const char* parse_end;
113 switch (c) {
114 case '[':
115 parse_end = parse_csi(p, end);
116 if (parse_end == nullptr)
117 goto unfinished_run;
118 p = parse_end;
119 break;
120 case 'P':
121 parse_end = parse_dcs(p, end);
122 if (parse_end == nullptr)
123 goto unfinished_run;
124 p = parse_end;
125 break;
126 case ']':
127 parse_end = parse_osc(p, end);
128 if (parse_end == nullptr)
129 goto unfinished_run;
130 p = parse_end;
131 break;
132 case 'X':
133 case '^':
134 case '_':
135 // We ignore these.
136 parse_end = parse_st_string(p, end);
137 if (parse_end == nullptr)
138 goto unfinished_run;
139 p = parse_end;
140 #ifdef PRINT_UNIMPLEMENTED_ESCAPES
141 printf("- Unimplemented escape: %c%.*s\n", c, (int) (parse_end - p), p);
142 #endif
143 break;
144 case 'M':
145 // Reverse Index.
147 if (current_line == calc_screen_top_line() + top_margin)
148 insert_lines(1);
149 else
150 current_line -= 1;
152 break;
153 default:
154 // Unimplemented.
155 // We assume only one character follows the ESC.
156 #ifdef PRINT_UNIMPLEMENTED_ESCAPES
157 printf("- Unimplemented escape: %c.\n", c);
158 #endif
159 break;
162 else if (c >= 0x60 && c <= 0x7E) {
163 // "Fs" escape sequence.
164 // Not currently implemented. We assume only one character
165 // follows the ESC.
167 else if (c >= 0x30 && c <= 0x3F) {
168 // "Fp" escape sequence ("private use").
169 // We assume only one character follows the ESC.
171 else if (c >= 0x20 && c <= 0x2F) {
172 // "nF" escape sequence.
173 while (true) {
174 if (p >= end)
175 goto unfinished_run;
176 c = *p++;
177 if (c >= 0x30 && c <= 0x7E)
178 break;
179 else if (c < 0x20 || c > 0x2F) {
180 // Not valid. We'll just terminate the escape sequence there.
181 break;
184 run_start += 1; // Skip the ESC.
185 switch (run_start[0]) {
186 case '(':
187 if (p > run_start + 1) {
188 g0_character_set = run_start[1];
189 current_style.line_drawing = (g0_character_set == '0');
191 break;
193 default:
194 #ifdef PRINT_UNIMPLEMENTED_ESCAPES
195 printf("- Unimplemented escape: %.*s\n", (int) (p - run_start), run_start);
196 #endif
197 break;
200 break;
202 case '\r':
203 current_column = 0;
204 at_end_of_line = false;
205 break;
207 case '\n':
208 next_line();
209 break;
211 case '\b':
212 if (current_column > 0) {
213 current_column -= 1;
214 at_end_of_line = false;
216 break;
218 case '\t':
220 Line* cur_line = line(current_line);
221 if (at_end_of_line) {
222 cur_line->append_tab(current_style);
223 // Just need to make sure "current_elastic_tabs" gets enough
224 // columns.
225 characters_added();
227 else {
228 cur_line->replace_character_with_tab(current_column, current_style);
229 // Could be splitting a column. Trigger a full recalculation of
230 // the columns.
231 characters_deleted();
234 break;
236 case '\a':
237 // BEL. Ignore.
238 break;
240 default:
241 // Normal run of characters.
242 // TODO: Don't consume partial UTF8 character at end.
243 while (p < end) {
244 unsigned char c = *p;
245 if (c < ' ' || c == '\x7F')
246 break;
247 ++p;
249 // Add to current line.
250 if (g0_character_set == '0') {
251 // DEC Special Character and Line Drawing Set.
252 std::string translated_chars = translate_line_drawing_chars(run_start, p);
253 add_characters(
254 translated_chars.data(),
255 translated_chars.data() + translated_chars.size());
257 else
258 add_characters(run_start, p);
259 break;
261 unfinished_run:
262 return run_start - input;
263 break;
267 return p - input;
271 void History::add_characters(const char* start, const char* end)
273 if (auto_wrap) {
274 int num_new_characters = UTF8::num_characters(start, end - start);
275 while (current_column + num_new_characters > characters_per_line) {
276 int chars_to_add = characters_per_line - current_column;
277 if (at_end_of_line || !insert_mode) {
278 int num_bytes = UTF8::bytes_for_n_characters(start, end - start, chars_to_add);
279 add_to_current_line(start, start + num_bytes);
280 start += num_bytes;
281 next_line();
282 current_column = 0;
283 update_at_end_of_line();
284 num_new_characters -= chars_to_add;
286 else {
287 // In insert mode.
288 // TODO: split the line.
289 // Instead, for now, we just insert the characters.
290 add_to_current_line(start, end);
291 return;
296 if (end > start)
297 add_to_current_line(start, end);
301 void History::add_to_current_line(const char* start, const char* end)
303 Line* cur_line = line(current_line);
304 if (at_end_of_line)
305 cur_line->append_characters(start, end - start, current_style);
306 else if (insert_mode) {
307 cur_line->insert_characters(
308 current_column, start, end - start, current_style);
310 else {
311 cur_line->replace_characters(
312 current_column, start, end - start, current_style);
314 current_column += UTF8::num_characters(start, end - start);
315 characters_added();
316 if (!at_end_of_line)
317 update_at_end_of_line();
321 void History::next_line()
323 bool needs_scroll =
324 (top_margin > 0 && current_line >= last_line) ||
325 (bottom_margin >= 0 &&
326 current_line == calc_screen_top_line() + bottom_margin);
327 if (needs_scroll) {
328 int64_t screen_top = calc_screen_top_line();
329 scroll_up(screen_top + top_margin, screen_top + bottom_margin, 1);
330 update_at_end_of_line();
332 else if (current_line >= last_line)
333 new_line();
334 else {
335 current_line += 1;
336 Line* cur_line = line(current_line);
337 if (cur_line->elastic_tabs)
338 cur_line->elastic_tabs->release();
339 cur_line->elastic_tabs = current_elastic_tabs;
340 if (current_elastic_tabs)
341 current_elastic_tabs->acquire();
342 update_at_end_of_line();
347 void History::new_line()
349 allocate_new_line();
350 current_line = last_line;
351 line(current_line)->elastic_tabs = current_elastic_tabs;
352 if (current_elastic_tabs)
353 current_elastic_tabs->acquire();
354 at_end_of_line = true;
358 void History::allocate_new_line()
360 last_line += 1;
361 if (last_line >= capacity) {
362 // History is full, we'll recycle the previous first line if necessary.
363 // (It might not be necessary because window resizing can delete lines
364 // from the bottom.)
365 int last_line_index = line_index(last_line);
366 Line* line = lines[last_line_index];
367 line->fully_clear();
369 // If we took "first_line", update that.
370 if (last_line_index == first_line_index) {
371 first_line += 1;
372 first_line_index += 1;
373 if (first_line_index >= capacity)
374 first_line_index = 0;
377 else {
378 // We may not have allocated the Line yet.
379 if (lines[last_line] == nullptr)
380 lines[last_line] = new Line();
381 else
382 lines[last_line]->fully_clear();
387 void History::ensure_current_line()
389 while (last_line < current_line)
390 allocate_new_line();
394 void History::ensure_current_column()
396 // If we moved beyond the end of the line, add some spaces.
397 Line* cur_line = line(current_line);
398 int cur_length = cur_line->num_characters();
399 if (current_column > cur_length) {
400 Style default_style;
401 cur_line->append_spaces(current_column - cur_length, default_style);
402 at_end_of_line = true;
404 else if (current_column == cur_length)
405 at_end_of_line = true;
409 void History::update_at_end_of_line()
411 at_end_of_line = (current_column >= line(current_line)->num_characters());
415 const char* History::parse_csi(const char* p, const char* end)
417 // This, like the other parse_*() functions, returns NULL if the escape
418 // sequence is incomplete. Otherwise, it returns a pointer to the byte
419 // after the escape sequence.
421 char c;
422 #if defined(PRINT_UNIMPLEMENTED_ESCAPES) || defined(DUMP_CSIS)
423 const char* escape_start = p;
424 #endif
426 // Arguments.
427 Arguments args;
428 p = args.parse(p, end);
429 if (p == nullptr)
430 return nullptr;
432 // "Intermediate bytes".
433 // We ignore these.
434 while (true) {
435 if (p >= end)
436 return nullptr;
437 c = *p;
438 if (c >= 0x20 && c <= 0x2F)
439 p += 1;
440 else
441 break;
444 // Last character tells us what to do.
445 if (p >= end)
446 return nullptr;
447 c = *p++;
448 if (args.private_code_type == 0)
449 switch (c) {
450 case '@':
451 // Insert blank characters (ICH).
453 int num_blanks = args.args[0] ? args.args[0] : 1;
454 std::string blanks(num_blanks, ' ');
455 line(current_line)->insert_characters(
456 current_column, blanks.data(), num_blanks, current_style);
457 at_end_of_line = false;
458 characters_added();
460 break;
462 case 'A':
463 case 'F':
464 // Cursor up (CUU) / Cursor Prev Line (CPL).
466 current_line -= args.args[0] ? args.args[0] : 1;
467 int64_t screen_top_line = calc_screen_top_line();
468 if (is_in_alternate_screen()) {
469 if (current_line < screen_top_line)
470 current_line = screen_top_line;
472 else {
473 if (current_line < first_line)
474 current_line = first_line;
476 if (c == 'F')
477 current_column = 0;
478 update_at_end_of_line();
480 break;
482 case 'B':
483 case 'E':
484 case 'e':
485 // Cursor down (CUD) / Cursor Next Line (CNL) / Line Position Relative
486 // (VPR).
488 current_line += args.args[0] ? args.args[0] : 1;
489 int64_t screen_bottom_line = calc_screen_bottom_line();
490 if (current_line > screen_bottom_line)
491 current_line = screen_bottom_line;
492 if (c == 'E')
493 current_column = 0;
494 ensure_current_line();
495 update_at_end_of_line();
497 break;
499 case 'C':
500 // Cursor forward.
502 current_column += args.args[0] ? args.args[0] : 1;
503 ensure_current_column();
505 break;
507 case 'D':
508 // Cursor back.
509 current_column -= args.args[0] ? args.args[0] : 1;
510 if (current_column < 0)
511 current_column = 0;
512 at_end_of_line = false;
513 break;
515 case 'G':
516 // Cursor Character Absolute (CHA).
517 current_column = args.args[0] ? args.args[0] - 1 : 0;
518 ensure_current_column();
519 update_at_end_of_line();
520 break;
522 case 'H':
523 // Cursor Position.
524 current_line =
525 calc_screen_top_line() + (args.args[0] ? args.args[0] - 1 : 0);
526 current_column = args.args[1] ? args.args[1] - 1 : 0;
527 ensure_current_line();
528 ensure_current_column();
529 update_at_end_of_line();
530 break;
532 case 'J':
533 // Erase in Display.
534 if (args.args[0] == 0)
535 clear_to_end_of_screen();
536 else if (args.args[0] == 1)
537 clear_to_beginning_of_screen();
538 else if (args.args[0] == 2 || args.args[0] == 3)
539 clear_screen();
540 update_at_end_of_line();
541 break;
543 case 'K':
544 // Erase in Line.
546 Line* cur_line = line(current_line);
547 if (args.args[0] == 0) {
548 cur_line->clear_to_end_from(current_column);
549 at_end_of_line = true;
551 else if (args.args[0] == 1) {
552 cur_line->clear_from_beginning_to(current_column);
553 cur_line->prepend_spaces(current_column, current_style);
554 update_at_end_of_line();
556 else if (args.args[0] == 2) {
557 cur_line->clear();
558 if (current_column > 0)
559 cur_line->prepend_spaces(current_column, current_style);
560 at_end_of_line = true;
562 characters_deleted();
564 break;
566 case 'L':
567 // Insert blank lines (IL).
568 insert_lines(args.args[0] ? args.args[0] : 1);
569 break;
571 case 'M':
572 // Delete lines (DL).
573 delete_lines(args.args[0] ? args.args[0] : 1);
574 break;
576 case 'P':
577 // Delete Character (DCH).
578 line(current_line)->delete_characters(current_column, args.args[0] ? args.args[0] : 1);
579 update_at_end_of_line();
580 characters_deleted();
581 break;
583 case 'S':
584 // Scroll up (SU).
585 // This scrolls the whole screen (or at least the scrolling region).
587 int effective_bottom_margin = bottom_margin;
588 if (effective_bottom_margin < 0)
589 effective_bottom_margin = lines_on_screen - 1;
590 int64_t top_line = calc_screen_top_line();
591 scroll_up(
592 top_line + top_margin, top_line + effective_bottom_margin,
593 args.args[0] ? args.args[0] : 1);
595 break;
597 case 'T':
598 // Scroll down (SD).
599 scroll_down(
600 args.args[0] ? args.args[0] : 1,
601 calc_screen_top_line() + top_margin);
602 break;
604 case 'X':
605 // Erase Character(s) (ECH).
606 // This appears to mean replacing them with spaces, unlike DCH which
607 // actually deletes characters.
609 int num_blanks = args.args[0] ? args.args[0] : 1;
610 std::string blanks(num_blanks, ' ');
611 line(current_line)->replace_characters(
612 current_column, blanks.data(), num_blanks, current_style);
613 at_end_of_line = false;
614 characters_deleted();
616 break;
618 case 'd':
619 // Line Position Absolute (VPA).
621 current_line =
622 calc_screen_top_line() + (args.args[0] ? args.args[0] - 1 : 0);
623 int64_t top_line = calc_screen_top_line();
624 if (current_line < top_line)
625 current_line = top_line;
626 else {
627 int64_t bottom_line = calc_screen_bottom_line();
628 if (current_line > bottom_line)
629 current_line = bottom_line;
631 ensure_current_line();
632 ensure_current_column();
633 update_at_end_of_line();
635 break;
637 case 'h':
638 switch (args.args[0]) {
639 case 4:
640 insert_mode = true;
641 break;
642 default:
643 goto unimplemented;
645 break;
647 case 'l':
648 switch (args.args[0]) {
649 case 4:
650 insert_mode = false;
651 break;
652 default:
653 goto unimplemented;
655 break;
657 case 'm':
658 // Select Graphic Rendition (SGR).
659 if (args.num_args == 0) {
660 // Default to at least one arg (which will have the default value
661 // of zero).
662 args.num_args = 1;
664 for (int which_arg = 0; which_arg < args.num_args; ++which_arg) {
665 switch (args.args[which_arg]) {
666 case 0:
667 current_style.reset();
668 if (g0_character_set == '0')
669 current_style.line_drawing = true;
670 break;
671 case 1:
672 current_style.bold = true;
673 break;
674 case 3:
675 current_style.italic = true;
676 break;
677 case 4:
678 current_style.underlined = true;
679 break;
680 case 7:
681 current_style.inverse = true;
682 break;
683 case 8:
684 current_style.invisible = true;
685 break;
686 case 9:
687 current_style.crossed_out = true;
688 break;
689 case 21:
690 current_style.doubly_underlined = true;
691 break;
692 case 22:
693 current_style.bold = false;
694 break;
695 case 23:
696 current_style.italic = false;
697 break;
698 case 24:
699 current_style.underlined = current_style.doubly_underlined = false;
700 break;
701 case 27:
702 current_style.inverse = false;
703 break;
704 case 28:
705 current_style.invisible = false;
706 break;
707 case 29:
708 current_style.crossed_out = false;
709 break;
710 case 30: case 31: case 32: case 33:
711 case 34: case 35: case 36: case 37:
712 // Set foreground color.
713 current_style.foreground_color = args.args[which_arg] - 30;
714 break;
715 case 90: case 91: case 92: case 93:
716 case 94: case 95: case 96: case 97:
717 // Set high-intensity foreground color.
718 current_style.foreground_color = args.args[which_arg] - 90 + 8;
719 break;
720 case 38:
721 // Set foreground color.
722 which_arg += 1;
723 if (args.args[which_arg] == 5) {
724 which_arg += 1;
725 current_style.foreground_color = args.args[which_arg];
727 else if (args.args[which_arg] == 2) {
728 current_style.foreground_color =
729 Colors::true_color_bit |
730 args.args[which_arg + 1] << 16 |
731 args.args[which_arg + 2] << 8 |
732 args.args[which_arg + 3];
733 which_arg += 3;
735 break;
736 case 40: case 41: case 42: case 43:
737 case 44: case 45: case 46: case 47:
738 // Set background color.
739 current_style.background_color = args.args[which_arg] - 40;
740 break;
741 case 100: case 101: case 102: case 103:
742 case 104: case 105: case 106: case 107:
743 // Set high-intensity background color.
744 current_style.background_color = args.args[which_arg] - 100 + 8;
745 break;
746 case 48:
747 // Set background color.
748 which_arg += 1;
749 if (args.args[which_arg] == 5) {
750 which_arg += 1;
751 current_style.background_color = args.args[which_arg];
753 else if (args.args[which_arg] == 2) {
754 current_style.background_color =
755 Colors::true_color_bit |
756 args.args[which_arg + 1] << 16 |
757 args.args[which_arg + 2] << 8 |
758 args.args[which_arg + 3];
759 which_arg += 3;
761 break;
764 break;
766 case 'n':
767 if (args.args[0] == 6) {
768 // Device Status Report (DSR).
769 char report[32];
770 sprintf(
771 report, "\x1B[%d;%dR",
772 (int) (current_line - calc_screen_top_line() + 1),
773 current_column + 1);
774 terminal->send(report);
776 else
777 goto unimplemented;
778 break;
780 case 'r':
781 // Set scroll margins (DECSTBM).
782 top_margin = args.args[0] ? args.args[0] - 1 : 0;
783 bottom_margin = args.args[1] ? args.args[1] - 1 : -1;
784 if (top_margin >= bottom_margin) {
785 // Invalid; reset them.
786 top_margin = 0;
787 bottom_margin = -1;
789 break;
791 default:
792 unimplemented:
793 // This is either unimplemented or invalid.
794 #ifdef PRINT_UNIMPLEMENTED_ESCAPES
795 printf("- Unimplemented CSI: %.*s\n", (int) (p - escape_start), escape_start);
796 #endif
797 break;
800 // Private codes.
801 else if (args.private_code_type == '?') {
802 switch (c) {
803 case 'h':
804 // Set Mode (SM).
805 set_private_modes(&args, true);
806 break;
808 case 'l':
809 // Reset Mode (RM).
810 set_private_modes(&args, false);
811 break;
813 default:
814 // This is either unimplemented or invalid.
815 #ifdef PRINT_UNIMPLEMENTED_ESCAPES
816 printf("- Unimplemented CSI: %.*s\n", (int) (p - escape_start), escape_start);
817 #endif
818 break;
822 #ifdef DUMP_CSIS
823 printf("- %.*s\n", (int) (p - escape_start), escape_start);
824 #endif
825 return p;
829 const char* History::parse_dcs(const char* p, const char* end)
831 const char* sequence_end = parse_st_string(p, end);
832 if (sequence_end == nullptr)
833 return nullptr;
835 //*** TODO
836 #ifdef PRINT_UNIMPLEMENTED_ESCAPES
837 printf("- Unimplemented DCS: %.*s\n", (int) (sequence_end - p), p);
838 #endif
839 return sequence_end;
843 const char* History::parse_osc(const char* p, const char* end)
845 const char* sequence_end = parse_st_string(p, end, true);
846 if (sequence_end == nullptr)
847 return nullptr;
849 //*** TODO
850 #ifdef PRINT_UNIMPLEMENTED_ESCAPES
851 printf("- Unimplemented OSC: %.*s\n", (int) (sequence_end - p), p);
852 #endif
853 return sequence_end;
857 const char* History::parse_st_string(const char* p, const char* end, bool can_end_with_bel)
859 while (p < end) {
860 char c = *p++;
861 if (c == '\x1B') {
862 if (p >= end)
863 return nullptr;
864 if (*p++ == '\\') {
865 // Got ST; string is complete.
866 return p;
869 else if (c == '\a' && can_end_with_bel)
870 return p;
873 // Incomplete string.
874 return nullptr;
878 void History::set_private_modes(Arguments* args, bool set)
880 for (int i = 0; i < args->num_args; ++i) {
881 switch (args->args[i]) {
882 case 1:
883 // DECCKM.
884 application_cursor_keys = set;
885 break;
887 case 7:
888 auto_wrap = set;
889 break;
891 case 12:
892 // Cursor blinking.
893 // We don't support this currently, so we're ignoring it.
894 break;
896 case 25:
897 // Show cursor (DECTCEM).
898 cursor_enabled = set;
899 break;
901 case 1049:
902 // Alternate screen.
903 if (set)
904 enter_alternate_screen();
905 else
906 exit_alternate_screen();
907 break;
909 case 2004:
910 use_bracketed_paste = set;
911 break;
913 case 5001:
914 if (set)
915 start_elastic_tabs();
916 else
917 end_elastic_tabs();
918 break;
919 case 5002:
920 if (set)
921 start_elastic_tabs(args->args[++i]);
922 else
923 end_elastic_tabs(args->args[++i] == 1);
924 break;
926 default:
927 printf("- Unimplemented set mode: ?%d\n", args->args[i]);
928 break;
934 int64_t History::calc_screen_top_line()
936 int screen_top_line = last_line - lines_on_screen + 1;
937 if (screen_top_line < 0)
938 screen_top_line = 0;
939 return screen_top_line;
943 int64_t History::calc_screen_bottom_line()
945 // Usually "last_line", except early on...
946 if (last_line < lines_on_screen)
947 return lines_on_screen - 1;
948 return last_line;
952 void History::clear_to_end_of_screen()
954 line(current_line)->clear_to_end_from(current_column);
955 for (int64_t which_line = current_line + 1; which_line <= last_line; ++which_line)
956 line(which_line)->clear();
960 void History::clear_to_beginning_of_screen()
962 Line* cur_line = line(current_line);
963 cur_line->clear_from_beginning_to(current_column);
964 cur_line->prepend_spaces(current_column, current_style);
965 for (int64_t which_line = calc_screen_top_line(); which_line < current_line; ++which_line)
966 line(which_line)->clear();
970 void History::clear_screen()
972 for (int64_t which_line = calc_screen_top_line(); which_line <= last_line; ++which_line)
973 line(which_line)->clear();
977 void History::insert_lines(int num_lines)
979 scroll_down(num_lines, current_line);
983 void History::scroll_down(int num_lines, int64_t top_scroll_line)
985 int64_t bottom_scroll_line =
986 bottom_margin < 0 ?
987 calc_screen_bottom_line() :
988 calc_screen_top_line() + bottom_margin;
989 int max_scroll = bottom_scroll_line - top_scroll_line + 1;
990 if (num_lines > max_scroll)
991 num_lines = max_scroll;
993 // Move and erase the lines.
994 for (int dest_line = bottom_scroll_line; dest_line >= top_scroll_line; --dest_line) {
995 int dest_index = line_index(dest_line);
996 int64_t src_line = dest_line - num_lines;
997 if (src_line < top_scroll_line) {
998 if (lines[dest_index])
999 lines[dest_index]->clear();
1001 else {
1002 int src_index = line_index(src_line);
1003 delete lines[dest_index];
1004 lines[dest_index] = lines[src_index];
1005 lines[src_index] = new Line();
1009 update_at_end_of_line();
1013 void History::delete_lines(int num_lines)
1015 if (is_in_alternate_screen() || bottom_margin >= 0) {
1016 // Scrolling a particular area.
1017 int effective_bottom_margin = bottom_margin;
1018 if (effective_bottom_margin < 0) {
1019 // Must be in alternate screen.
1020 effective_bottom_margin = lines_on_screen - 1;
1022 scroll_up(
1023 current_line, calc_screen_top_line() + effective_bottom_margin,
1024 num_lines);
1026 else {
1027 scroll_up(current_line, last_line, num_lines);
1028 last_line -= num_lines;
1031 update_at_end_of_line();
1035 void History::scroll_up(int64_t top_scroll_line, int64_t bottom_scroll_line, int num_lines)
1037 // Move and erase the lines.
1038 for (int dest_line = top_scroll_line; dest_line <= bottom_scroll_line; ++dest_line) {
1039 int dest_index = line_index(dest_line);
1040 int64_t src_line = dest_line + num_lines;
1041 if (src_line > bottom_scroll_line) {
1042 if (lines[dest_index])
1043 lines[dest_index]->clear();
1045 else {
1046 int src_index = line_index(src_line);
1047 delete lines[dest_index];
1048 lines[dest_index] = lines[src_index];
1049 lines[src_index] = new Line();
1055 void History::enter_alternate_screen()
1057 if (alternate_screen_top_line >= 0)
1058 return;
1060 // mlterm replaces the bottom lines with the alternate screen, but we add
1061 // new lines instead. This allows you to see the entire main screen when
1062 // scrolling back. We don't save the alternate screen after exiting it.
1064 // Save state.
1065 main_screen_current_line = current_line;
1066 main_screen_current_column = current_column;
1067 main_screen_top_margin = top_margin;
1068 main_screen_bottom_margin = bottom_margin;
1070 // Create the new lines.
1071 alternate_screen_top_line = last_line + 1;
1072 current_column = 0;
1073 for (int i = 0; i < lines_on_screen; ++i)
1074 allocate_new_line();
1076 // Reset state.
1077 current_line = alternate_screen_top_line;
1078 current_column = 0;
1079 top_margin = 0;
1080 bottom_margin = -1;
1081 update_at_end_of_line();
1085 void History::exit_alternate_screen()
1087 if (alternate_screen_top_line < 0)
1088 return;
1090 // Delete the last screen's lines.
1091 last_line = alternate_screen_top_line - 1;
1093 // Restore state.
1094 current_line = main_screen_current_line;
1095 current_column = main_screen_current_column;
1096 top_margin = main_screen_top_margin;
1097 bottom_margin = main_screen_bottom_margin;
1098 alternate_screen_top_line = -1;
1099 update_at_end_of_line();
1104 void History::start_elastic_tabs(int num_right_columns)
1106 end_elastic_tabs();
1108 current_elastic_tabs = new ElasticTabs(num_right_columns);
1109 current_elastic_tabs->acquire();
1110 Line* cur_line = line(current_line);
1111 if (cur_line->elastic_tabs)
1112 cur_line->elastic_tabs->release();
1113 cur_line->elastic_tabs = current_elastic_tabs;
1114 current_elastic_tabs->acquire();
1118 void History::end_elastic_tabs(bool include_current_line)
1120 if (current_elastic_tabs == nullptr)
1121 return;
1123 // The current cursor line will not be part of the group of elastic tabbed
1124 // lines (unless include_current_line is true).
1125 if (!include_current_line) {
1126 Line* cur_line = line(current_line);
1127 if (cur_line->elastic_tabs == current_elastic_tabs) {
1128 current_elastic_tabs->release();
1129 cur_line->elastic_tabs = nullptr;
1132 current_elastic_tabs->release();
1133 current_elastic_tabs = nullptr;
1137 void History::characters_added()
1139 if (current_elastic_tabs == nullptr)
1140 return;
1142 current_elastic_tabs->is_dirty = true;
1143 if (current_line < current_elastic_tabs->first_dirty_line)
1144 current_elastic_tabs->first_dirty_line = current_line;
1148 void History::characters_deleted()
1150 if (current_elastic_tabs == nullptr)
1151 return;
1153 current_elastic_tabs->is_dirty = true;
1154 current_elastic_tabs->first_dirty_line = 0;
1158 const char* History::Arguments::parse(const char* p, const char* end)
1160 char c;
1162 // Clear.
1163 num_args = 0;
1164 for (int i = 0; i < max_args; ++i)
1165 args[i] = 0;
1166 private_code_type = 0;
1168 // Parse.
1169 bool arg_started = false;
1170 while (true) {
1171 if (p >= end)
1172 return nullptr;
1173 c = *p;
1174 if (c >= '0' && c <= '9') {
1175 if (num_args < max_args) {
1176 args[num_args] *= 10;
1177 args[num_args] += c - '0';
1178 arg_started = true;
1180 p += 1;
1182 else if (c == ';') {
1183 num_args += 1;
1184 arg_started = false;
1185 p += 1;
1187 else if (c == '?' || c == '<' || c == '=' || c == '>') {
1188 private_code_type = c;
1189 p += 1;
1191 else if (c >= 0x30 && c <= 0x3F) {
1192 // Valid, but we ignore it.
1193 p += 1;
1195 else
1196 break;
1198 if (arg_started)
1199 num_args += 1;
1201 return p;
1205 std::string History::translate_line_drawing_chars(const char* start, const char* end)
1207 static const char* translation[] = {
1208 "\u2518", "\u2510", "\u250C", "\u2514", "\u253C", "",
1209 "", "\u2500", "", "", "\u251C", "\u2524", "\u2534", "\u252C", "\u2502",
1212 std::stringstream result;
1213 for (const char* p = start; p < end; ++p) {
1214 char c = *p;
1215 if (c >= 0x6A && c <= 0x78)
1216 result << translation[c - 0x6A];
1217 else
1218 result << c;
1220 return result.str();