diff options
| author | erco77 <erco@seriss.com> | 2023-11-14 07:01:52 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-11-14 07:01:52 -0800 |
| commit | 6842a43a3170c6f7a852186d5688baebdac16e2c (patch) | |
| tree | 04493580bf8fc798858720c7ab7ffecedeb9ee3e | |
| parent | 83f6336f3b024f4a7c55a7499d036f03d946c1b9 (diff) | |
Fl_Terminal widget (#800)
Pull Fl_Terminal widget from Greg's fork
35 files changed, 7818 insertions, 217 deletions
diff --git a/FL/Fl_Terminal.H b/FL/Fl_Terminal.H new file mode 100644 index 000000000..4b7039f0c --- /dev/null +++ b/FL/Fl_Terminal.H @@ -0,0 +1,1013 @@ +// +// Fl_Terminal.cxx - A terminal widget for Fast Light Tool Kit (FLTK). +// +// Copyright 2022 by Greg Ercolano. +// Copyright 2023 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +/** \file + Fl_Terminal widget. +*/ + +#ifndef Fl_Terminal_H +#define Fl_Terminal_H + +#include <FL/Fl.H> +#include <FL/Fl_Window.H> +#include <FL/Fl_Group.H> +#include <FL/Fl_Scrollbar.H> +#include <FL/Fl_Rect.H> + +/** \class Fl_Terminal + + \brief Terminal widget supporting Unicode/utf-8, ANSI/xterm escape codes with full RGB color control. + + \section Fl_Terminal + + \image html Fl_Terminal-demo.png "Fl_Terminal widget showing a linux manual page" + \image latex Fl_Terminal-demo.png "Fl_Terminal widget showing a linux manual page" width=6cm + + Fl_Terminal is an output-only text widget supporting ASCII and UTF-8/Unicode. + It supports most terminal text features, such as most VT100/xterm style escape sequences + (see \ref Fl_Terminal_escape_codes), text colors/attributes, scrollback history, mouse selection, etc. + + It is recommended that accessing features such as setting text colors and cursor positioning + is best done with ANSI/XTERM escape sequences. But if one sets ansi(false) then this is + not possible. Many commonly used API functions are public, such as textfgcolor() for setting + text colors. Others, such as cursor_up() are protected to prevent common misuse, and are + available only to subclasses. + + For applications that need input support, the widget can be subclassed to provide + keyboard input, and advanced features like pseudo ttys, termio, serial port I/O, etc., + as such features are beyond the scope of FLTK. + + \subsection Fl_Terminal_Examples Examples + + \par + \code + // + // Fl_Terminal: Simple Use + // + Fl_Terminal *tty = new Fl_Terminal(...); + : + tty->append("Hello world.\n"); // simple strings + tty->append("\033[31mThis text is red.\033[0m\n"); // colored text + tty->append("\033[32mThis text is green.\033[0m\n"); + tty->printf("The value of x is %.02f\n", x); // printf() formatting + \endcode + \par + There are also public methods for doing what most "\033[" escape codes do, + so that if ansi(bool) is set to "false", one can still change text colors + or clear the screen via application control, e.g. + \par + \code + tty->home(); // home the cursor + tty->clear_screen(); // clear the screen + tty->textfgcolor(0xff000000); // change the text color to RED + tty->textbgcolor(0x0000ff00); // change the background color to BLUE + // ├┘├┘├┘ + // R G B + \endcode + \par + When creating the widget, the width/height determine the default column + and row count for the terminal's display based on the current font size. + The column width determines where text will wrap. + \par + You can specify wider column sizes than the screen using + display_columns(colwidth). When this value is larger than + the widget's width, text will wrap off-screen, and can be revealed by + resizing the widget wider. + + \subsection Fl_Terminal_Writing Writing To Terminal From Applications + + \par + An application needing terminal output as part of its user interface + can instance Fl_Terminal, and write text strings with: + \par + - append() to append strings + - printf() to append formatted strings + \par + Single character output can be done with: + \par + - print_char() to print a single ASCII/UTF-8 char at the cursor + - putchar() to put single ASCII/UTF-8 char at an x,y position + \par + + \subsection Fl_Terminal_Attributes Text Attributes + \par + The terminal's text supports these attributes: + \par + - Italic - italicized text: <TT>\\033[3m</TT> + - Bold - brighter/thicker text: <TT>\\033[1m</TT> + - Dim - lower brightness text: <TT>\\033[2m</TT> + - Underline - text that is underlined: <TT>\\033[4m</TT> + - Strikeout - text that has a line through the text: <TT>\\033[9m</TT> + - Inverse - text whose background and foreground colors are swapped: <TT>\\033[7m</TT> + - Normal - normal text: <TT>\\033[0m</TT> + \par + \image html Fl_Terminal-utf8-demo.png "Fl_Terminal screen" + \image latex Fl_Terminal-utf8-demo.png "Fl_Terminal screen" width=6cm + + \subsection Fl_Terminal_Colors Text and Background Colors + + \par + There's at least two ways to specify colors for text and background colors: + \par + - 3 bit / 8 Color Values + - Full 24 bit R/G/B colors + \par + Example of 3 bit colors: + \image html Fl_Terminal-3bit-colors.png "Fl_Terminal 3 bit colors" + \image latex Fl_Terminal-3bit-colors.png "Fl_Terminal 3 bit colors" width=6cm + \par + Example application source code using 3 bit colors: + \code + // + // Text colors + // + tty->append("\033[31m Red text.\033[0m\n"); // Print red text.. + tty->append("\033[32m Green text.\033[0m\n"); + : + tty->append("\033[36m Cyan text.\033[0m\n"); + tty->append("\033[37m White text.\033[0m\n"); + // + // Background colors + // + tty->append("\033[41m Red Background.\033[0m\n"); // background will be red + tty->append("\033[42m Green Background.\033[0m\n"); + : + tty->append("\033[46m Cyan Background.\033[0m\n"); + tty->append("\033[47m White Background.\033[0m\n"); + \endcode + \par + Example of 24 bit colors: + \image html Fl_Terminal-24bit-colors.png "Fl_Terminal 24 bit colors" + \image latex Fl_Terminal-24bit-colors.png "Fl_Terminal 24 bit colors" width=6cm + \par + Example application source code using 24 bit colors: + \code + // + // 24 bit Text Color + // + tty->append("\033[38;2;0;0;255m Text is BLUE.\033[0m\n"); // RGB: R=0, G=0, B=255 + tty->append("\033[38;2;255;0;0m Text is RED.\033[0m\n"); // RGB: R=255, G=0, B=0 + tty->append("\033[38;2;127;64;0m Text is DARK ORANGE.\033[0m\n"); // RGB: R=127, G=64, B=0 + // + // 24 bit Background Color + // + tty->append("\033[48;2;0;0;255m Background is BLUE.\033[0m\n"); // RGB: R=0, G=0, B=255 + tty->append("\033[48;2;255;0;0m Background is RED.\033[0m\n"); // RGB: R=255, G=0, B=0 + tty->append("\033[48;2;127;64;0m Background is DARK ORANGE.\033[0m\n"); // RGB: R=127, G=64, B=0 + \endcode + \par + For more on the ANSI escape codes, see \ref Fl_Terminal_escape_codes. + + \subsection Fl_Terminal_Features Features + + \par + Most standard terminal behaviors are supported, e.g. + \par + - ASCII + UTF-8/Unicode + - scrollback history management + - mouse selection + copy/paste (^C, ^A) + - autoscroll during selection + \par + Most popular ANSI/DEC VT100/Xterm escape sequences are supported (see \ref Fl_Terminal_escape_codes), including: + - per-character colors for text and background + - per-character text attributes: bold/dim, underline, strikeout + - scrolling up/down + - character insert/delete for characters/rows/screen + - clearing characters/rows/screen + \par + Does not (yet) support: + - programmable regions (scroll regions and attribute blocks) + - dynamic line wrap (where resizing display dynamically re-wraps long lines) + \par + Will likely never implement as part of this widget: + - pty/termio management (such features should be _subclassed_) + - Different per-character font family + sizes (font family/size is global only) + - variable width fonts + \par + Regarding the font family+size; the way the terminal is currently designed, + the font family and size must not vary within text; rows have to be consistent + height. Varying widths are tricky too, esp. when it comes to moving the cursor + up/down within a column; varying *widths* are supported (due to Unicode characters + sometimes being "wide", but not heights. + + \subsection Fl_Terminal_Margins Margins + + \par + The margins define the amount of space (in pixels) around the outside of the + text display area, the space between the widget's inner edge (inside the box()) + and the text display area's outer edge. The margins can be inspected and changed + with the margin_left(), margin_right(), margin_top() and margin_bottom() methods. + \par + \code +· + TERMINAL WIDGET (Fl_Terminal) + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━┓ + ┃ ↑ margin_top ┃ ┃ + ┃ TEXT DISPLAY AREA ↓ ┃ ┃ + ┃ ┌──────────────────────────────────────────────────────┐ ┃ ┃ + ┃ │ ↑ │ ┃ S ┃ + ┃ │ │ │ ┃ c ┃ + ┃ │ display_rows │ ┃ r ┃ + ┃ │ │ │ ┃ o ┃ + ┃ │ │ │ ┃ l ┃ + ┃ │ │ │ ┃ l ┃ + ┃ │◄───display_columns────────┼─────────────────────────►│ ┃ ┃ + ┃ │ │ │ ┃ B ┃ + ┃ │ │ │ ┃ a ┃ + ┃ │ │ │ ┃ r ┃ + ┃ │ │ │ ┃ ┃ + ┃ │ │ │ ┃ ┃ + ┃◄──┬──►│ ↓ │◄──┬──►┃ ┃ + ┃ │ └──────────────────────────────────────────────────────┘ │ ┃ ┃ + ┃ margin_left ↑ margin_right ┃ ┃ + ┃ ↓ margin_bottom ┃ ┃ + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━┛ + Fl_Terminal Margins +· + \endcode + + \subsection Fl_Terminal_Caveats Caveats + + \par + - This widget is not a full terminal emulator; it does not do stdio redirection, + pseudo ttys/termios/character cooking, keyboard input processing, full + curses(3) support. However, such features CAN be implemented with subclassing. + \par + - The printf() and vprintf() functions are limited to strings no longer than + 1024 characters (including NULL). For printing longer strings, use append() + which has no string limits. +**/ + +class Fl_Terminal : public Fl_Group { +public: + ////////////////////////////////////// + ////// Fl_Terminal Public Enums ////// + ////////////////////////////////////// + + /** + \enum RedrawStyle + Determines when Fl_Terminal calls redraw() if new text is added. + RATE_LIMITED is the recommended setting, using redraw_rate(float) to determine + the maximum rate of redraws. + \see redraw_style(), redraw_rate() + */ + enum RedrawStyle { + NO_REDRAW=0, ///< app must call redraw() as needed to update text to screen + RATE_LIMITED, ///< timer controlled redraws. (DEFAULT) + PER_WRITE ///< redraw triggered after *every* append() / printf() / etc. operation + }; + + /** + \enum Attrib + Bits for the per-character attributes, which control text features + such as italic, bold, underlined text, etc. + + NOTE: enum names with a leading underbar must not be used, and are + reserved for future use. + */ + enum Attrib { + NORMAL = 0x00, ///< all attributes off + BOLD = 0x01, ///< bold text: uses bold font, color brighter than normal + DIM = 0x02, ///< dim text; color slightly darker than normal + ITALIC = 0x04, ///< italic font text + UNDERLINE = 0x08, ///< underlined text + _RESERVED_1 = 0x10, ///< <EM>(reserved for internal future use)</EM> + INVERSE = 0x20, ///< inverse text; fg/bg color are swapped + _RESERVED_2 = 0x40, ///< <EM>(reserved for internal future use)</EM> + STRIKEOUT = 0x80 ///< strikeout text + }; + + /** + \enum CharFlags + Per-character 8 bit flags (uchar) used to manage special states for characters. + */ + enum CharFlags { + FG_XTERM = 0x01, ///< this char's fg color is an XTERM color; can be affected by Dim+Bold + BG_XTERM = 0x02, ///< this char's bg color is an XTERM color; can be affected by Dim+Bold + EOL = 0x04, ///< this char at end of line, used for line wrap during screen resizing + RESV_A = 0x08, + RESV_B = 0x10, + RESV_C = 0x20, + RESV_D = 0x40, + RESV_E = 0x80, + COLORMASK = (FG_XTERM | BG_XTERM) + }; + +protected: + // Margin Class //////////////////////////////////////////// + // + // Class to manage the terminal's margins + // + class Margin { + int left_, right_, top_, bottom_; + public: + Margin(void) { left_ = right_ = top_ = bottom_ = 3; } + int left(void) const { return left_; } + int right(void) const { return right_; } + int top(void) const { return top_; } + int bottom(void) const { return bottom_; } + void left(int val) { left_ = val; } + void right(int val) { right_ = val; } + void top(int val) { top_ = val; } + void bottom(int val) { bottom_ = val; } + }; + + // CharStyle Class //////////////////////////////////////////// + // + // Class to manage the terminal's character style + // This includes the font, color, and some cached internal + // info for optimized drawing speed. + // + class CharStyle { + uchar attrib_; // bold, underline.. + uchar flags_; // CharFlags + Fl_Color fgcolor_; // foreground color for text + Fl_Color bgcolor_; // background color for text + Fl_Color defaultfgcolor_; // default fg color used by ESC[0m + Fl_Color defaultbgcolor_; // default bg color used by ESC[0m + Fl_Font fontface_; // font face + Fl_Fontsize fontsize_; // font size + int fontheight_; // font height (in pixels) + int fontdescent_; // font descent (pixels below font baseline) + int charwidth_; // width of a fixed width ASCII character + public: + CharStyle(void); + Fl_Color fgcolor(void) const; + Fl_Color bgcolor(void) const; + Fl_Color defaultfgcolor(void) const { return defaultfgcolor_; } + Fl_Color defaultbgcolor(void) const { return defaultbgcolor_; } + uchar attrib(void) const { return attrib_; } + Fl_Font fontface(void) const { return fontface_; } + Fl_Fontsize fontsize(void) const { return fontsize_; } + int fontheight(void) const { return fontheight_; } + int fontdescent(void) const { return fontdescent_; } + int charwidth(void) const { return charwidth_; } + uchar colorbits_only(uchar inflags) const; + void attrib(uchar val) { attrib_ = val; } + void set_flag(uchar val) { flags_ |= val; } + void clr_flag(uchar val) { flags_ &= ~val; } + void fgcolor_uchar(uchar val); + void bgcolor_uchar(uchar val); + void fgcolor(int r,int g,int b) { fgcolor_ = (r<<24) | (g<<16) | (b<<8); clr_flag(FG_XTERM); } + void bgcolor(int r,int g,int b) { bgcolor_ = (r<<24) | (g<<16) | (b<<8); clr_flag(BG_XTERM); } + void fgcolor(Fl_Color val) { fgcolor_ = val; clr_flag(FG_XTERM); } + void bgcolor(Fl_Color val) { bgcolor_ = val; clr_flag(BG_XTERM); } + void defaultfgcolor(Fl_Color val) { defaultfgcolor_ = val; } + void defaultbgcolor(Fl_Color val) { defaultbgcolor_ = val; } + void fontface(Fl_Font val) { fontface_ = val; update(); } + void fontsize(Fl_Fontsize val) { fontsize_ = val; update(); } + void update(void); + // SGR MODES: Set Graphics Rendition + void sgr_reset(void) { // e.g. ESC[0m + attrib(Fl_Terminal::NORMAL); + fgcolor(defaultfgcolor_); + bgcolor(defaultbgcolor_); + } + int onoff(bool flag, Attrib a) { return (flag ? (attrib_ | a) : (attrib_ & ~a)); } + void sgr_bold(bool val) { attrib_ = onoff(val, Fl_Terminal::BOLD); } // e.g. ESC[1m + void sgr_dim(bool val) { attrib_ = onoff(val, Fl_Terminal::DIM); } // e.g. ESC[2m + void sgr_italic(bool val) { attrib_ = onoff(val, Fl_Terminal::ITALIC); } // e.g. ESC[3m + void sgr_underline(bool val) { attrib_ = onoff(val, Fl_Terminal::UNDERLINE); } // e.g. ESC[3m + void sgr_dbl_under(bool val) { attrib_ = onoff(val, Fl_Terminal::UNDERLINE); } // e.g. ESC[21m (TODO!) + void sgr_blink(bool val) { /* NOT IMPLEMENTED */ } // e.g. ESC[5m + void sgr_inverse(bool val) { attrib_ = onoff(val, Fl_Terminal::INVERSE); } // e.g. ESC[7m + void sgr_strike(bool val) { attrib_ = onoff(val, Fl_Terminal::STRIKEOUT); } // e.g. ESC[9m + }; + +protected: + // Cursor Class /////////////////////////////////////////////////////////// + // + // Class to manage the terminal's cursor position, color, etc. + // + class Cursor { + int col_; // cursor's current col (x) position on display + int row_; // cursor's current row (y) position on display + int h_; // cursor's height (affected by font size) + Fl_Color fgcolor_; // cursor's fg color (color of text, if any) + Fl_Color bgcolor_; // cursor's bg color + public: + Cursor(void) { + col_ = 0; + row_ = 0; + h_ = 10; + fgcolor_ = 0xfffff000; // wht + bgcolor_ = 0x00d00000; // grn + } + int col(void) const { return col_; } + int row(void) const { return row_; } + int h(void) const { return h_; } + Fl_Color fgcolor(void) const { return fgcolor_; } + Fl_Color bgcolor(void) const { return bgcolor_; } + void col(int val) { col_ = val >= 0 ? val : 0; } + void row(int val) { row_ = val >= 0 ? val : 0; } + void h(int val) { h_ = val; } + void fgcolor(Fl_Color val) { fgcolor_ = val; } + void bgcolor(Fl_Color val) { bgcolor_ = val; } + int left(void) { col_ = (col_>0) ? (col_-1) : 0; return col_; } + int right(void) { return ++col_; } + int up(void) { row_ = (row_>0) ? (row_-1) : 0; return row_; } + int down(void) { return ++row_; } + bool is_rowcol(int drow,int dcol) const; + void scroll(int nrows); + void home(void) { row_ = 0; col_ = 0; } + }; + + // Utf8Char Class /////////////////////////////////////////////////////////// + // + // Class to manage the terminal's individual UTF-8 characters. + // Includes fg/bg color, attributes (BOLD, UNDERLINE..) + // + class Utf8Char { + static const int max_utf8_ = 4; // RFC 3629 paraphrased: In UTF-8, chars are encoded with 1 to 4 octets + char text_[max_utf8_]; // memory for actual ASCII or UTF-8 byte contents + uchar len_; // length of bytes in text_[] buffer; 1 for ASCII, >1 for UTF-8 + uchar attrib_; // attribute bits for this char (bold, underline..) + uchar flags_; // CharFlags bits + Fl_Color fgcolor_; // fltk fg color (supports 8color or 24bit color set w/ESC[37;<r>;<g>;<b>m) + Fl_Color bgcolor_; // fltk bg color (supports 8color or 24bit color set w/ESC[47;<r>;<g>;<b>m) + // Private methods + void text_utf8_(const char *text, int len); + public: + // Public methods + Utf8Char(void); // ctor + Utf8Char(const Utf8Char& o); // copy ctor + ~Utf8Char(void); // dtor + Utf8Char& operator=(const Utf8Char& o); // assignment + inline int max_utf8() const { return max_utf8_; } + void text_utf8(const char *text, int len, const CharStyle& style); + void text_ascii(char c, const CharStyle& style); + void fl_font_set(const CharStyle& style) const; + + // Return the UTF-8 text string for this character. + // Use length() to get number of bytes in string, which will be 1 for ASCII chars. + // + const char* text_utf8(void) const { return text_; } + // Return the attribute for this char + uchar attrib(void) const { return attrib_; } + uchar flags(void) const { return flags_; } + Fl_Color fgcolor(void) const; + Fl_Color bgcolor(void) const; + // Return the length of this character in bytes (UTF-8 can be multibyte..) + int length(void) const { return int(len_); } + double pwidth(void) const; + int pwidth_int(void) const; + // Clear the character to a 'space' + void clear(const CharStyle& style) { text_ascii(' ', style); } + bool is_char(char c) const { return *text_ == c; } + void show_char(void) const { ::printf("%.*s", len_, text_); } + void show_char_info(void) const { ::fprintf(stderr, "UTF-8('%.*s', len=%d)\n", len_, text_, len_); } + + Fl_Color attr_color(Fl_Color col, const Fl_Widget *grp) const; + Fl_Color attr_fg_color(const Fl_Widget *grp) const; + Fl_Color attr_bg_color(const Fl_Widget *grp) const; + }; + + // RingBuffer Class /////////////////////////////////////////////////// + // + // Manages ring with indexed row/col and "history" vs. "display" concepts. + // + class RingBuffer { + Utf8Char *ring_chars_; // the ring UTF-8 char buffer + int ring_rows_; // #rows in ring total + int ring_cols_; // #columns in ring/hist/disp + int nchars_; // #chars in ring (ring_rows*ring_cols) + int hist_rows_; // #rows in history + int hist_use_; // #rows in use by history + int disp_rows_; // #rows in display + int offset_; // index offset (used for 'scrolling') + +private: + void new_copy(int drows, int dcols, int hrows, const CharStyle& style); + //DEBUG void write_row(FILE *fp, Utf8Char *u8c, int cols) const { + //DEBUG cols = (cols != 0) ? cols : ring_cols(); + //DEBUG for ( int col=0; col<cols; col++, u8c++ ) { + //DEBUG ::fprintf(fp, "%.*s", u8c->length(), u8c->text_utf8()); + //DEBUG } + //DEBUG } + public: + void clear(void); + void clear_hist(void); + RingBuffer(void); + RingBuffer(int drows, int dcols, int hrows); + ~RingBuffer(void); + + // Methods to access ring + // + // The 'offset' concept allows the 'history' and 'display' + // to be scrolled indefinitely. The 'offset' is applied + // to all the row accesses, and are clamped to within their bounds. + // + // For 'raw' access to the ring (without the offset concept), + // use the ring_chars() method, and walk from 0 - ring_rows(). + // + // _____________ + // | | <- hist_srow() <- ring_srow() + // | H i s t | + // | | + // |_____________| <- hist_erow() + // | | <- disp_srow() + // | D i s p | + // | | + // |_____________| <- disp_erow() <- ring_erow() + // + // \___________/ + // ring_cols() + // hist_cols() + // disp_cols() + // + inline int ring_rows(void) const { return ring_rows_; } + inline int ring_cols(void) const { return ring_cols_; } + inline int ring_srow(void) const { return(0); } + inline int ring_erow(void) const { return(ring_rows_ - 1); } + inline int hist_rows(void) const { return hist_rows_; } + inline int hist_cols(void) const { return ring_cols_; } + inline int hist_srow(void) const { return((offset_ + 0) % ring_rows_); } + inline int hist_erow(void) const { return((offset_ + hist_rows_ - 1) % ring_rows_); } + inline int disp_rows(void) const { return disp_rows_; } + inline int disp_cols(void) const { return ring_cols_; } + inline int disp_srow(void) const { return((offset_ + hist_rows_) % ring_rows_); } + inline int disp_erow(void) const { return((offset_ + hist_rows_ + disp_rows_ - 1) % ring_rows_); } + inline int offset(void) const { return offset_; } + // History use + inline int hist_use(void) const { return hist_use_; } + inline void hist_use(int val) { hist_use_ = val; } + inline int hist_use_srow(void) const { return((offset_ + hist_rows_ - hist_use_) % ring_rows_); } + inline Utf8Char *ring_chars(void) { return ring_chars_; } // access ring buffer directly + inline Utf8Char *ring_chars(void) const { return ring_chars_; } // access ring buffer directly + + bool is_hist_ring_row(int grow) const; + bool is_disp_ring_row(int grow) const; + //DEBUG void show_ring_info(void) const; + void move_disp_row(int src_row, int dst_row); + void clear_disp_row(int drow, const CharStyle& style); + void scroll(int rows, const CharStyle& style); + + const Utf8Char* u8c_ring_row(int row) const; + const Utf8Char* u8c_hist_row(int hrow) const; + const Utf8Char* u8c_hist_use_row(int hurow) const; + const Utf8Char* u8c_disp_row(int drow) const; + + // Non-const versions of the above methods + // Using "Effective C++" ugly-as-hell syntax technique. + // + Utf8Char* u8c_ring_row(int row); + Utf8Char* u8c_hist_row(int hrow); + Utf8Char* u8c_hist_use_row(int hurow); + Utf8Char* u8c_disp_row(int drow); + + void create(int drows, int dcols, int hrows); + void resize(int drows, int dcols, int hrows, const CharStyle& style); + + void change_disp_rows(int drows, const CharStyle& style); + void change_disp_cols(int dcols, const CharStyle& style); + }; + + // Selection Class /////////////////////////////////////////////////// + // + // Class to manage mouse selection + // + class Selection { + int srow_, scol_, erow_, ecol_; // selection start/end. NOTE: start *might* be > end + int push_row_, push_col_; // global row/col for last FL_PUSH + Fl_Color selectionbgcolor_; + Fl_Color selectionfgcolor_; + int state_ ; // 0=none, 1=started, 2=extended, 3=done + bool is_selection_; // false: no selection + public: + Selection(void); + int srow(void) const { return srow_; } + int scol(void) const { return scol_; } + int erow(void) const { return erow_; } + int ecol(void) const { return ecol_; } + void push_clear() { push_row_ = push_col_ = -1; } + void push_rowcol(int row,int col) { push_row_ = row; push_col_ = col; } + void start_push() { start(push_row_, push_col_); } + bool dragged_off(int row,int col) { return (push_row_ != row) || (push_col_ != col); } + void selectionfgcolor(Fl_Color val) { selectionfgcolor_ = val; } + void selectionbgcolor(Fl_Color val) { selectionbgcolor_ = val; } + Fl_Color selectionfgcolor(void) const { return selectionfgcolor_; } + Fl_Color selectionbgcolor(void) const { return selectionbgcolor_; } + bool is_selection(void) const { return is_selection_; } + bool get_selection(int &srow,int &scol,int &erow,int &ecol) const; // guarantees return (start < end) + bool start(int row, int col); + bool extend(int row, int col); + void end(void); + void select(int srow, int scol, int erow, int ecol); + bool clear(void); + int state(void) const { return state_; } + void scroll(int nrows); + }; + + // EscapeSeq Class /////////////////////////////////////////////////// + // + // Class to handle parsing ESC sequences + // + // Holds all state information for parsing esc sequences, + // so sequences can span multiple block read(2) operations, etc. + // Handling of parsed sequences is NOT handled in this class, + // just the parsing of the sequences and managing generic integers. + // + class EscapeSeq { + public: + // EscapeSeq Constants + // Maximums + static const int maxbuff = 80; // character buffer + static const int maxvals = 20; // integer value buffer + // Return codes + static const int success = 0; // operation succeeded + static const int fail = -1; // operation failed + static const int completed = 1; // multi-step operation completed successfully + private: + char esc_mode_; // escape parsing mode state + char csi_; // This is an ESC[.. sequence (Ctrl Seq Introducer) + char buff_[maxbuff]; // escape sequence being parsed + char *buffp_; // parsing ptr into buff[] + char *buffendp_; // end of buff[] (ptr to last valid buff char) + char *valbuffp_; // pointer to first char in buff of integer being parsed + int vals_[maxvals]; // value array for parsing #'s in ESC[#;#;#.. + int vali_; // parsing index into vals_[], 0 if none + int save_row_, save_col_; // used by ESC[s/u for save/restore + + int append_buff(char c); + int append_val(void); + + public: + EscapeSeq(void); + void reset(void); + char esc_mode(void) const; + void esc_mode(char val); + int total_vals(void) const; + int val(int i) const; + int defvalmax(int dval, int max) const; + bool parse_in_progress(void) const; + bool is_csi(void) const; + int parse(char c); + void save_cursor(int row, int col); + void restore_cursor(int &row, int &col); + }; + + // Partial UTF-8 Buffer Class //////////////////////////////////////////// + // + // Class to manage buffering partial UTF-8 characters between write calls. + // + class PartialUtf8Buf { + char buf_[10]; // buffer partial UTF-8 encoded char + int buflen_; // length of buffered UTF-8 encoded char + int clen_; // final byte length of a UTF-8 char + public: + void clear(void) { buflen_ = clen_ = 0; } // clear the buffer + PartialUtf8Buf(void) { clear(); } // Ctor + // Is byte 'c' in the middle of a UTF-8 encoded byte sequence? + bool is_continuation(char c) { + // Byte 1 Byte 2 Byte 3 ..etc.. + // ASCII: 0xxxxxxx + // UTF8(2): 110xxxxx 10xxxxxx + // UTF8(3): 1110xxxx 10xxxxxx 10xxxxxx + // UTF8(4): 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + // UTF8(5): 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + // UTF8(6): 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + // \______/ \______________________________________________/ + // Start byte Continuation bytes + // (c & 0xc0) == 0x80 + return ((c & 0xc0) == 0x80); + } + // Access buffer + const char* buf(void) const { return buf_; } + // Access buffer length + int buflen(void) const { return buflen_; } + // Append bytes of a partial UTF-8 string to the buffer. + // + // Returns: + // - true if done OK. Use is_complete() to see if a complete char received. + // - false if buffer overrun occurred, class is clear()ed. + // + // An appropriate response to 'false' would be to print the + // "unknown character" and skip all subsequent UTF-8 continuation chars. + // + bool append(const char* p, int len) { + if (len <= 0) return true; // ignore silly requests: say we did but dont + if (buflen_ + len >= (int)sizeof(buf_)) // overrun check + { clear(); return false; } // clear self, return false + if (!buflen_) clen_ = fl_utf8len(*p); // first byte? save char len for later + while (len>0) { buf_[buflen_++] = *p++; len--; } // append byte to buffer + return true; + } + bool is_complete(void) const { return (buflen_ && (buflen_ == clen_)); } + }; + +private: + // Fl_Terminal Members + Fl_Scrollbar *vscroll_; // vertical scrollbar (value: rows above disp_chars[]) + int scrollbar_size_; // local preference for scrollbar size + CharStyle current_style_; // current font, attrib, color.. + + // A ring buffer is used for the terminal's history (hist) and display (disp) buffer. + // See README-Fl_Terminal.txt, section "RING BUFFER DESCRIPTION" for diagrams/info. + // + // Ring buffer + RingBuffer ring_; // terminal history/display ring buffer + Cursor cursor_; // terminal cursor (position, color, etc) + Margin margin_; // terminal margins (top,left,bottom,right) + Selection select_; // mouse selection + EscapeSeq escseq; // Escape sequence parsing (ESC[ xterm/vt100) + bool show_unknown_; // if true, show unknown chars as '¿' (default off) + bool ansi_; // if true, parse ansi codes (default on) + char *tabstops_; // array of tab stops (0|1) \__ TODO: This should probably + int tabstops_size_; // size of tabstops[] array / be a class "TabStops". + Fl_Rect scrn_; // terminal screen xywh inside box(), margins, and vscroll + int autoscroll_dir_; // 0=autoscroll timer off, 3=scrolling up, 4=scrolling down + int autoscroll_amt_; // #pixels above or below edge, used for autoscroll speed + RedrawStyle redraw_style_; // NO_REDRAW, RATE_LIMITED, PER_WRITE + float redraw_rate_; // maximum redraw rate in seconds, default=0.10 + bool redraw_modified_; // display modified; used by update_cb() to rate limit redraws + bool redraw_timer_; // if true, redraw timer is running + PartialUtf8Buf pub_; // handles Partial Utf8 Buffer (pub) + +protected: + // Ring buffer management + const Utf8Char* u8c_ring_row(int grow) const; + const Utf8Char* u8c_hist_row(int hrow) const; + const Utf8Char* u8c_hist_use_row(int hrow) const; + const Utf8Char* u8c_disp_row(int drow) const; + // non-const versions of the above. + // "Effective C++" says: implement non-const method to cast away const + // + Utf8Char* u8c_ring_row(int grow); + Utf8Char* u8c_hist_row(int hrow); + Utf8Char* u8c_hist_use_row(int hurow); + Utf8Char* u8c_disp_row(int drow); +private: + void create_ring(int drows, int dcols, int hrows); +protected: + Utf8Char* u8c_cursor(void); +private: + int vscroll_width(void) const; + // Tabstops + void init_tabstops(int newsize); + void default_tabstops(void); + void clear_all_tabstops(void); + void set_tabstop(void); + void clear_tabstop(void); + // Updates + void update_screen_xywh(void); + void update_screen(bool font_changed); + void update_scrollbar(void); + // Resize + void resize_display_rows(int drows); + void resize_display_columns(int dcols); + void refit_disp_to_screen(void); + // Callbacks + static void scrollbar_cb(Fl_Widget*, void*); // scrollbar manipulation + static void autoscroll_timer_cb(void*); // mouse drag autoscroll + void autoscroll_timer_cb2(void); + static void redraw_timer_cb(void*); // redraw rate limiting timer + void redraw_timer_cb2(void); + + // Screen management +protected: + const CharStyle& current_style(void) const; + void current_style(const CharStyle& sty); + int x_to_glob_col(int X, int grow, int &gcol) const; + int xy_to_glob_rowcol(int X, int Y, int &grow, int &gcol) const; + int w_to_col(int W) const; + int h_to_row(int H) const; + // API: Display clear operations + void clear_screen(bool scroll_to_hist=true); + void clear_screen_home(bool scroll_to_hist=true); + void clear_sod(void); + void clear_eod(void); + void clear_eol(void); + void clear_sol(void); + void clear_line(int row); + void clear_line(void); + const Utf8Char* walk_selection(const Utf8Char *u8c, int &row, int &col) const; + bool get_selection(int &srow,int &scol,int &erow,int &ecol) const; + bool is_selection(void) const; + bool is_inside_selection(int row,int col) const; + bool is_hist_ring_row(int grow) const; + bool is_disp_ring_row(int grow) const; + int selection_text_len(void) const; + const char* selection_text(void) const; + void clear_mouse_selection(void); + bool selection_extend(int X,int Y); + void scroll(int rows); + void insert_rows(int count); + void delete_rows(int count); + void insert_char_eol(char c, int drow, int dcol, int rep); + void insert_char(char c, int rep); + void delete_chars(int drow, int dcol, int rep); + void delete_chars(int rep); + // History + void history_use(int val, bool update=true); +public: + // API: Terminal operations + void clear_history(void); // ESC [ 3 J + void reset_terminal(void); // ESC c +protected: + // Cursor management + int cursor_h(void) const; +public: + // API: Cursor + void cursorfgcolor(Fl_Color val); + void cursorbgcolor(Fl_Color val); + Fl_Color cursorfgcolor(void) const; + Fl_Color cursorbgcolor(void) const; +protected: + void cursor_row(int row); + void cursor_col(int col); +public: + int cursor_row(void) const; + int cursor_col(void) const; +protected: + void cursor_up(int count=1, bool do_scroll=false); + void cursor_down(int count=1, bool do_scroll=false); + void cursor_left(int count=1); + void cursor_right(int count=1, bool do_scroll=false); + void cursor_home(void); + void cursor_eol(void); + void cursor_sol(void); + void cursor_cr(void); + void cursor_crlf(int count=1); + void cursor_tab_right(int count=1); + void cursor_tab_left(int count=1); + void save_cursor(void); + void restore_cursor(void); + // Printing + void handle_ctrl(char c); + bool is_printable(char c); + bool is_ctrl(char c); + void handle_SGR(void); + void handle_DECRARA(void); + void handle_escseq(char c); + // -- + void display_modified(void); + void display_modified_clear(void); + void clear_char_at_disp(int drow, int dcol); + const Utf8Char* utf8_char_at_disp(int drow, int dcol) const; + const Utf8Char* utf8_char_at_glob(int grow, int gcol) const; + void repeat_char(char c, int rep); + void utf8_cache_clear(void); + void utf8_cache_flush(void); + + // API: Character display output +public: + void putchar(const char *text, int len, int drow, int dcol); + void putchar(char c, int drow, int dcol); + void print_char(const char *text, int len=-1); + void print_char(char c); + // API: String display output + void append_utf8(const char *buf, int len=-1); + void append_ascii(const char *s); + void append(const char *s, int len=-1); +protected: + int handle_unknown_char(void); + // Drawing + void draw_row_bg(int grow, int X, int Y) const; + void draw_row(int grow, int Y) const; + void draw_buff(int Y) const; + void handle_selection_autoscroll(void); + int handle_selection(int e); +public: + // FLTK: draw(), resize(), handle() + void draw(void) FL_OVERRIDE; + void resize(int X,int Y,int W,int H) FL_OVERRIDE; + int handle(int e) FL_OVERRIDE; + +protected: + // Internal short names + // Don't make these public, but allow internals and + // derived classes to maintain brevity. + // + inline int ring_rows(void) const { return ring_.ring_rows(); } + inline int ring_cols(void) const { return ring_.ring_cols(); } + inline int ring_srow(void) const { return ring_.ring_srow(); } + inline int ring_erow(void) const { return ring_.ring_erow(); } + inline int hist_rows(void) const { return ring_.hist_rows(); } + inline int hist_cols(void) const { return ring_.hist_cols(); } + inline int hist_srow(void) const { return ring_.hist_srow(); } + inline int hist_erow(void) const { return ring_.hist_erow(); } + inline int hist_use(void) const { return ring_.hist_use(); } + inline int hist_use_srow(void) const { return ring_.hist_use_srow(); } + inline int disp_rows(void) const { return ring_.disp_rows(); } + inline int disp_cols(void) const { return ring_.disp_cols(); } + inline int disp_srow(void) const { return ring_.disp_srow(); } + inline int disp_erow(void) const { return ring_.disp_erow(); } + inline int offset(void) const { return ring_.offset(); } + + // TODO: CLEAN UP WHAT'S PUBLIC, AND WHAT SHOULD BE 'PROTECTED' AND 'PRIVATE' + // Some of the public stuff should, quite simply, "not be". + + // API: Terminal features +public: + // API: Scrollbar + int scrollbar_size(void) const; + void scrollbar_size(int val); + int scrollbar_actual_size(void) const; + // API: History + int history_rows(void) const; + void history_rows(int val); + int history_use(void) const; + // API: Display + int display_rows(void) const; + void display_rows(int val); + int display_columns(void) const; + void display_columns(int val); + // API: Box + /// Sets the box type, updates terminal margins et al. Default is FL_DOWN_FRAME. + /// + /// FL_XXX_FRAME types are handled in a special way by this widget, and guarantee + /// the background is a flat field. + /// + /// FL_XXX_BOX may draw gradients as inherited by Fl::scheme(). + /// + void box(Fl_Boxtype val) { Fl_Group::box(val); update_screen(false); } + /// Returns the current box type. + Fl_Boxtype box(void) const { return Fl_Group::box(); } + // API: Margins + /// Return the left margin; see \ref Fl_Terminal_Margins. + int margin_left(void) const { return margin_.left(); } + /// Return the right margin; see \ref Fl_Terminal_Margins. + int margin_right(void) const { return margin_.right(); } + /// Return the top margin; see \ref Fl_Terminal_Margins. + int margin_top(void) const { return margin_.top(); } + /// Return the bottom margin; see \ref Fl_Terminal_Margins. + int margin_bottom(void) const { return margin_.bottom(); } + void margin_left(int val); + void margin_right(int val); + void margin_top(int val); + void margin_bottom(int val); + // API: Text font/size/color + void textfont(Fl_Font val); + void textsize(Fl_Fontsize val); + void textfgcolor(Fl_Color val); + void textbgcolor(Fl_Color val); + void textfgcolor_default(Fl_Color val); + void textbgcolor_default(Fl_Color val); + /// Return text font used to draw all text in the terminal. + Fl_Font textfont(void) const { return current_style_.fontface(); } + /// Return text font size used to draw all text in the terminal. + Fl_Fontsize textsize(void) const { return current_style_.fontsize(); } + /// Return text's current foreground color. + Fl_Color textfgcolor(void) const { return current_style_.fgcolor(); } + /// Return text's current background color. + Fl_Color textbgcolor(void) const { return current_style_.bgcolor(); } + /// Return text's default foreground color. \see textfgcolor() + Fl_Color textfgcolor_default(void) const { return current_style_.defaultfgcolor(); } + /// Return text's default background color. \see textbgcolor() + Fl_Color textbgcolor_default(void) const { return current_style_.defaultbgcolor(); } + void textfgcolor_xterm(uchar val); + void textbgcolor_xterm(uchar val); + /// Set mouse selection foreground color. + void selectionfgcolor(Fl_Color val) { select_.selectionfgcolor(val); } + /// Set mouse selection background color. + void selectionbgcolor(Fl_Color val) { select_.selectionbgcolor(val); } + /// Get mouse selection foreground color. + Fl_Color selectionfgcolor(void) const { return select_.selectionfgcolor(); } + /// Get mouse selection background color. + Fl_Color selectionbgcolor(void) const { return select_.selectionbgcolor(); } + // API: Text attrib + void textattrib(uchar val); + // API: Redraw style/rate + RedrawStyle redraw_style(void) const; + void redraw_style(RedrawStyle val); +protected: + bool is_redraw_style(RedrawStyle val) { return redraw_style_ == val; } +public: + float redraw_rate(void) const; + void redraw_rate(float val); + // API: Show unknown/unprintable chars + bool show_unknown(void) const; + void show_unknown(bool val); + // API: ANSI sequences + bool ansi(void) const; + void ansi(bool val); + // Fl_Simple_Terminal API compatibility + int history_lines(void) const; + void history_lines(int val); + // API: printf() + void printf(const char *fmt, ...); + void vprintf(const char *fmt, va_list ap); + // Ctor + Fl_Terminal(int X, int Y, int W, int H, const char*L=0); + // Dtor + ~Fl_Terminal(void); + // Debugging features +//DEBUG void show_ring_info() const { ring_.show_ring_info(); } +//DEBUG void write_row(FILE *fp, Utf8Char *u8c, int cols) const; +//DEBUG void show_buffers(RingBuffer *a, RingBuffer *b=0) const; +}; +#endif diff --git a/documentation/Doxyfile.in b/documentation/Doxyfile.in index bf88783f4..130dd1845 100644 --- a/documentation/Doxyfile.in +++ b/documentation/Doxyfile.in @@ -789,6 +789,7 @@ INPUT = @CMAKE_CURRENT_SOURCE_DIR@/src/index.dox \ @CMAKE_CURRENT_SOURCE_DIR@/src/coordinates.dox \ @CMAKE_CURRENT_SOURCE_DIR@/src/resize.dox \ @CMAKE_CURRENT_SOURCE_DIR@/src/editor.dox \ + @CMAKE_CURRENT_SOURCE_DIR@/src/Fl_Terminal.dox \ @CMAKE_CURRENT_SOURCE_DIR@/src/drawing.dox \ @CMAKE_CURRENT_SOURCE_DIR@/src/events.dox \ @CMAKE_CURRENT_SOURCE_DIR@/src/subclassing.dox \ diff --git a/documentation/Makefile b/documentation/Makefile index c75dd567c..5643ba118 100644 --- a/documentation/Makefile +++ b/documentation/Makefile @@ -48,7 +48,8 @@ HTMLFILES = \ $(SRC_DOCDIR)/development.dox \ $(SRC_DOCDIR)/license.dox \ $(SRC_DOCDIR)/examples.dox \ - $(SRC_DOCDIR)/faq.dox + $(SRC_DOCDIR)/faq.dox \ + $(SRC_DOCDIR)/Fl_Terminal.dox MANPAGES = $(SRC_DOCDIR)/fltk.$(CAT3EXT) $(SRC_DOCDIR)/fltk-config.$(CAT1EXT) \ $(SRC_DOCDIR)/fluid.$(CAT1EXT) $(SRC_DOCDIR)/blocks.$(CAT6EXT) \ diff --git a/documentation/src/Fl_Terminal-24bit-colors.png b/documentation/src/Fl_Terminal-24bit-colors.png Binary files differnew file mode 100644 index 000000000..5e7f6adb1 --- /dev/null +++ b/documentation/src/Fl_Terminal-24bit-colors.png diff --git a/documentation/src/Fl_Terminal-3bit-colors.png b/documentation/src/Fl_Terminal-3bit-colors.png Binary files differnew file mode 100644 index 000000000..a0407738e --- /dev/null +++ b/documentation/src/Fl_Terminal-3bit-colors.png diff --git a/documentation/src/Fl_Terminal-demo.png b/documentation/src/Fl_Terminal-demo.png Binary files differnew file mode 100644 index 000000000..43996ba72 --- /dev/null +++ b/documentation/src/Fl_Terminal-demo.png diff --git a/documentation/src/Fl_Terminal-utf8-demo.png b/documentation/src/Fl_Terminal-utf8-demo.png Binary files differnew file mode 100644 index 000000000..a04f11bf9 --- /dev/null +++ b/documentation/src/Fl_Terminal-utf8-demo.png diff --git a/documentation/src/Fl_Terminal.dox b/documentation/src/Fl_Terminal.dox new file mode 100644 index 000000000..2963218da --- /dev/null +++ b/documentation/src/Fl_Terminal.dox @@ -0,0 +1,514 @@ +// vim:syntax=doxygen +/** + +\page Fl_Terminal_Tech_Docs Fl_Terminal Technical Documentation + +This chapter covers the vt100/xterm style "escape codes" used by +Fl_Terminal for cursor positioning, text colors, and other display +screen control features such as full or partial screen clearing, +up/down scrolling, character insert/delete, etc. + +\section Fl_Terminal_escape_codes The Escape Codes Fl_Terminal Supports + +These are the escape codes Fl_Terminal actually supports, and is not +the 'complete' list that e.g. xterm supports. Most of the important stuff +has been implemented, but esoteric features (such as scroll regions) has not. + +Features will be added as the widget matures. + +\code{.unparsed} +│ -------------------------------------------------------- +│ --- The CSI (Control Sequence Introducer, or "ESC[") --- +│ -------------------------------------------------------- +│ +│ ESC[#@ - (ICH) Insert blank Chars (default=1) +│ ESC[#A - (CUU) Cursor Up, no scroll/wrap +│ ESC[#B - (CUD) Cursor Down, no scroll/wrap +│ ESC[#C - (CUF) Cursor Forward, no wrap +│ ESC[#D - (CUB) Cursor Back, no wrap +│ ESC[#E - (CNL) Cursor Next Line (crlf) xterm, !gnome +│ ESC[#F - (CPL) Cursor Preceding Line: move to sol and up # lines +│ ESC[#G - (CHA) Cursor Horizontal Absolute positioning +│ │ +│ ├── ESC[G - move to column 1 (start of line, sol) +│ └── ESC[#G - move to column # +│ +│ ESC[#H - (CUP) Cursor Position (#'s are 1 based) +│ │ +│ ├── ESC[H - go to row #1 +│ ├── ESC[#H - go to (row #) (default=1) +│ └── ESC[#;#H - go to (row# ; col#) +│ +│ ESC[#I - (CHT) Cursor Horizontal Tab: tab forward +│ │ +│ └── ESC[#I - tab # times (default 1) +│ +│ ESC[#J - (ED) Erase in Display +│ │ +│ ├── ESC[0J - clear to end of display (default) +│ ├── ESC[1J - clear to start of display +│ ├── ESC[2J - clear all lines +│ └── ESC[3J - clear screen history +│ +│ ESC[#K - (EL) Erase in line +│ │ +│ ├── ESC[0K - clear to end of line (default) +│ ├── ESC[1K - clear to start of line +│ └── ESC[2K - clear current line +│ +│ ESC[#L - (IL) Insert # Lines (default=1) +│ ESC[#M - (DL) Delete # Lines (default=1) +│ ESC[#P - (DCH) Delete # Chars (default=1) +│ ESC[#S - (SU) Scroll Up # lines (default=1) +│ ESC[#T - (SD) Scroll Down # lines (default=1) +│ ESC[#X - (ECH) Erase Characters (default=1) +│ +│ ESC[#Z - (CBT) Cursor Backwards Tab +│ │ +│ └── ESC[#Z - backwards tab # times (default=1) +│ +│ ESC[#a - (HPR) move cursor relative [columns] (default=[row,col+1]) (NOT IMPLEMENTED) +│ ESC[#b - (REP) repeat prev graphics char # times (NOT IMPLEMENTED) +│ ESC[#d - (VPA) Line Position Absolute [row] (NOT IMPLEMENTED) +│ ESC[#e - (LPA) Line Position Relative [row] (NOT IMPLEMENTED) +│ ESC[#f - (CUP) cursor position (#'s 1 based), same as ESC[H +│ +│ ESC[#g - (TBC)Tabulation Clear +│ │ +│ ├── ESC[0g - Clear tabstop at cursor +│ └── ESC[3g - Clear all tabstops +│ +│ ESC[#m - (SGR) Set Graphic Rendition +│ │ +│ │ *** Attribute Enable *** +│ │ +│ ├── ESC[0m - reset: normal attribs/default fg/bg color (VT100) +│ ├── ESC[1m - bold (VT100) +│ ├── ESC[2m - dim +│ ├── ESC[3m - italic +│ ├── ESC[4m - underline (VT100) +│ ├── ESC[5m - blink (NOT IMPLEMENTED) (VT100) +│ ├── ESC[6m - (unused) +│ ├── ESC[7m - inverse (VT100) +│ ├── ESC[8m - (unused) +│ ├── ESC[9m - strikeout +│ ├── ESC[21m - doubly underline (Currently this just does single underline) +│ │ +│ │ *** Attribute Disable *** +│ │ +│ ├── ESC[22m - disable bold/dim +│ ├── ESC[23m - disable italic +│ ├── ESC[24m - disable underline +│ ├── ESC[25m - disable blink (NOT IMPLEMENTED) +│ ├── ESC[26m - (unused) +│ ├── ESC[27m - disable inverse +│ ├── ESC[28m - disable hidden +│ ├── ESC[29m - disable strikeout +│ │ +│ │ *** Foreground Text "8 Color" *** +│ │ +│ ├── ESC[30m - fg Black +│ ├── ESC[31m - fg Red +│ ├── ESC[32m - fg Green +│ ├── ESC[33m - fg Yellow +│ ├── ESC[34m - fg Blue +│ ├── ESC[35m - fg Magenta +│ ├── ESC[36m - fg Cyan +│ ├── ESC[37m - fg White +│ ├── ESC[39m - fg default +│ │ +│ │ *** Background Text "8 Color" *** +│ │ +│ ├── ESC[40m - bg Black +│ ├── ESC[41m - bg Red +│ ├── ESC[42m - bg Green +│ ├── ESC[43m - bg Yellow +│ ├── ESC[44m - bg Blue +│ ├── ESC[45m - bg Magenta +│ ├── ESC[46m - bg Cyan +│ ├── ESC[47m - bg White +│ ├── ESC[49m - bg default +│ │ +│ │ *** Special RGB Color *** +│ │ +│ └── ESC [ 38 ; Red ; Grn ; Blue m - where Red,Grn,Blu are decimal (0-255) +│ +│ ESC[s - save cursor pos (ansi.sys+xterm+gnome, but NOT vt100) +│ ESC[u - rest cursor pos (ansi.sys+xterm+gnome, but NOT vt100) +│ +│ ESC[>#q - (DECSCA) Set Cursor style (block/line/blink..) (NOT IMPLEMENTED) +│ ESC[#;#r - (DECSTBM) Set scroll Region top;bot (NOT IMPLEMENTED) +│ ESC[#..$t - (DECRARA) (NOT IMPLEMENTED) +│ +│ ------------------------ +│ --- C1 Control Codes --- +│ ------------------------ +│ +│ <ESC>c - (RIS) Reset term to Initial State +│ <ESC>D - (IND) Index: move cursor down a line, scroll if at bottom +│ <ESC>E - (NEL) Next Line: basically do a crlf, scroll if at bottom +│ <ESC>H - (HTS) Horizontal Tab Set: set a tabstop +│ <ESC>M - (RI) Reverse Index (up w/scroll) +│ +│ NOTE: Acronyms in parens are Digital Equipment Corporation's names these VT features. +│ +\endcode + +\section external_escape_codes Useful Terminal Escape Code Documentation + +Useful links for reference: + + - https://vt100.net/docs/vt100-ug/chapter3.html + - https://www.xfree86.org/current/ctlseqs.html + - https://www.x.org/docs/xterm/ctlseqs.pdf + - https://gist.github.com/justinmk/a5102f9a0c1810437885a04a07ef0a91 <-- alphabetic! + - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html + +\section Fl_Terminal_design Fl_Terminal Design Document + +When I started this project, I identified the key concepts needed to +implement Fl_Terminal: + +- Draw and manage multiline Unicode text in FLTK +- Allow per-character colors and attributes +- Efficient screen buffer to handle "scrollback history" +- Efficient scrolling with vertical scrollbar for even large screen history +- Mouse selection for copy/paste +- Escape code management to implement VT100 style / ANSI escape codes. + +A class was created for each character, since characters can be either ASCII +or Utf8 encoded byte sequences. This class is called Utf8Char, and handles +the character, its fg and bg color, and any attributes like dim, bold, italic, etc. + +For managing the screen, after various experiments, I decided a ring buffer +was the best way to manage things, the ring split in two: + +- 'screen history' which is where lines scrolled off the top are saved +- 'display screen' displayed to the user at all times, and where the cursor lives + +Scrolling the display, either by scrollbar or by new text causing the display +to scroll up one line, would simply change an 'offset' index# of where in the +ring buffer the top of the screen is, automatically moving the top line +into the history, all without moving memory around. + +In fact the only time screen memory is moved around is during these infrequent +operations: + +- during scrolling "down" +- character insert/delete operations within a line +- changing the display size +- changing the history size + +So a class "RingBuffer" is defined to manage the ring, and accessing its various +parts, either as the entire entity ring, just the history, or just the display. + +These three concepts, "ring", "history" and "display" are given abbreviated +names in the RingBuffer class's API: + + ┌─────────────────────────────────────────┬──────────────────────────────┐ + │ NOTE: Abbreviations "hist" and "disp" │ │ + ├─────────────────────────────────────────┘ │ + │ │ + │ "history" may be abbreviated as "hist", and "display" as "disp" in │ + │ both this text and the source code. 4 character names are used so │ + │ they line up cleanly in the source, e.g. │ + │ │ + │ ring_rows() ring_cols() │ + │ hist_rows() hist_cols() │ + │ disp_rows() disp_cols() │ + │ └─┬┘ └─┬┘ └─┬┘ └─┬┘ │ + │ └────┴──────────┴────┴───────── 4 characters │ + │ │ + └────────────────────────────────────────────────────────────────────────┘ + +These concepts were able to fit into C++ classes: + +Utf8Char +-------- +Each character on the screen is a "Utf8Char" which can manage +the UTF-8 encoding of any character as one or more bytes. Also +in that class is a byte for an attribute (underline, bold, etc), +and two integers for fg/bg color. + +RingBuffer +---------- +The RingBuffer class keeps track of the buffer itself, a single +array of Utf8Chars called "ring_chars" whose width is ring_cols() +and whose height is ring_rows(). + +The "top" part of the ring is the history, whose width is hist_cols() +and whose height is hist_rows(). hist_use_rows() is used to define +what part of the history is currently in use. + +The "bottom" part of the ring is the display, whose width is disp_cols() +and whose height is disp_rows(). + +An index number called "offset" points to where in the ring buffer +the top of the ring currently is. This index changes each time the +screen is scrolled, and affects both where the top of the display is, +and where the top of the history is. + +The memory layout of the Utf8Char character array is: + + ring_chars[]: + ___________________ _ _ + | | ʌ + | | | + | | | + | H i s t o r y | | hist_rows + | | | + | | | + |___________________| _v_ + | | ʌ + | | | + | D i s p l a y | | disp_rows + | | | + |___________________| _v_ + + |<----------------->| + ring_cols + hist_cols + disp_cols + +So it's basically a single continuous array of Utf8Char instances +where any character can generally be accessed by index# using the formula: + + ring_chars[ (row*ring_cols)+col ] + +..where 'row' is the desired row, 'col' is the desired column, +and 'ring_cols' is how many columns "wide" the buffer is. + +The "offset" index affects that formula as an extra row offset, +and the resulting index is then clamped within the range of the +ring buffer using modulus. + +Methods are used to allow direct access to the characters +in the buffer that automatically handle the offset and modulus +formulas, namely: + + u8c_ring_row(row,col) // access the entire ring by row/col + u8c_hist_row(row,col) // access just the history buffer + u8c_disp_row(row,col) // access just the display buffer + +A key concept is the use of the simple 'offset' index integer +to allow the starting point of the history and display to be +moved around to implement 'text scrolling', such as when +crlf at the screen bottom causes a 'scroll up'. + +This is simply an "index offset" integer applied to the +hist and disp indexes when drawing the display. So after +scrolling two lines up, the offset is just increased by 2, +redefining where the top of the history and display are, e.g. + + Offset is 0: 2 Offset now 2: + ┌───────────────────┐ ──┐ ┌───────────────────┐ + │ │ │ │ D i s p l a y │ + │ │ └─> ├───────────────────┤ + │ │ │ │ + │ H i s t o r y │ │ │ + │ │ │ H i s t o r y │ + │ │ 2 │ │ + ├───────────────────┤ ──┐ │ │ + │ │ │ │ │ + │ │ └─> ├───────────────────┤ + │ D i s p l a y │ │ │ + │ │ │ D i s p l a y │ + │ │ │ │ + └───────────────────┘ └───────────────────┘ + +This 'offset' trivially implements "text scrolling", avoiding having +to physically move memory around. Just the 'offset' changes, the +text remains where it is in memory. + +This also makes it appear the top line in the display is 'scrolled up' +into the bottom of the scrollback 'history'. + +If the offset exceeds the size of the ring buffer, it simply wraps +around back to the beginning of the buffer with a modulo. + +Indexes into the display and history are also modulo their respective +rows, e.g. + + act_ring_index = (hist_rows + disp_row + offset - scrollbar_pos) % ring_rows; + +This way indexes for ranges can run beyond the bottom of the ring, +and automatically wrap around the ring, e.g. + + ┌───────────────────┐ + ┌─> 2 │ │ + │ 3 │ D i s p l a y │ + │ 4 │ │ + │ ├───────────────────┤ <-- offset points here + │ │ │ + disp │ │ │ + index ┤ │ H i s t o r y │ + wraps │ │ │ + │ │ │ + │ │ │ + │ ├───────────────────┤ + │ 0 │ D i s p l a y │ + │ 1 └───────────────────┘ <- ring_rows points to end of ring + └── 2 : : + 3 : : + disp_row(5) -> 4 :...................: + +The dotted lines show where the display would be if not for the fact +it extends beyond the bottom of the ring buffer (due to the current offset), +and therefore wraps up to the top of the ring. + +So to find a particular row in the display, in this case a 5 line display +whose lines lie between 0 and 4, some simple math calculates the row position +into the ring: + + act_ring_index = (histrows // the display exists AFTER the history, so offset the hist_rows + + offset // include the scroll 'offset' + + disp_row // add the desired row relative to the top of the display (0..disp_rows) + ) % ring_rows; // make sure the resulting index is within the ring buffer (0..ring_rows) + +An additional bit of math makes sure if a negative result occurs, that +negative value works relative to the end of the ring, e.g. + + if (act_ring_index < 0) act_ring_index = ring_rows + act_ring_index; + +This guarantees the act_ring_index is within the ring buffer's address space, +with all offsets applied. + +The math that implements this can be found in the u8c_xxxx_row() methods, +where "xxxx" is one of the concept regions "ring", "hist" or "disp": + + Utf8Char *u8c; + u8c = u8c_ring_row(rrow); // address within ring, rrow can be 0..(ring_rows-1) + u8c = u8c_hist_row(hrow); // address within hist, hrow can be 0..(hist_rows-1) + u8c = u8c_disp_row(drow); // address within disp, drow can be 0..(disp_rows-1) + +The small bit of math is only involved whenever a new row address is needed, +so in a display that's 80x25, to walk all the characters in the screen, the +math above would only be called 25 times, once for each row, and each column +in the row is just a simple integer offset: + + for ( int row=0; row<disp_rows(); row++ ) { // walk rows: disp_rows = 25 + Utf8Char *u8c = u8c_disp_row(row); // get first char in display 'row' + for ( int col=0; col<disp_cols(); col++ ) { // walk cols: disp_cols = 80 + u8c[col].do_something(); // work with the char at row/col + } + } + +So to recap, the concepts here are: + +- The ring buffer itself, a linear array that is conceptually + split into a 2 dimensional array of rows and columns whose + height and width are: + + ring_rows -- how many rows in the entire ring buffer + ring_cols -- how many columns in the ring buffer + nchars -- total chars in ring, e.g. (ring_rows * ring_cols) + +- The "history" within the ring. For simplicity this is thought of + as starting relative to the top of the ring buffer, occupying + ring buffer rows: + + 0 .. hist_rows()-1 + +- The "display", or "disp", within the ring, just after the "history". + It occupies the ring buffer rows: + + hist_rows() .. hist_rows()+disp_rows()-1 + + ..or similarly: + + (hist_rows)..(ring_rows-1) + + The following convenience methods provide access to the + start and end indexes within the ring buffer for each entity: + + // Entire ring + ring_srow() -- start row index of the ring buffer (always 0) + ring_erow() -- end row index of the ring buffer + + // "history" part of ring + hist_srow() -- start row index of the screen history + hist_erow() -- end row index of the screen history + + // "display" part of ring + disp_srow() -- start row index of the display + disp_erow() -- end row index of the display + + The values returned by these are as described above. + For the hist_xxx() and disp_xxx() methods the 'offset' included into + the forumula. (For this reason hist_srow() won't always be zero + the way ring_srow() is, due to the 'offset') + + The values returned by these methods can all be passed to the + u8c_ring_row() function to access the actual character buffer's contents. + +- An "offset" used to move the "history" and "display" around within + the ring buffer to implement the "text scrolling" concept. The offset + is applied when new characters are added to the buffer, and during + drawing to find where the display actually is within the ring. + +- The "scrollbar", which only is used when redrawing the screen the user sees, + and is simply an additional offset to all the above, where a scrollbar + value of zero (the scrollbar tab at the bottom) shows the display rows, + and as the scrollbar values increase as the user moves the scrollbar + tab upwards, +1 per line, this is subtracted from the normal starting + index to let the user work their way backwards into the scrollback history. + Again, negative numbers wrap around within the ring buffer automatically. + +The ring buffer allows new content to simply be appended to the ring buffer, +and the index# for the start of the display and start of scrollback history are +simply incremented. So the next time the display is "drawn", it starts at +a different position in the ring. + +This makes scrolling content at high speed trivial, without memory moves. +It also makes the concept of "scrolling" with the scrollbar simple as well, +simply being an extra index offset applied during drawing. + +Mouse Selection +--------------- + +Dragging the mouse across the screen should highlight the text, allowing the user +to extend the selection either beyond or before the point started. Extending the +drag to the top of the screen should automatically 'scroll up' to select more +lines in the scrollback history, or below the bottom to do the opposite. + +The mouse selection is implemented as a class to keep track of the start/end +row/col positions of the selection, and other details such as a flag indicating +if a selection has been made, what color the fg/bg text should appear when +text is selected, and methods that allow setting and extending the selection, +clearing the selection, and "scrolling" the selection, to ensure the row/col +indexes adjust correctly to track when the screen or scrollbar is scrolled. + + +Redraw Timer +------------ + +Knowing when to redraw is tricky with a terminal, because sometimes high volumes +of input will come in asynchronously, so in that case we need to determine when +to redraw the screen to show the new content; too quickly will cause the screen +to spend more time redrawing itself, preventing new input from being added. Too +slowly, the user won't see new information appear in a timely manner. + +To solve this, a rate timer is used to prevent too many redraws: + +- When new data comes in, a 1/10 sec timer is started and a modify flag is set. + +- redraw() is NOT called yet, allowing more data to continue to arrive quickly + +- When the 1/10th second timer fires, the callback checks the modify flag: + + - if set, calls redraw(), resets the modify to 0, and calls + Fl::repeat_timeout() to repeat the callback in another 1/10th sec. + + - if clear, no new data came in, so DISABLE the timer, done. + +In this way, redraws don't happen more than 10x per second, and redraw() is called +only when there's new content to see. + +The redraw rate can be set by the user application using the Fl_Terminal::redraw_rate(), +0.10 being the default. + +Some terminal operations necessarily call redraw() directly, such as interactive mouse +selection, or during user scrolling the terminal's scrollbar, where it's important there's +no delay in what the user sees while interacting directly with the widget. + +*/ diff --git a/examples/simple-terminal.cxx b/examples/simple-terminal.cxx index f1b6e5d5e..f51dc1bb2 100644 --- a/examples/simple-terminal.cxx +++ b/examples/simple-terminal.cxx @@ -1,5 +1,5 @@ // -// Simple Example app using Fl_Simple_Terminal. - erco 10/12/2017 +// Simple Example app using Fl_Terminal. - erco 10/12/2017 // // Copyright 2017 Greg Ercolano. // Copyright 1998-2016 by Bill Spitzak and others. @@ -15,17 +15,17 @@ // https://www.fltk.org/bugs.php // -#include <time.h> //START +#include <time.h> #include <FL/Fl_Double_Window.H> #include <FL/Fl_Box.H> -#include <FL/Fl_Simple_Terminal.H> +#include <FL/Fl_Terminal.H> #define TERMINAL_HEIGHT 120 // Globals Fl_Double_Window *G_win = 0; Fl_Box *G_box = 0; -Fl_Simple_Terminal *G_tty = 0; +Fl_Terminal *G_tty = 0; // Append a date/time message to the terminal every 2 seconds void tick_cb(void *data) { @@ -43,7 +43,7 @@ int main(int argc, char **argv) { "Your app's debugging output in tty below"); // Add simple terminal to bottom of app window for scrolling history of status messages. - G_tty = new Fl_Simple_Terminal(0,200,G_win->w(),TERMINAL_HEIGHT); + G_tty = new Fl_Terminal(0,200,G_win->w(),TERMINAL_HEIGHT); G_tty->ansi(true); // enable use of "\033[32m" G_win->end(); @@ -51,4 +51,4 @@ int main(int argc, char **argv) { G_win->show(); Fl::add_timeout(0.5, tick_cb); return Fl::run(); -} //END +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0db9631be..f9511f358 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -90,6 +90,7 @@ set (CPPFILES Fl_Table.cxx Fl_Table_Row.cxx Fl_Tabs.cxx + Fl_Terminal.cxx Fl_Text_Buffer.cxx Fl_Text_Display.cxx Fl_Text_Editor.cxx diff --git a/src/Fl_Terminal.cxx b/src/Fl_Terminal.cxx new file mode 100644 index 000000000..6b7d6dd87 --- /dev/null +++ b/src/Fl_Terminal.cxx @@ -0,0 +1,3508 @@ +// +// Fl_Terminal.H - A terminal widget for Fast Light Tool Kit (FLTK). +// +// Copyright 2022 by Greg Ercolano. +// Copyright 2023 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +// TODO: horizontal scrollbar +// TODO: double clicking text should make word selection, +// and drag should word-enlarge selection +// FIXME: While dragging a selection, hitting shift stops the selection + +// This must appear above #include <assert.h> +#ifndef NDEBUG +#define NDEBUG // comment out to enable assert() +#endif + +#include <ctype.h> // isdigit +#include <stdlib.h> // malloc +#include <string.h> // strlen +#include <stdarg.h> // vprintf, va_list +#include <assert.h> + +#include <FL/Fl.H> +#include <FL/Fl_Terminal.H> +#include <FL/fl_utf8.h> // fl_utf8len1 +#include <FL/fl_draw.H> +#include <FL/fl_string_functions.h> + +///////////////////////////////// +////// Static Functions ///////// +///////////////////////////////// + +#define MIN(a,b) ((a)<=(b)) ? (a) : (b) // Return smaller of two values +#define MAX(a,b) ((a)>=(b)) ? (a) : (b) // Return larger of two values +#define ABS(a) ((a)<0) ? -(a) : (a) // Return abs value + +// Return val clamped between min and max +static int clamp(int val, int min, int max) + { return (val<min) ? min : (val>max) ? max : val; } + +// Swap integer values a and b +static void swap(int &a, int &b) + { int asave = a; a = b; b = asave; } + +static int normalize(int row, int maxrows) { + row = row % maxrows; + if (row < 0) row = maxrows + row; // negative? index relative to end + return row; +} + +// Color channel management +static int red(Fl_Color val) { return (val & 0xff000000) >> 24; } +static int grn(Fl_Color val) { return (val & 0x00ff0000) >> 16; } +static int blu(Fl_Color val) { return (val & 0x0000ff00) >> 8; } +static Fl_Color rgb(int r,int g,int b) { return (r << 24) | (g << 16) | (b << 8); } + +// Return dim version of color 'val' +static Fl_Color dim_color(Fl_Color val) { + int r = clamp(red(val) - 0x20, 0, 255); + int g = clamp(grn(val) - 0x20, 0, 255); + int b = clamp(blu(val) - 0x20, 0, 255); + //DEBUG ::printf("DIM COLOR: %08x -> %08x\n", val, rgb(r,g,b)); + return rgb(r,g,b); +} + +// Return bold version of color 'val' +static Fl_Color bold_color(Fl_Color val) { + int r = clamp(red(val) + 0x20, 0, 255); + int g = clamp(grn(val) + 0x20, 0, 255); + int b = clamp(blu(val) + 0x20, 0, 255); + //DEBUG ::printf("BOLD COLOR: %08x -> %08x\n", val, rgb(r,g,b)); + return rgb(r,g,b); +} + +// Return an FLTK color for given foreground color index (0..7) +static Fl_Color fltk_fg_color(uchar ci) { + static const Fl_Color xterm_fg_colors_[] = { + 0x00000000, // 0 + 0xd0000000, // 1 - red + 0x00d00000, // 2 - grn + 0xd0d00000, // 3 - yel + 0x0000d000, // 4 - blu + 0xd000d000, // 5 - mag + 0x00d0d000, // 6 - cyn + 0xd0d0d000 // 7 - white + }; + ci &= 0x07; // clamp to array size + return xterm_fg_colors_[ci]; +} + +// Return an FLTK color for the given background color index (0..7) and attribute. +// Background colors should be just a little darker than +// the fg colors to prevent too much brightness clashing +// for 'normal' bg vs fg colors. +// +static Fl_Color fltk_bg_color(uchar ci) { + static const Fl_Color xterm_bg_colors_[] = { + 0x00000000, // 0 + 0xc0000000, // 1 - red + 0x00c00000, // 2 - grn + 0xc0c00000, // 3 - yel + 0x0000c000, // 4 - blu + 0xc000c000, // 5 - mag + 0x00c0c000, // 6 - cyn + 0xc0c0c000 // 7 - white + }; + ci &= 0x07; // clamp to array size + return xterm_bg_colors_[ci]; +} + +// See if an Fl_Boxtype is FL_XXX_FRAME +static bool is_frame(Fl_Boxtype b) { + if (b == FL_UP_FRAME || b == FL_DOWN_FRAME || + b == FL_THIN_UP_FRAME || b == FL_THIN_DOWN_FRAME || + b == FL_ENGRAVED_FRAME || b == FL_EMBOSSED_FRAME || + b == FL_BORDER_FRAME) return true; + return false; +} + +/////////////////////////////////////// +////// Selection Class Methods //////// +/////////////////////////////////////// + +// Ctor +Fl_Terminal::Selection::Selection(void) { + // These are used to set/get the mouse selection + srow_ = scol_ = erow_ = ecol_ = 0; + // FL_PUSH event row/col + push_clear(); + selectionfgcolor_ = FL_BLACK; + selectionbgcolor_ = FL_WHITE; + state_ = 0; + is_selection_ = false; +} + +/** + Return selection start/end. + Ensures (start < end) to allow walking 'forward' thru selection, + left-to-right, top-to-bottom. + + Returns: + - true -- valid selection values returned + - false -- no selection was made, returned values undefined +*/ +bool Fl_Terminal::Selection::get_selection(int &srow,int &scol, + int &erow,int &ecol) const { + srow = srow_; scol = scol_; + erow = erow_; ecol = ecol_; + if (!is_selection_) return false; + // Ensure (start < end) on return + if (srow_ == erow_ && scol_ > ecol_) swap(scol, ecol); + if (srow_ > erow_) + { swap(srow, erow); swap(scol, ecol); } + return true; +} + +// Start new selection at specified row,col +// Always returns true. +// +bool Fl_Terminal::Selection::start(int row, int col) { + srow_ = erow_ = row; + scol_ = ecol_ = col; + state_ = 1; // state: "started selection" + is_selection_ = true; + return true; +} + +// Extend existing selection to row,col +// Returns true if anything changed, false if not. +// +bool Fl_Terminal::Selection::extend(int row, int col) { + // no selection started yet? start and return true + if (!is_selection()) return start(row, col); + state_ = 2; // state: "extending selection" + if (erow_ == row && ecol_ == col) return false; // no change + erow_ = row; + ecol_ = col; + return true; +} + +// End selection (turn dragging() off) +void Fl_Terminal::Selection::end(void) { + state_ = 3; // state: "finished selection" + // Order selection + if (erow_ < srow_) + { swap(srow_, erow_); swap(scol_, ecol_); } + if (erow_ == srow_ && scol_ > ecol_) swap(scol_, ecol_); +} + +// Create a complete selection +void Fl_Terminal::Selection::select(int srow, int scol, int erow, int ecol) { + srow_ = srow; scol_ = scol; + erow_ = erow; ecol_ = ecol; + state_ = 3; // state: "finished selection" + is_selection_ = true; +} + +// Clear selection +// Returns true if there was a selection, false if there wasn't +// +bool Fl_Terminal::Selection::clear(void) { + bool was_selected = is_selection(); // save for return + srow_ = scol_ = erow_ = ecol_ = 0; + state_ = 0; + is_selection_ = false; + return was_selected; +} + +// Scroll the selection up(+)/down(-) number of rows +void Fl_Terminal::Selection::scroll(int nrows) { + if (is_selection()) { + srow_ -= nrows; + erow_ -= nrows; + // Selection scrolled off? clear selection + if (srow_ < 0 || erow_ < 0) clear(); + } +} + +/////////////////////////////////////// +////// EscapeSeq Class Methods //////// +/////////////////////////////////////// + +// Append char to buff[] safely (with bounds checking) +// Returns: +// success - ok +// fail - buffer full/overflow +// +int Fl_Terminal::EscapeSeq::append_buff(char c) { + if (buffp_ >= buffendp_) return fail; // end of buffer reached? + *buffp_++ = c; + *buffp_ = 0; // keep buff[] null terminated + return success; +} + +// Append whatever integer string is at valbuffp into vals_[] safely w/bounds checking +// Assumes valbuffp points to a null terminated string. +// Returns: +// success - parsed ok +// fail - error occurred (non-integer, or vals_[] full) +// +int Fl_Terminal::EscapeSeq::append_val(void) { + if (vali_ >= maxvals) // vals_[] full? + { vali_ = maxvals-1; return fail; } // clamp index, fail + if (!valbuffp_ || (*valbuffp_ == 0)) // no integer to parse? e.g. ESC[m, ESC[;m + { vals_[vali_] = 0; return success; } // zero in array, do not inc vali + if (sscanf(valbuffp_, "%d", &vals_[vali_]) != 1) // Parse integer into vals_[] + { return fail; } // fail if parsed a non-integer + vals_[vali_] &= 0x3ff; // sanity: enforce int in range 0 ~ 1023 (prevent DoS attack) + if (++vali_ >= maxvals) // advance val index, fail if too many vals + { vali_ = maxvals-1; return fail; } // clamp + fail + valbuffp_ = 0; // parsed val ok, reset valbuffp to NULL + return success; +} + +// Ctor +Fl_Terminal::EscapeSeq::EscapeSeq(void) { + reset(); + save_row_ = -1; // only in ctor + save_col_ = -1; +} + +// Reset the class +// Named reset to not be confused with clear() screen/line/etc +// +void Fl_Terminal::EscapeSeq::reset(void) { + esc_mode_ = 0; // disable ESC mode, so parse_in_progress() returns false + csi_ = false; // CSI off until '[' received + buffp_ = buff_; // point to beginning of buffer + buffendp_ = buff_ + (maxbuff - 1); // point to end of buffer + valbuffp_ = 0; // disable val ptr (no vals parsed yet) + vali_ = 0; // zero val index + buff_[0] = 0; // null terminate buffer + vals_[0] = 0; // first val[] 0 + memset(vals_, 0, sizeof(vals_)); +} + +// Return current escape mode. +// This is really only valid after parse() returns 'completed'. +// After a reset() this will return 0. +// +char Fl_Terminal::EscapeSeq::esc_mode(void) const { return esc_mode_; } + +// Set current escape mode. +void Fl_Terminal::EscapeSeq::esc_mode(char val) { esc_mode_ = val; } + +// Return the total vals parsed. +// This is really only valid after parse() returns 'completed'. +// +int Fl_Terminal::EscapeSeq::total_vals(void) const { return vali_; } + +// Return the value at index i. +// i is not range checked; it's assumed 0 <= i < total_vals(). +// It is only valid to call this after parse() returns 'completed'. +// +int Fl_Terminal::EscapeSeq::val(int i) const { return vals_[i]; } + +// See if we're in the middle of parsing an ESC sequence +bool Fl_Terminal::EscapeSeq::parse_in_progress(void) const { + return (esc_mode_ == 0) ? false : true; +} + +// See if we're in the middle of parsing an ESC sequence +bool Fl_Terminal::EscapeSeq::is_csi(void) const { return csi_; } + +// Return with default value (if none) or vals[0] (if at least one val spec'd). +// Handles default for single values (e.g. ESC[#H vs. ESC[H) +// vals[0] is clamped between 0 and 'max' +// +int Fl_Terminal::EscapeSeq::defvalmax(int dval, int max) const { + if (total_vals() == 0) return dval; + else return clamp(vals_[0], 0, max); +} + +// Save row,col for later retrieval +void Fl_Terminal::EscapeSeq::save_cursor(int row, int col) { + save_row_ = row; + save_col_ = col; +} + +// Return saved position in row,col +void Fl_Terminal::EscapeSeq::restore_cursor(int &row, int &col) { + row = save_row_; + col = save_col_; +} + +// Handle parsing an escape sequence. +// Call this only if parse_in_progress() is true. +// Passing ESC does a reset() and sets esc_mode() to ESC. +// When a full escape sequence has been parsed, 'completed' is returned (see below). +// +// Returns: +// fail - error occurred: escape sequence invalid, class is reset() +// success - parsing ESC sequence OK so far, still in progress/not done yet +// completed - complete ESC sequence was parsed, esc_mode() will be the operation, e.g. +// 'm' - <ESC>[1m -- is_csi() will be true, val() has value(s) parsed +// 'A' - <ESC>A -- is_csi() will be false (no vals) +// +int Fl_Terminal::EscapeSeq::parse(char c) { + // NOTE: During parsing esc_mode() will be: + // 0 - reset/not parsing + // 0x1b - ESC received, expecting next one of A/B/C/D or '[' + // '[' - actively parsing CSI sequence, e.g. ESC[ + // + // At the /end/ of parsing, after 'completed' is returned, + // esc_mode() will be the mode setting char, e.g. 'm' for 'ESC[0m', etc. + // + if (c == 0) { // NULL? (caller should really never send us this) + return success; // do nothing -- leave state unchanged, return 'success' + } else if (c == 0x1b) { // ESC at ANY time resets class/begins new ESC sequence + reset(); + esc_mode(0x1b); + if (append_buff(c) < 0) goto pfail; // save ESC in buf + return success; + } else if (c < ' ' || c >= 0x7f) { // any other control or binary characters? + goto pfail; // reset + fail out of esc sequence parsing + } + // Whatever the character is, handle it depending on esc_mode.. + if (esc_mode() == 0x1b) { // in ESC mode? + if (c == '[') { // <ESC>[? CSI (Ctrl Seq Introducer) + esc_mode(c); // switch to parsing mode for ESC[#;#;#.. + csi_ = true; // this is now a CSI sequence + vali_ = 0; // zero vals_[] index + valbuffp_ = 0; // valbuffp NULL (no vals yet) + if (append_buff(c) < 0) goto pfail; // save '[' in buf + return success; // success + } else if ( (c >= '@' && c <= 'Z') || // C1 control code (e.g. <ESC>D, <ESC>c, etc) + (c >= 'a' && c <= 'z') ) { + esc_mode(c); // save op in esc_mode() for caller to see + csi_ = false; // NOT a CSI sequence + vali_ = 0; + valbuffp_ = 0; // valbuffp NULL (no vals yet) + if (append_buff(c) < 0) goto pfail; // save op in buf + return completed; // completed sequence + } else { // ESCx? + goto pfail; // not supported + } + } else if (esc_mode() == '[') { // '[' mode? e.g. ESC[... aka. is_csi() + if (c == ';') { // ';' indicates end of a value, e.g. ESC[0;2.. + if (append_val() < 0) goto pfail; // append value parsed so far, vali gets inc'ed + if (append_buff(c) < 0) goto pfail; // save ';' in buf + return success; + } + if (isdigit(c)) { // parsing an integer? + if (!valbuffp_) // valbuffp not set yet? + { valbuffp_ = buffp_; } // point to first char in integer string + if (append_buff(c) < 0) goto pfail; // add value to buffer + return success; + } + // Not a ; or digit? fall thru to [A-Z,a-z] check + } else { // all other esc_mode() chars are fail/unknown + goto pfail; + } + if ( ( c >= '@' && c<= 'Z') || // ESC#X or ESC[...X, where X is [A-Z,a-z]? + ( c >= 'a' && c<= 'z') ) { + if (append_val() < 0 ) goto pfail; // append any trailing vals just before letter + if (append_buff(c) < 0 ) goto pfail; // save letter in buffer + esc_mode(c); // change mode to the mode setting char + return completed; // completed/done + } + // Any other chars? reset+fail +pfail: + reset(); + return fail; +} + +////////////////////////////////////// +///// CharStyle Class Methods //////// +////////////////////////////////////// + +// Ctor +Fl_Terminal::CharStyle::CharStyle(void) { + attrib_ = 0; + flags_ = 0; + defaultfgcolor_ = 0xd0d0d000; // off white + defaultbgcolor_ = 0xffffffff; // special color: doesn't draw, 'shows thru' to box() + fgcolor_ = defaultfgcolor_; + bgcolor_ = defaultbgcolor_; + flags_ |= (FG_XTERM | BG_XTERM); + fontface_ = FL_COURIER; + fontsize_ = 14; + update(); // updates fontheight_, fontdescent_, charwidth_ +} + +// Update fontheight/descent cache whenever font changes +void Fl_Terminal::CharStyle::update(void) { + // cache these values + fl_font(fontface_, fontsize_); + fontheight_ = int(fl_height() + 0.5); + fontdescent_ = int(fl_descent() + 0.5); + charwidth_ = int(fl_width("X") + 0.5); +} + +// Return fg color +Fl_Color Fl_Terminal::CharStyle::fgcolor(void) const { + return fgcolor_; +} + +// Return bg color +Fl_Color Fl_Terminal::CharStyle::bgcolor(void) const { + return bgcolor_; +} + +// Return only the color bit flags +// Only the color bits of 'inflags' are modified with our color bits. +// +uchar Fl_Terminal::CharStyle::colorbits_only(uchar inflags) const { + return (inflags & ~COLORMASK) | (flags_ & COLORMASK); // add color bits only +} + +void Fl_Terminal::CharStyle::fgcolor_uchar(uchar val) { + fgcolor_ = fltk_fg_color(val); + set_flag(FG_XTERM); +} + +void Fl_Terminal::CharStyle::bgcolor_uchar(uchar val) { + bgcolor_ = fltk_bg_color(val); + set_flag(BG_XTERM); +} + +/////////////////////////////////// +///// Cursor Class Methods //////// +/////////////////////////////////// + +// Is cursor at display row,col? +bool Fl_Terminal::Cursor::is_rowcol(int drow,int dcol) const { + return(drow == row_ && dcol == col_); +} + +// Scroll the cursor row up(+)/down(-) number of rows +void Fl_Terminal::Cursor::scroll(int nrows) { + row_ = MAX(row_ - nrows, 0); // don't let (row_<0) +} + +///////////////////////////////////// +///// Utf8Char Class Methods //////// +///////////////////////////////////// + +// Ctor +Fl_Terminal::Utf8Char::Utf8Char(void) { + text_[0] = ' '; + len_ = 1; + attrib_ = 0; + flags_ = 0; + fgcolor_ = 0xffffff00; + bgcolor_ = 0xffffffff; // special color: doesn't draw, 'shows thru' to box() +} + +// copy ctor +Fl_Terminal::Utf8Char::Utf8Char(const Utf8Char& src) { + // local instance not initialized yet; init first, then copy text + text_[0] = ' '; + len_ = 1; + attrib_ = src.attrib_; + flags_ = src.flags_; + fgcolor_ = src.fgcolor_; + bgcolor_ = src.bgcolor_; + text_utf8_(src.text_utf8(), src.length()); // copy the src text +} + +// assignment +Fl_Terminal::Utf8Char& Fl_Terminal::Utf8Char::operator=(const Utf8Char& src) { + // local instance is already initialized, so just change its contents + text_utf8_(src.text_utf8(), src.length()); // local copy src text + attrib_ = src.attrib_; + flags_ = src.flags_; + fgcolor_ = src.fgcolor_; + bgcolor_ = src.bgcolor_; + return *this; +} + +// dtor +Fl_Terminal::Utf8Char::~Utf8Char(void) { + len_ = 0; +} + +// Set 'text_' to valid UTF-8 string 'text'. +// +// text_ must not be NULL, and len must be in range: 1 <= len <= max_utf8(). +// NOTE: Caller must handle such checks, and use handle_unknown_char() +// for invalid chars. +// +void Fl_Terminal::Utf8Char::text_utf8_(const char *text, int len) { + memcpy(text_, text, len); + len_ = len; // update new length +} + +// Set UTF-8 string for this char. +// +// text_ must not be NULL, and len must be in range: 1 <= len <= max_utf8(). +// NOTE: Caller must handle such checks, and use handle_unknown_char() +// for invalid chars. +// +void Fl_Terminal::Utf8Char::text_utf8(const char *text, + int len, + const CharStyle& style) { + text_utf8_(text, len); // updates text_, len_ + fl_font(style.fontface(), style.fontsize()); // need font to calc UTF-8 width + attrib_ = style.attrib(); + flags_ = style.colorbits_only(flags_); + fgcolor_ = style.fgcolor(); + bgcolor_ = style.bgcolor(); +} + +// Set char to single printable ASCII character 'c' +// 'c' must be "printable" ASCII in the range (0x20 <= c <= 0x7e). +// Anything outside of that is silently ignored. +// +void Fl_Terminal::Utf8Char::text_ascii(char c, const CharStyle& style) { + // Signed char vals above 0x7f are /negative/, so <0x20 check covers those + if (c < 0x20 || c >= 0x7e) return; // ASCII non-printable? + text_utf8(&c, 1, style); +} + +// Set fl_font() based on specified style for this char's attribute +void Fl_Terminal::Utf8Char::fl_font_set(const CharStyle& style) const { + int face = style.fontface() | + ((attrib_ & Fl_Terminal::BOLD) ? FL_BOLD : 0) | + ((attrib_ & Fl_Terminal::ITALIC) ? FL_ITALIC : 0); + fl_font(face, style.fontsize()); +} + +// Return the foreground color as an fltk color +Fl_Color Fl_Terminal::Utf8Char::fgcolor(void) const { + return fgcolor_; +} + +// Return the background color as an fltk color +Fl_Color Fl_Terminal::Utf8Char::bgcolor(void) const { + return bgcolor_; +} + +// Return the width of this character in floating point pixels +// +// WARNING: Uses current font, so assumes fl_font(face,size) +// has already been set to current font! +// +double Fl_Terminal::Utf8Char::pwidth(void) const { + return fl_width(text_, len_); +} + +// Return the width of this character in integer pixels +// +// WARNING: Uses current font, so assumes fl_font(face,size) +// has already been set to current font! +// +int Fl_Terminal::Utf8Char::pwidth_int(void) const { + return int(fl_width(text_, len_) + 0.5); +} + +// Return color \p col, possibly influenced by BOLD or DIM attributes \p attr. +// If a \p grp widget is specified (i.e. not NULL), don't let the color \p col be +// influenced by the attribute bits /if/ \p col matches the \p grp widget's own color(). +// +Fl_Color Fl_Terminal::Utf8Char::attr_color(Fl_Color col, const Fl_Widget *grp) const { + // Don't modify color if it's the special 'see thru' color 0x0 or widget's color() + if (grp && ((col == 0xffffffff) || (col == grp->color()))) return grp->color(); + switch (attrib_ & (Fl_Terminal::BOLD|Fl_Terminal::DIM)) { + case 0: return col; // not bold or dim? no change + case Fl_Terminal::BOLD: return bold_color(col); // bold? use bold_color() + case Fl_Terminal::DIM : return dim_color(col); // dim? use dim_color() + default: return col; // bold + dim? cancel out + } +} + +// Return the fg color of char \p u8c possibly influenced by BOLD or DIM. +// If a \p grp widget is specified (i.e. not NULL), don't let the color \p col be +// influenced by the attribute bits /if/ \p col matches the \p grp widget's own color(). +// +Fl_Color Fl_Terminal::Utf8Char::attr_fg_color(const Fl_Widget *grp) const { + if (grp && (fgcolor_ == 0xffffffff)) // see thru color? + { return grp->color(); } // return grp's color() + return (flags_ & Fl_Terminal::FG_XTERM) // fg is an xterm color? + ? attr_color(fgcolor(), grp) // ..use attributes + : fgcolor(); // ..ignore attributes. +} + +Fl_Color Fl_Terminal::Utf8Char::attr_bg_color(const Fl_Widget *grp) const { + if (grp && (bgcolor_ == 0xffffffff)) // see thru color? + { return grp->color(); } // return grp's color() + return (flags_ & Fl_Terminal::BG_XTERM) // bg is an xterm color? + ? attr_color(bgcolor(), grp) // ..use attributes + : bgcolor(); // ..ignore attributes. +} + + +//////////////////////////////////// +///// RingBuffer Class Methods ///// +//////////////////////////////////// + +// Create a new copy of the buffer with different row/col sizes +// Preserves old contents of display and history in use. +// +// The old buffer might have an offset and the hist/disp might wrap +// around the end of the ring. The NEW buffer's offset will be zero, +// so the hist/disp do NOT wrap around, making the move operation easier to debug. +// +// The copy preservation starts at the LAST ROW in display of both old (src) and new (dst) +// buffers, and copies rows in reverse until hist_use_srow() reached, or if we hit top +// of the new history (index=0), which ever comes first. So in the following where the +// display is being enlarged, the copy preservation starts at "Line 5" (bottom of display) +// and works upwards, ending at "Line 1" (top of history use): +// +// OLD (SRC) NEW (DST) +// _____________ _____________ ___ +// | x x x x x x | | x x x x x x | ʌ 'x' indicates +// | Line 1 | ─┐ | x x x x x x | | hist_rows unused history +// | Line 2 | └─> | Line 1 | v buffer memory. +// |-------------| |-------------| --- +// | Line 3 | | Line 2 | ʌ +// | Line 4 | | Line 3 | | +// | Line 5 | ─┐ | Line 4 | | disp_rows +// |_____________| └─> | Line 5 | | +// |_____________| _v_ +// +void Fl_Terminal::RingBuffer::new_copy(int drows, int dcols, int hrows, const CharStyle& style) { + // Create new buffer + int addhist = disp_rows() - drows; // adjust history use + int new_ring_rows = (drows+hrows); + int new_hist_use = clamp(hist_use_ + addhist, 0, hrows); // clamp incase new_hist_rows smaller than old + int new_nchars = (new_ring_rows * dcols); + Utf8Char *new_ring_chars = new Utf8Char[new_nchars]; // Create new ring buffer (all blanks) + // Preserve old contents in new buffer + int dst_cols = dcols; + int src_stop_row = hist_use_srow(); + int tcols = MIN(ring_cols(), dcols); + int src_row = hist_use_srow() + hist_use_ + disp_rows_ - 1; // use row#s relative to hist_use_srow() + int dst_row = new_ring_rows - 1; + // Copy rows: working up from bottom of disp, stop at top of hist + while ((src_row >= src_stop_row) && (dst_row >= 0)) { + Utf8Char *src = u8c_ring_row(src_row); + Utf8Char *dst = new_ring_chars + (dst_row*dst_cols); + for (int col=0; col<tcols; col++ ) *dst++ = *src++; + --src_row; + --dst_row; + } + // Install new buffer: dump old, install new, adjust internals + if (ring_chars_) delete[] ring_chars_; + ring_chars_ = new_ring_chars; + ring_rows_ = new_ring_rows; + ring_cols_ = dcols; + nchars_ = new_nchars; + hist_rows_ = hrows; + hist_use_ = new_hist_use; + disp_rows_ = drows; + offset_ = 0; // for new buffer, we used a zero offset +} + +// Clear the class, delete previous ring if any +void Fl_Terminal::RingBuffer::clear(void) { + if (ring_chars_) delete[] ring_chars_; // dump our ring + ring_chars_ = 0; + ring_rows_ = 0; + ring_cols_ = 0; + nchars_ = 0; + hist_rows_ = 0; + hist_use_ = 0; + disp_rows_ = 0; + offset_ = 0; +} + +// Clear history +void Fl_Terminal::RingBuffer::clear_hist(void) { + hist_use_ = 0; +} + +// Default ctor +Fl_Terminal::RingBuffer::RingBuffer(void) { + ring_chars_ = 0; + clear(); +} + +// Ctor with specific sizes +Fl_Terminal::RingBuffer::RingBuffer(int drows, int dcols, int hrows) { + // Start with cleared buffer first.. + ring_chars_ = 0; + clear(); + // ..then create. + create(drows, dcols, hrows); +} + +// Dtor +Fl_Terminal::RingBuffer::~RingBuffer(void) { + if (ring_chars_) delete[] ring_chars_; + ring_chars_ = NULL; +} + +// See if 'grow' is within the history buffer +// It's assumed grow is in the range 0 .. hist_rows()-1. +// +bool Fl_Terminal::RingBuffer::is_hist_ring_row(int grow) const { + grow %= ring_rows_; + grow -= offset_; // move into positive space + if (grow < 0) { grow = (ring_rows_ + grow); } + int htop = 0; + int hbot = (hist_rows_ - 1); + return ((grow >= htop) && (grow <= hbot)); +} + +// See if 'grow' is within the display buffer +// It's assumed grow is in the range hist_rows() .. ring_rows()-1. +// +bool Fl_Terminal::RingBuffer::is_disp_ring_row(int grow) const { + grow %= ring_rows_; + grow -= offset_; + if (grow < 0) { grow = (ring_rows_ + grow); } + int dtop = hist_rows_; + int dbot = hist_rows_ + disp_rows_ - 1; + return ((grow >= dtop) && (grow <= dbot)); +} + +// Move display row from src_row to dst_row +void Fl_Terminal::RingBuffer::move_disp_row(int src_row, int dst_row) { + Utf8Char *src = u8c_disp_row(src_row); + Utf8Char *dst = u8c_disp_row(dst_row); + for (int col=0; col<disp_cols(); col++) *dst++ = *src++; +} + +// Clear the display row 'drow' using specified CharStyle 'style' +void Fl_Terminal::RingBuffer::clear_disp_row(int drow, const CharStyle& style) { + int row = hist_rows_ + drow + offset_; + Utf8Char *u8c = u8c_ring_row(row); + for (int col=0; col<disp_cols(); col++) u8c++->clear(style); +} + +// Scroll the ring buffer up or down #rows, using 'style' for empty rows +// > Positive rows scroll "up", moves top line(s) into history, clears bot line(s) +// Increases hist_use (unless maxed out). +// > Negative rows scroll "down", clears top line(s), history unaffected +// +void Fl_Terminal::RingBuffer::scroll(int rows, const CharStyle& style) { + if (rows > 0) { + // Scroll up into history + // Example: scroll(2): + // + // BEFORE AFTER + // --------------- --------------- + // | H i s t | --- | x x x x x x x | \_ blanked rows + // | | \ | x x x x x x x | / + // | | ---> |---------------| <- disp_erow() + // | | | H i s t | + // | | | | + // disp_srow() -> |---------------| --- | 0001 | + // | 0001 | \ | 0002 | + // | 0002 | ---> |---------------| <- disp_srow() + // | 0003 | | 0003 | + // | 0004 | | 0004 | + // | 0005 | --- | 0005 | + // | 0006 | \ | 0006 | + // disp_erow() -> --------------- ---> --------------- + // + // \______/ + // Simple + // Offset + rows = clamp(rows, 1, disp_rows()); // sanity + // Scroll up into history + offset_ = (offset_ + rows) % ring_rows_; + // Adjust hist_use, clamp to max + hist_use_ = clamp(hist_use_ + rows, 0, hist_rows_); + // Clear exposed lines at bottom + int srow = (disp_rows() - rows) % disp_rows(); + int erow = disp_rows(); + for (int row=srow; row<erow; row++) clear_disp_row(row, style); + } else { + // Scroll down w/out affecting history + // To leave history unaffect, we must move memory. + // Start at bottom row [A] and work up to top row [B]. + // + // Example: scroll(-2): + // + // BEFORE AFTER + // ----------------- ----------------- _ + // | 0001 [B] | ---┐ | x x x x x x x x | \_ blanked + // | 0002 | | | x x x x x x x x | _/ rows + // | 0003 | └---> | 0001 | + // | 0004 [A] | ---┐ | 0002 | + // | 0005 | | | 0003 | + // | 0006 | └---> | 0004 | + // ----------------- ----------------- + // \______/ + // Memory + // move + rows = clamp(-rows, 1, disp_rows()); // make rows positive + sane + for (int row=disp_rows()-1; row>=0; row--) { + int src_row = (row - rows); + int dst_row = row; + if (src_row >= 0) move_disp_row(row-rows, dst_row); // move rows + else clear_disp_row(dst_row, style); // hit top? blank the rest + } + } +} + +// Return UTF-8 char for 'row' in the ring +// Scrolling offset is NOT applied; this is raw access to the ring's rows. +// +// Example: +// // Walk ALL rows in the ring buffer.. +// for (int row=0; row<ring.rows(); row++) { +// Utf8Char *u8c = ring.u8c_ring_row(row); +// .. +// } +// +const Fl_Terminal::Utf8Char* Fl_Terminal::RingBuffer::u8c_ring_row(int row) const { + row = normalize(row, ring_rows()); + assert(row >= 0 && row < ring_rows_); + return &ring_chars_[row * ring_cols()]; +} + +// Return UTF-8 char for beginning of 'row' in the history buffer. +// Example: +// // Walk ALL rows in history.. +// for (int hrow=0; hrow<ring.hrows(); hrow++) { +// Utf8Char *u8c = u8c_hist_row(hrow); +// .. +// } +// +const Fl_Terminal::Utf8Char* Fl_Terminal::RingBuffer::u8c_hist_row(int hrow) const { + int rowi = normalize(hrow, hist_rows()); + rowi = (rowi + offset_) % ring_rows_; + assert(rowi >= 0 && rowi <= ring_rows_); + return &ring_chars_[rowi * ring_cols()]; +} + +// Special case to walk the "in use" rows of the history +// Example: +// // Walk the "in use" rows of history.. +// for (int hrow=0; hrow<ring.hist_use(); hrow++) { +// Utf8Char *u8c = u8c_hist_use_row(hrow); +// .. +// } +// +const Fl_Terminal::Utf8Char* Fl_Terminal::RingBuffer::u8c_hist_use_row(int hurow) const { + if (hist_use_ == 0) return 0; // history is empty! (caller is dumb to ask) + hurow = hurow % hist_use_; // normalize indexing within history in use + hurow = hist_rows_ - hist_use_ + hurow; // index hist_use rows from end history + hurow = (hurow + offset_) % ring_rows_; // convert to absolute index in ring_chars_[] + assert(hurow >= 0 && hurow <= hist_use()); + return &ring_chars_[hurow * ring_cols()]; +} + +// Return UTF-8 char for beginning of 'row' in the display buffer +// Example: +// // Walk ALL rows in display.. +// for (int drow=0; drow<ring.drows(); drow++) { +// Utf8Char *u8c = u8c_disp_row(drow); +// .. +// } +// +const Fl_Terminal::Utf8Char* Fl_Terminal::RingBuffer::u8c_disp_row(int drow) const { + int rowi = normalize(drow, disp_rows()); + rowi = (hist_rows_ + rowi + offset_) % ring_rows_; // display starts at end of history + assert(rowi >= 0 && rowi <= ring_rows_); + return &ring_chars_[rowi * ring_cols()]; +} + +// non-const versions of the above +Fl_Terminal::Utf8Char* Fl_Terminal::RingBuffer::u8c_ring_row(int row) + { return const_cast<Utf8Char*>(const_cast<const RingBuffer*>(this)->u8c_ring_row(row)); } + +Fl_Terminal::Utf8Char* Fl_Terminal::RingBuffer::u8c_hist_row(int hrow) + { return const_cast<Utf8Char*>(const_cast<const RingBuffer*>(this)->u8c_hist_row(hrow)); } + +Fl_Terminal::Utf8Char* Fl_Terminal::RingBuffer::u8c_hist_use_row(int hurow) + { return const_cast<Utf8Char*>(const_cast<const RingBuffer*>(this)->u8c_hist_use_row(hurow)); } + +Fl_Terminal::Utf8Char* Fl_Terminal::RingBuffer::u8c_disp_row(int drow) + { return const_cast<Utf8Char*>(const_cast<const RingBuffer*>(this)->u8c_disp_row(drow)); } + +// Resize ring buffer by creating new one, dumping old (if any). +// Input: +// drows -- display height in lines of text (rows) +// dcols -- display width in characters (columns) +// hrows -- scrollback history size in lines of text (rows) +// +void Fl_Terminal::RingBuffer::create(int drows, int dcols, int hrows) { + clear(); + // History + hist_rows_ = hrows; + hist_use_ = 0; + // Display + disp_rows_ = drows; + // Ring buffer + ring_rows_ = hist_rows_ + disp_rows_; + ring_cols_ = dcols; + nchars_ = ring_rows_ * ring_cols_; + ring_chars_ = new Utf8Char[nchars_]; +} + +// Resize the buffer, preserve previous contents as much as possible +void Fl_Terminal::RingBuffer::resize(int drows, int dcols, int hrows, const CharStyle& style) { + // If dcols or (drows+hrows) changed, make a NEW buffer and copy old contents. + // New copy will have xxxx_rows/cols and nchars adjusted. + // + if (dcols != disp_cols() || // cols changed size? + (drows+hrows) != (disp_rows()+hist_rows()) ) { // total #rows changed? + new_copy(drows, dcols, hrows, style); + } else { + // Cols and total rows the same, probably just changed disp/hist ratio + int addhist = disp_rows() - drows; // adj hist_use smaller if disp enlarged + hist_rows_ = hrows; // adj hist rows for new value + disp_rows_ = drows; // adj disp rows for new value + hist_use_ = clamp(hist_use_ + addhist, 0, hrows); + } +} + +// Change the display rows. Use style for new rows, if any. +void Fl_Terminal::RingBuffer::change_disp_rows(int drows, const CharStyle& style) + { resize(drows, ring_cols(), hist_rows(), style); } + +// Change the display columns. Use style for new columns, if any. +void Fl_Terminal::RingBuffer::change_disp_cols(int dcols, const CharStyle& style) + { resize(disp_rows(), dcols, hist_rows(), style); } + +///////////////////////////////////// +///// Fl_Terminal Class Methods ///// +///////////////////////////////////// + +// Return u8c for beginning of a row inside the ring. +// 'grow' is 'globally' indexed (relative to the beginning of the ring buffer), +// and so can access ANY character in the entire ring buffer (hist or disp) +// by its global index, which is to say without any scrolling offset applied. +// Should really ONLY be used for making a complete copy of the ring. +// +const Fl_Terminal::Utf8Char* Fl_Terminal::u8c_ring_row(int grow) const + { return ring_.u8c_ring_row(grow); } + +// Return u8c for beginning of a row inside the history. +// 'hrow' is indexed relative to the beginning of the history buffer. +// +const Fl_Terminal::Utf8Char* Fl_Terminal::u8c_hist_row(int hrow) const + { return ring_.u8c_hist_row(hrow); } + +// Return u8c for beginning of a row inside the 'in use' history. +// 'hurow' is indexed relative to the beginning of the 'in use' buffer. +// +const Fl_Terminal::Utf8Char* Fl_Terminal::u8c_hist_use_row(int hurow) const + { return ring_.u8c_hist_use_row(hurow); } + +// Return u8c for beginning of a row inside the display +// 'drow' is indexed relative to the beginning of the display buffer. +// +const Fl_Terminal::Utf8Char* Fl_Terminal::u8c_disp_row(int drow) const + { return ring_.u8c_disp_row(drow); } + +// non-const versions of the above +Fl_Terminal::Utf8Char* Fl_Terminal::u8c_ring_row(int grow) + { return const_cast<Utf8Char*>(const_cast<const Fl_Terminal*>(this)->u8c_ring_row(grow)); } + +Fl_Terminal::Utf8Char* Fl_Terminal::u8c_hist_row(int hrow) + { return const_cast<Utf8Char*>(const_cast<const Fl_Terminal*>(this)->u8c_hist_row(hrow)); } + +Fl_Terminal::Utf8Char* Fl_Terminal::u8c_hist_use_row(int hurow) + { return const_cast<Utf8Char*>(const_cast<const Fl_Terminal*>(this)->u8c_hist_use_row(hurow)); } + +Fl_Terminal::Utf8Char* Fl_Terminal::u8c_disp_row(int drow) + { return const_cast<Utf8Char*>(const_cast<const Fl_Terminal*>(this)->u8c_disp_row(drow)); } + +// Create ring buffer. +// Input: +// drows -- display height in lines of text (rows) +// dcols -- display width in characters (columns) +// hrows -- scrollback history size in lines of text (rows) +// +// NOTE: Caller should call update_screen() at some point +// to fix the scrollbar and other things. +// +void Fl_Terminal::create_ring(int drows, int dcols, int hrows) { + // recreate tabstops if col width being changed + if (dcols != ring_.ring_cols()) init_tabstops(dcols); + // recreate ring (dumps old) + ring_.create(drows, dcols, hrows); + // ensure cursor starts at home position + cursor_.home(); +} + +// Return the Utf8Char* for character under cursor. +Fl_Terminal::Utf8Char* Fl_Terminal::u8c_cursor(void) { + return u8c_disp_row(cursor_.row()) + cursor_.col(); +} + +// Return scrollbar width if visible, or 0 if not visible +int Fl_Terminal::vscroll_width(void) const { + return(vscroll_->visible() ? vscroll_->w() : 0); +} + +// Initialize tabstops for terminal +// NOTE: 'newsize' should always be at least 'ring_cols()'.. +// +void Fl_Terminal::init_tabstops(int newsize) { + if (newsize > tabstops_size_) { // enlarge? + char *oldstops = tabstops_; // save old stops + int oldsize = tabstops_size_; // save old size + tabstops_ = (char*)malloc(newsize); // alloc new + for (int t=0; t<newsize; t++) { // init new tabstops: + tabstops_[t] = (oldstops && t<oldsize) + ? oldstops[t] // copy old + : ((t % 8) == 0) ? 1 : 0; // new defaults + } + if (oldstops) free((void*)oldstops); // dump old stops + tabstops_size_ = newsize; + } else { + // Same size or smaller? Do nothing -- just keep old tabstops + } +} + +// Reset all tabstops to default 8th char +void Fl_Terminal::default_tabstops(void) { + for (int t=1; t<tabstops_size_; t++) // t=1: skip 0 + tabstops_[t] = ((t % 8) == 0) ? 1 : 0; // every 8th char is a tabstop +} + +// Clear all tabstops +void Fl_Terminal::clear_all_tabstops(void) { + memset(tabstops_, 0, tabstops_size_); +} + +// Set/clear tabstop at current cursor x position +// val: 0 clears tabstop, 1 sets tabstop +// +void Fl_Terminal::set_tabstop(void) { + int index = clamp(cursor_col(), 0, tabstops_size_-1); // clamp cursor pos + tabstops_[index] = 1; // set/clr tabstop +} + +// Clear tabstop at current cursor x position +void Fl_Terminal::clear_tabstop(void) { + int index = clamp(cursor_col(), 0, tabstops_size_-1); // clamp cursor pos + tabstops_[index] = 0; // clear tabstop +} + +// Update the scrollbar based on screen, history buffer, etc +// Scrollbar should range from 0 (bottom) to hist_rows (top), +// the scrollbar's value indicating how far back into history. +// +void Fl_Terminal::update_scrollbar(void) { + int value_before = vscroll_->value(); + // Enforce minimum tabsize of 10 or width of scrollbar + // The minimum vert size of tab should be scrollbar's width, + // but not smaller than 10 pixels, so user can grab it easily. + { + float tabsize = disp_rows() / float(disp_rows() + history_use()); + float minpix = float(MAX(10, vscroll_->w())); // scroll width preferred, 10pix minimum + float minfrac = minpix/vscroll_->h(); // scroll wants a fraction, so convert + tabsize = MAX(minfrac, tabsize); // use the best fractional size + vscroll_->slider_size(tabsize); + } + vscroll_->range(hist_use(), 0); // 'minimum' is larger than 'max' + if (value_before == 0) vscroll_->value(0); // was at bottom? stay at bottom + // Ensure scrollbar in proper position + update_screen_xywh(); // ensure scrn_ up to date first + int sx = scrn_.r() + margin_.right(); + int sy = scrn_.y() - margin_.top(); + int sw = scrollbar_actual_size(); + int sh = scrn_.h() + margin_.top() + margin_.bottom(); + if (vscroll_->x() != sx || + vscroll_->y() != sy || + vscroll_->w() != sw || + vscroll_->h() != sh) { + vscroll_->resize(sx, sy, sw, sh); + init_sizes(); // tell Fl_Group child changed size.. + update_screen_xywh(); // ensure scrn_ is aware of sw change + display_modified(); // redraw Fl_Terminal since scroller changed size + } + vscroll_->redraw(); // redraw scroll always +} + +// Refit the display to match screen +void Fl_Terminal::refit_disp_to_screen(void) { + int dh = h_to_row(scrn_.h()); + int dw = MAX(w_to_col(scrn_.w()), disp_cols()); // enlarge cols only + int drows = clamp(dh, 2, dh); // 2 rows minimum + int dcols = clamp(dw, 10, dw); // 10 cols minimum + int drow_diff = drows - ring_.disp_rows(); // change in rows? + ring_.resize(drows, dcols, hist_rows(), current_style_); + cursor_.scroll(-drow_diff); + clear_mouse_selection(); + update_screen(false); +} + +// Resize the display's vertical size to (drows). +// When enlarging / shrinking, KEEP BOTTOM OF DISPLAY THE SAME, e.g. +// +// Display Display +// BEFORE: SMALLER: +// ___________ ___________ +// | Hist -3 | ʌ | Hist -3 | ʌ +// | Hist -2 | |- 3 | Hist -2 | | +// | Hist -1 | v | Hist -1 | |-- new hist size +// |-----------| ---┐ | Line 1 | | +// | Line 1 | | | Line 2 | v +// | Line 2 | └---> |-----------| +// | Line 3 | | Line 3 | ʌ +// | : | | : | |-- new disp size +// | Line 25 | -------> | Line 25 | v +// ----------- ----------- +// +// Display Display +// BEFORE: LARGER +// ___________ ___________ +// | Hist -3 | ʌ | Hist -3 | --- new hist size +// | Hist -2 | |- 3 ┌-->|-----------| +// | Hist -1 | v | | Hist -2 | ʌ +// |-----------| ------┘ | Hist -1 | | +// | Line 1 | | Line 1 | | +// | Line 2 | | Line 2 | |-- new disp size +// | Line 3 | | Line 3 | | +// | : | | : | | +// | Line 25 | --------> | Line 25 | v +// ----------- ----------- +// +// +void Fl_Terminal::resize_display_rows(int drows) { + int drow_diff = drows - ring_.disp_rows(); // Change in rows? + if (drow_diff == 0) return; // No changes? early exit + int new_dcols = ring_cols(); // keep cols the same + int new_hrows = hist_rows() - drow_diff; // keep disp:hist ratio same + if (new_hrows<0) new_hrows = 0; // don't let hist be <0 + ring_.resize(drows, new_dcols, new_hrows, current_style_); + // ..update cursor/selections to track text position + cursor_.scroll(-drow_diff); + select_.clear(); // clear any mouse selection + // ..update scrollbar, since disp_height relative to hist_use changed + update_scrollbar(); +} + +// Resize the display's columns +// This affects the history and entire ring buffer too. +// Make an effort to preserve previous content. +// Up to caller to enforce any 'minimum' size for drows. +// +void Fl_Terminal::resize_display_columns(int dcols) { + // No changes? early exit + if (dcols == disp_cols()) return; + // Change cols, preserves previous content if possible + ring_.resize(disp_rows(), dcols, hist_rows(), current_style_); + update_scrollbar(); +} + +// Update only the internal terminal screen xywh and x2/y2 values +void Fl_Terminal::update_screen_xywh(void) { + const Margin &m = margin_; + scrn_ = *this; // start with widget's current xywh + scrn_.inset(box()); // apply box offset + scrn_.inset(m.left(), m.top(), m.right(), m.bottom()); // apply margins offset + scrn_.inset(0, 0, scrollbar_actual_size(), 0); // apply scrollbar width +} + +// Update internals when something "global" changes +// Call this when something important is changed: +// Resizing screen or changing font/size affect internals globally. +// Font change affects per-character caching of char widths. +// Display resize affects scrn_ cache, scrollbars, etc. +// +void Fl_Terminal::update_screen(bool font_changed) { + // current_style: update cursor's size for current font/size + if (font_changed) { + // Change font and current_style's font height + fl_font(current_style_.fontface(), current_style_.fontsize()); + cursor_.h(current_style_.fontheight()); + } + // Update the scrn_* values + update_screen_xywh(); + // Recalc the scrollbar size/position/etc + update_scrollbar(); +} + +/** + Return terminal's scrollback history buffer size in lines of text (rows). +*/ +int Fl_Terminal::history_rows(void) const { + return hist_rows(); +} + +/** + Set terminal's scrollback history buffer size in lines of text (rows). +*/ +void Fl_Terminal::history_rows(int hrows) { + if (hrows == history_rows()) return; // no change? done + ring_.resize(disp_rows(), disp_cols(), hrows, current_style_); + update_screen(false); // false: no font change + display_modified(); +} + +/** + Returns how many lines are "in use" by the screen history buffer. + + This value will be 0 if history was recently cleared with e.g. + clear_history() or \c "<ESC>c". + + Return value will be in the range 0 .. (history_lines()-1). +*/ +int Fl_Terminal::history_use(void) const { + return ring_.hist_use(); +} + +/** + Return terminal's display height in lines of text (rows). + + This value is normally managed automatically by resize() + based on the current font size. +*/ +int Fl_Terminal::display_rows(void) const { + return ring_.disp_rows(); +} + +/** + Set terminal's display height in lines of text (rows). + + This value is normally managed automatically by resize() + based on the current font size, and should not be changed. + + To change the display height, use resize() instead. +*/ +void Fl_Terminal::display_rows(int drows) { + if (drows == disp_rows()) return; // no change? early exit + ring_.resize(drows, disp_cols(), hist_rows(), current_style_); + update_screen(false); // false: no font change ?NEED? +} + +/** + Return terminal's display width in columns of text characters. + + This value is normally managed automatically by resize() + based on the current font size. +*/ +int Fl_Terminal::display_columns(void) const { + return ring_.disp_cols(); +} + +/** + Set terminal's display width in columns of text characters. + + This value is normally managed automatically by resize() + based on the current font size, and should not be changed. + + You CAN make the display_columns() larger than the width of + the widget; text in the terminal will simply run off the + screen edge and be clipped; the only way to reveal that + text is if the user enlarges the widget, or the font size + made smaller. + + To change the display width, it is best to use resize() instead. +*/ +void Fl_Terminal::display_columns(int dcols) { + if (dcols == disp_cols()) return; // no change? early exit + // Change cols, preserves previous content if possible + ring_.resize(disp_rows(), dcols, hist_rows(), current_style_); + update_screen(false); // false: no font change ?NEED? +} + +/** Return current style for rendering text. */ +const Fl_Terminal::CharStyle& Fl_Terminal::current_style(void) const { + return current_style_; +} + +/** Set current style for rendering text. */ +void Fl_Terminal::current_style(const CharStyle& sty) { + current_style_ = sty; +} + +/** + Set the left margin; see \ref Fl_Terminal_Margins. +*/ +void Fl_Terminal::margin_left(int val) { + val = clamp(val,0,w()-1); + margin_.left(val); + update_screen(true); + refit_disp_to_screen(); +} + +/** + Set the right margin; see \ref Fl_Terminal_Margins. +*/ +void Fl_Terminal::margin_right(int val) { + val = clamp(val,0,w()-1); + margin_.right(val); + update_screen(true); + refit_disp_to_screen(); +} + +/** + Set the top margin; see \ref Fl_Terminal_Margins. +*/ +void Fl_Terminal::margin_top(int val) { + val = clamp(val,0,h()-1); + margin_.top(val); + update_screen(true); + refit_disp_to_screen(); +} + +/** + Set the bottom margin; see \ref Fl_Terminal_Margins. +*/ +void Fl_Terminal::margin_bottom(int val) { + val = clamp(val,0,h()-1); + margin_.bottom(val); + update_screen(true); + refit_disp_to_screen(); +} + +/** + Sets the font used for all text displayed in the terminal. + + This affects all existing text (in display and history) as well + as any newly printed text. + + Only monospace fonts are recommended, such as FL_COURIER or FL_SCREEN. + Custom fonts configured with Fl::set_font() will also work, as long + as they are monospace. +*/ +void Fl_Terminal::textfont(Fl_Font val) { + current_style_.fontface(val); + update_screen(true); + display_modified(); +} + +/** + Sets the font size used for all text displayed in the terminal. + + This affects all existing text (in display and history) as well + as any newly printed text. + + Changing this will affect the display_rows() and display_columns(). +*/ +void Fl_Terminal::textsize(Fl_Fontsize val) { + current_style_.fontsize(val); + update_screen(true); + // Changing font size affects #lines in display, so resize it + refit_disp_to_screen(); + display_modified(); +} + +/** + Sets the foreground text color as one of the 8 'xterm color' values. + + This will be the foreground color used for all newly printed text, + similar to the \c \<ESC\>[\#m escape sequence, where \# is between 30 and 37. + + This color will be reset to the default fg color if reset_terminal() + is called, or by \c \<ESC\>c, \c \<ESC\>[0m, etc. + + The xterm color intensity values can be influenced by the Dim/Bold/Normal + modes (which can be set with e.g. \c \<ESC\>[1m, textattrib(), etc), so the + actual RGB values of these colors allow room for Dim/Bold to influence their + brightness. For instance, "Normal Red" is not full brightness to allow + "Bold Red" to be brighter. This goes for all colors except 'Black', which + is not influenced by Dim or Bold; Black is always Black. + + The 8 color xterm values are: + - 0 = Black + - 1 = Red + - 2 = Green + - 3 = Yellow + - 4 = Blue + - 5 = Magenta + - 6 = Cyan + - 7 = White + + \see textfgcolor_default(Fl_Color) +*/ +void Fl_Terminal::textfgcolor_xterm(uchar val) { + current_style_.fgcolor(fltk_fg_color(val)); +} + +/** + Sets the background text color as one of the 8 'xterm color' values. + + This will be the foreground color used for all newly printed text, + similar to the \c \<ESC\>[\#m escape sequence, where \# is between 40 and 47. + + This color will be reset to the default bg color if reset_terminal() + is called, or by \c \<ESC\>c, \c \<ESC\>[0m, etc. + + The xterm color intensity values can be influenced by the Dim/Bold/Normal + modes (which can be set with e.g. \c \<ESC\>[1m, textattrib(), etc), so the + actual RGB values of these colors allow room for Dim/Bold to influence their + brightness. For instance, "Normal Red" is not full brightness to allow + "Bold Red" to be brighter. This goes for all colors except 'Black', which + is not influenced by Dim or Bold; Black is always Black. + + The 8 color xterm values are: + - 0 = Black + - 1 = Red + - 2 = Green + - 3 = Yellow + - 4 = Blue + - 5 = Magenta + - 6 = Cyan + - 7 = White + + \see textbgcolor_default(Fl_Color) +*/ +void Fl_Terminal::textbgcolor_xterm(uchar val) { + current_style_.bgcolor(fltk_bg_color(val)); +} + +/** + Set text foreground drawing color to fltk color \p val. + Use this for temporary color changes, similar to \<ESC\>[38;2;\<R\>;\<G\>;\<B\>m + + This setting does _not_ affect the 'default' text colors used by \<ESC\>[0m, + \<ESC\>c, reset_terminal(), etc. To change both the current _and_ + default fg color, also use textfgcolor_default(Fl_Color). Example: + \par + \code + // Set both 'current' and 'default' colors + Fl_Color amber = 0xd0704000; + tty->textfgcolor(amber); // set 'current' fg color + tty->textfgcolor_default(amber); // set 'default' fg color used by ESC[0m reset + \endcode + \see textfgcolor_default(Fl_Color) +*/ +void Fl_Terminal::textfgcolor(Fl_Color val) { + current_style_.fgcolor(val); +} + +/** + Set text background color to fltk color \p val. + Use this for temporary color changes, similar to \<ESC\>[48;2;\<R\>;\<G\>;\<B\>m + + This setting does _not_ affect the 'default' text colors used by \<ESC\>[0m, + \<ESC\>c, reset_terminal(), etc. To change both the current _and_ + default bg color, also use textbgcolor_default(Fl_Color). Example: + \par + \code + // Set both 'current' and 'default' colors + Fl_Color darkamber = 0x20100000; + tty->textbgcolor(darkamber); // set 'current' bg color + tty->textbgcolor_default(darkamber); // set 'default' bg color used by ESC[0m reset + \endcode + + \see textbgcolor_default(Fl_Color) +*/ +void Fl_Terminal::textbgcolor(Fl_Color val) { + current_style_.bgcolor(val); +} + +/** + Set the default text foreground color used by \c \<ESC\>c, \c \<ESC\>[0m, + and reset_terminal(). + + Does not affect the 'current' text fg color; use textfgcolor(Fl_Color) to + set that. + + \see textfgcolor(Fl_Color) +*/ +void Fl_Terminal::textfgcolor_default(Fl_Color val) { + current_style_.defaultfgcolor(val); +} + +/** + Set the default text background color used by \c \<ESC\>c, \c \<ESC\>[0m, + and reset_terminal(). + + Does not affect the 'current' text fg color; use textbgcolor(Fl_Color) to + set that. + + \see textbgcolor(Fl_Color) +*/ +void Fl_Terminal::textbgcolor_default(Fl_Color val) { + current_style_.defaultbgcolor(val); +} + +/** + Set text attribute bits (underline, inverse, etc). + This will be the default attribute used for all newly printed text. + + \see Fl_Terminal::Attrib +*/ +void Fl_Terminal::textattrib(uchar val) { + current_style_.attrib(val); +} + +// Convert fltk window X coord to column 'gcol' on specified global 'grow' +// Returns 1 if 'gcol' was found, or 0 if X not within any char in 'grow' +// +int Fl_Terminal::x_to_glob_col(int X, int grow, int &gcol) const { + int cx = x() + margin_.left(); // char x position + const Utf8Char *u8c = utf8_char_at_glob(grow, 0); + for (gcol=0; gcol<ring_cols(); gcol++,u8c++) { // walk the cols looking for X + u8c->fl_font_set(current_style_); // pwidth_int() needs fl_font set + int cx2 = cx + u8c->pwidth_int(); // char x2 (right edge of char) + if (X >= cx && X < cx2) return 1; // found? return with gcol set + cx += u8c->pwidth_int(); // move cx to start x of next char + } + gcol = ring_cols()-1; // don't leave larger than #cols + return 0; // not found +} + +// Convert fltk window X,Y coords to row + column indexing into ring_chars[] +// Returns: +// 1 -- found row,col +// 0 -- not found, outside display's character area +// -1/-2/-3/-4 -- not found, off top/bot/lt/rt edge respectively +// +int Fl_Terminal::xy_to_glob_rowcol(int X, int Y, int &grow, int &gcol) const { + // X,Y outside terminal area? early exit + if (Y<scrn_.y()) return -1; // up (off top edge) + if (Y>scrn_.b()) return -2; // dn (off bot edge) + if (X<scrn_.x()) return -3; // lt (off left edge) + if (X>scrn_.r()) return -4; // rt (off right edge) + // Find toprow of what's currently drawn on screen + int toprow = disp_srow() - vscroll_->value(); + // Find row the 'Y' value is in + grow = toprow + ( (Y-scrn_.y()) / current_style_.fontheight()); + return x_to_glob_col(X, grow, gcol); +} + +/** + Clear the terminal screen only; does not affect the cursor position. + + Also clears the current mouse selection. + + If \p 'scroll_to_hist' is true, the screen is cleared by scrolling the + contents into the scrollback history, where it can be retrieved with the + scrollbar. This is the default behavior. If false, the screen is cleared + and the scrollback history is unchanged. + + Similar to the escape sequence \c "<ESC>[2J". + + \see clear_screen_home() +*/ +void Fl_Terminal::clear_screen(bool scroll_to_hist) { + if (scroll_to_hist) { scroll(disp_rows()); return; } + for (int drow=0; drow<disp_rows(); drow++) + for (int dcol=0; dcol<disp_cols(); dcol++) + clear_char_at_disp(drow, dcol); + clear_mouse_selection(); +} + +/** + Clear the terminal screen and home the cursor. + + Also clears the current mouse selection. + + If \p 'scroll_to_hist' is true, the screen is cleared by scrolling the + contents into the scrollback history, where it can be retrieved with the + scrollbar. This is the default behavior. If false, the screen is cleared + and the scrollback history is unchanged. + + Similar to the escape sequence \c "<ESC>[2J<ESC>[H". + + \see clear_screen() +*/ +void Fl_Terminal::clear_screen_home(bool scroll_to_hist) { + cursor_home(); + clear_screen(scroll_to_hist); +} + +/// Clear from cursor to Start Of Display (EOD), like \c "<ESC>[1J". +void Fl_Terminal::clear_sod(void) { + for (int drow=0; drow <= cursor_.row(); drow++) + if (drow == cursor_.row()) + for (int dcol=0; dcol<=cursor_.col(); dcol++) + putchar(' ', drow, dcol); + else + for (int dcol=0; dcol<disp_cols(); dcol++) + putchar(' ', drow, dcol); + //TODO: Clear mouse selection? +} + +/// Clear from cursor to End Of Display (EOD), like \c "<ESC>[J<ESC>[0J". +void Fl_Terminal::clear_eod(void) { + for (int drow=cursor_.row(); drow<disp_rows(); drow++) + if (drow == cursor_.row()) + for (int dcol=cursor_.col(); dcol<disp_cols(); dcol++) + putchar(' ', drow, dcol); + else + for (int dcol=0; dcol<disp_cols(); dcol++) + putchar(' ', drow, dcol); + //TODO: Clear mouse selection? +} + +/// Clear from cursor to End Of Line (EOL), like \c "<ESC>[K". +void Fl_Terminal::clear_eol(void) { + Utf8Char *u8c = u8c_disp_row(cursor_.row()) + cursor_.col(); // start at cursor + for (int col=cursor_.col(); col<disp_cols(); col++) // run from cursor to eol + (u8c++)->clear(current_style_); + //TODO: Clear mouse selection? +} + +/// Clear from cursor to Start Of Line (SOL), like \c "<ESC>[1K". +void Fl_Terminal::clear_sol(void) { + Utf8Char *u8c = u8c_disp_row(cursor_.row()); // start at sol + for (int col=0; col<=cursor_.col(); col++) // run from sol to cursor + (u8c++)->clear(current_style_); + //TODO: Clear mouse selection? +} + +/// Clear entire line for specified row. +void Fl_Terminal::clear_line(int drow) { + Utf8Char *u8c = u8c_disp_row(drow); // start at sol + for (int col=0; col<disp_cols(); col++) // run to eol + (u8c++)->clear(current_style_); + //TODO: Clear mouse selection? +} + +/// Clear entire line cursor is currently on. +void Fl_Terminal::clear_line(void) { + clear_line(cursor_.row()); +} + +/// Returns true if there's a mouse selection. +bool Fl_Terminal::is_selection(void) const { + return select_.is_selection(); +} + +/** + Walk the mouse selection one character at a time from beginning to end, + returning a Utf8Char* to the next character in the selection, or NULL + if the end was reached, or if there's no selection. + + This is easier to use for walking the selection than get_selection(). + + \p u8c should start out as NULL, rewinding to the beginning of the selection. + If the returned Utf8Char* is not NULL, \p row and \p col return the + character's row/column position in the ring buffer. + \par + \code + // EXAMPLE: Walk the entire mouse selection, if any + int row,col; // the returned row/col for each char + Utf8Char *u8c = NULL; // start with NULL to begin walk + while ((u8c = walk_selection(u8c, row, col))) { // loop until end char reached + ..do something with *u8c.. + } + \endcode + + \see get_selection(), is_selection() +*/ +const Fl_Terminal::Utf8Char* Fl_Terminal::walk_selection( + const Utf8Char *u8c, ///< NULL on first iter + int &row, ///< returned row# + int &col ///< returned col# + ) const { + if (u8c==NULL) { + if (!is_selection()) return NULL; + row = select_.srow(); + col = select_.scol(); + u8c = u8c_ring_row(row); + } else { + // At end? done + if (row == select_.erow() && col == select_.ecol()) return NULL; + if (++col >= ring_cols()) // advance to next char + { col = 0; ++row; } // wrapped to next row? + } + return u8c_ring_row(row) + col; +} + +/** + Return mouse selection's start/end position in the ring buffer, if any. + + Ensures (start < end) to allow walking 'forward' thru selection, + left-to-right, top-to-bottom. The row/col values are indexes into + the entire ring buffer. + + Example: walk the characters of the mouse selection: + + \par + \code + // Get selection + int srow,scol,erow,ecol; + if (get_selection(srow,scol,erow,ecol)) { // mouse selection exists? + // Walk entire selection from start to end + for (int row=srow; row<=erow; row++) { // walk rows of selection + const Utf8Char *u8c = u8c_ring_row(row); // ptr to first character in row + int col_start = (row==srow) ? scol : 0; // start row? start at scol + int col_end = (row==erow) ? ecol : ring_cols(); // end row? end at ecol + u8c += col_start; // include col offset (if any) + for (int col=col_start; col<=col_end; col++,u8c++) { // walk columns + ..do something with each char at *u8c.. + } + } + } + \endcode + + Returns: + - true -- valid selection values returned + - false -- no selection was made, returned values undefined + + \see walk_selection(), is_selection() +*/ +bool Fl_Terminal::get_selection(int &srow, ///< starting row for selection + int &scol, ///< starting column for selection + int &erow, ///< ending row for selection + int &ecol ///< ending column for selection + ) const { + return select_.get_selection(srow, scol, erow, ecol); +} + +// Is global row,col (relative to ring_chars[]) inside the current mouse selection? +// Retruns: +// true -- (row,col) inside a valid selection. +// false -- (row,col) outside, or no valid selection. +// +bool Fl_Terminal::is_inside_selection(int grow, int gcol) const { + if (!is_selection()) return false; + int ncols = ring_cols(); + // Calculate row/col magnitudes to simplify test + int check = (grow * ncols) + gcol; + int start = (select_.srow() * ncols) + select_.scol(); + int end = (select_.erow() * ncols) + select_.ecol(); + if (start > end) swap(start, end); // ensure (start < end) + return (check >= start && check <= end); +} + +// See if global row (grow) is inside the 'display' area +// +// No wrap case: Wrap case: +// ______________ ______________ +// ring_srow() -> | | ring_srow() -> | [C] | +// | | : | | +// | | : | Display | ... +// | History | : | | : +// | | disp_erow() -> | [C] | : +// | | |--------------| : +// |______________| | | : Display +// disp_srow() -> | [A] | | History | : straddles +// : | | | | : end of ring +// : | Display | |--------------| : +// : | | disp_srow() -> | [D] | ..: +// : | | : | Display | +// : | | : | | +// disp_erow ┬─> | [B] | ring_erow() -> | [D] | +// ring_erow ┘ -------------- -------------- +// +bool Fl_Terminal::is_disp_ring_row(int grow) const { + return ring_.is_disp_ring_row(grow); +} + +// Return byte length of all UTF-8 chars in selection, or 0 if no selection. +// NOTE: Length includes trailing white on each line. +// +int Fl_Terminal::selection_text_len(void) const { + int row,col,len=0; + const Utf8Char *u8c = NULL; // start with NULL to begin walk + while ((u8c = walk_selection(u8c, row, col))) // loop until end char reached + len += u8c->length(); + return len; +} + +// Return text selection (for copy()/paste() operations) +// Returns allocated NULL terminated string for entire selection. +// Caller must free() this memory when done. +// Unicode safe. +// +const char* Fl_Terminal::selection_text(void) const { + if (!is_selection()) return fl_strdup(""); // no selection? empty string + // Allocate buff large enough for all UTF-8 chars + int clen = 0; // char length + int buflen = selection_text_len(); + char *buf = (char*)malloc(buflen+1); // +1 for NULL + char *bufp = buf; + char *nspc = bufp; // last 'non-space' char + // Loop from srow,scol .. erow,ecol + int row,col; + const Utf8Char *u8c = NULL; // start with NULL to begin walk + while ((u8c = walk_selection(u8c, row, col))) { // loop until end char reached + clen = u8c->length(); // get char length + memcpy(bufp, u8c->text_utf8(), clen); // append UTF-8 string to buffer + // Handle ignoring trailing whitespace + if (!u8c->is_char(' ')) nspc = bufp + clen; // save end pos of last non-spc + bufp += clen; // advance into buffer + if (col >= (ring_cols()-1)) { // eol? handle trailing white + if (nspc && nspc != bufp) { // trailing white space? + bufp = nspc; // rewind bufp, and.. + *bufp++ = '\n'; // ..append crlf + nspc = bufp; // becomes new nspc for nxt row + } + } + } + *bufp = 0; + return buf; +} + +// Clear mouse selection +void Fl_Terminal::clear_mouse_selection(void) { + select_.clear(); +} + +// Extend selection to FLTK coords X,Y. +// Returns true if extended, false if nothing done (X,Y offscreen) +// +bool Fl_Terminal::selection_extend(int X,int Y) { + if (is_selection()) { // selection already? + int grow,gcol; + if (xy_to_glob_rowcol(X, Y, grow, gcol) > 0) { + select_.extend(grow, gcol); // extend it + return true; + } else { + // TODO: If X,Y outside row/col area and SHIFT down, + // extend selection to nearest edge. + } + } + return false; +} + +// Scroll the display up(+) or down(-) number of rows. +// Negative row value scrolls "down", clearing top line, and history unaffected. +// Postive row value scrolls "up", clearing bottom line, rotating top line into history. +// +void Fl_Terminal::scroll(int rows) { + // Scroll the ring + ring_.scroll(rows, current_style_); + if (rows > 0) update_scrollbar(); // scroll up? changes hist, so scrollbar affected + else clear_mouse_selection(); // scroll dn? clear mouse select; it might wrap ring +} + +// Insert (count) rows at cursor position. +// Causes rows below to scroll down, and empty lines created. +// Scrolling does not involve history at all. +// +void Fl_Terminal::insert_rows(int count) { + int dst_drow = disp_rows()-1; // dst is bottom of display + int src_drow = clamp((dst_drow-count), 1, (disp_rows()-1)); // src is count lines up from dst + while ( src_drow >= cursor_.row() ) { // walk srcrow upwards to cursor row + Utf8Char *src = u8c_disp_row(src_drow--); + Utf8Char *dst = u8c_disp_row(dst_drow--); + for (int dcol=0; dcol<disp_cols(); dcol++) *dst++ = *src++; // move + } + // Blank remaining rows upwards to and including cursor line + while ( dst_drow >= cursor_.row() ) { // walk srcrow to curs line + Utf8Char *dst = u8c_disp_row(dst_drow--); + for (int dcol=0; dcol<disp_cols(); dcol++) + dst++->clear(current_style_); + } + clear_mouse_selection(); +} + +// Delete (count) rows at cursor position. +// Causes rows to scroll up, and empty lines created at bottom of screen. +// Scrolling does not involve history at all. +// +void Fl_Terminal::delete_rows(int count) { + int dst_drow = cursor_.row(); // dst is cursor row + int src_drow = clamp((dst_drow+count), 1, (disp_rows()-1)); // src is count rows below cursor + while ( src_drow < disp_rows() ) { // walk srcrow to EOD + Utf8Char *src = u8c_disp_row(src_drow++); + Utf8Char *dst = u8c_disp_row(dst_drow++); + for (int dcol=0; dcol<disp_cols(); dcol++) + *dst++ = *src++; // move + } + // Blank remaining rows downwards to End Of Display + while ( dst_drow < disp_rows() ) { // walk srcrow to EOD + Utf8Char *dst = u8c_disp_row(dst_drow++); + for (int dcol=0; dcol<disp_cols(); dcol++) + dst++->clear(current_style_); + } + clear_mouse_selection(); +} + +// Repeat printing char 'c' for 'rep' times, not to exceed end of line. +void Fl_Terminal::repeat_char(char c, int rep) { + rep = clamp(rep, 1, disp_cols()); + while ( rep-- > 0 && cursor_.col() < disp_cols() ) print_char(c); +} + +// Insert char 'c' for 'rep' times at display (drow,dcol). +void Fl_Terminal::insert_char_eol(char c, int drow, int dcol, int rep) { + // Walk the row from the eol backwards to the col position + // In this example, rep=3: + // + // dcol + // v + // BEFORE: |a|b|c|d|e|f|g|h|i|j| <- eol (disp_cols()-1) + // | | | | |_____ + // src end -> |_____ | <-- src start + // | | | | | + // v v v v v + // AFTER: |a|b|‸|‸|‸|c|d|e|f|g| + // |_|_| <-- spaces added last + // + rep = clamp(rep, 0, disp_cols()); // sanity + if (rep == 0) return; + const CharStyle &style = current_style_; + Utf8Char *src = u8c_disp_row(drow)+disp_cols()-1-rep; // start src at 'g' + Utf8Char *dst = u8c_disp_row(drow)+disp_cols()-1; // start dst at 'j' + for (int col=(disp_cols()-1); col>=dcol; col--) { // loop col in reverse: eol -> dcol + if (col >= (dcol+rep)) *dst-- = *src--; // let assignment do move + else (dst--)->clear(style); // clear chars displaced + } +} + +// Insert char 'c' for 'rep' times. +// Does not wrap; characters at end of line are lost. +// +void Fl_Terminal::insert_char(char c, int rep) { + insert_char_eol(c, cursor_.row(), cursor_.col(), rep); +} + +// Delete char(s) at (drow,dcol) for 'rep' times. +void Fl_Terminal::delete_chars(int drow, int dcol, int rep) { + rep = clamp(rep, 0, disp_cols()); // sanity + if (rep == 0) return; + const CharStyle &style = current_style_; + Utf8Char *u8c = u8c_disp_row(drow); + for (int col=dcol; col<disp_cols(); col++) // delete left-to-right + if (col+rep >= disp_cols()) u8c[col].text_ascii(' ', style); // blanks + else u8c[col] = u8c[col+rep]; // move +} + +// Delete char(s) at cursor position for 'rep' times. +void Fl_Terminal::delete_chars(int rep) { + delete_chars(cursor_.row(), cursor_.col(), rep); +} + +/** + Clears the scroll history buffer and adjusts scrollbar, + forcing it to redraw(). +*/ +void Fl_Terminal::clear_history(void) { + // Adjust history use + ring_.clear_hist(); + vscroll_->value(0); // zero scroll position + // Clear entire history buffer + for (int hrow=0; hrow<hist_rows(); hrow++) { + Utf8Char *u8c = u8c_hist_row(hrow); // walk history rows.. + for (int hcol=0; hcol<hist_cols(); hcol++) { // ..and history cols + (u8c++)->clear(current_style_); + } + } + // Adjust scrollbar (hist_use changed) + update_scrollbar(); +} + +/** + Resets terminal to default colors, clears screen, history and + mouse selection, homes cursor, resets tabstops. Same as \c "<ESC>c" +*/ +void Fl_Terminal::reset_terminal(void) { + current_style_.sgr_reset(); // reset current style + clear_screen_home(); // clear screen, home cursor + clear_history(); + clear_mouse_selection(); + default_tabstops(); // reset tabstops to default 8 char +} + +//DEBUG void Fl_Terminal::RingBuffer::show_ring_info(void) const { +//DEBUG ::printf("\033[s"); // save cursor +//DEBUG ::printf("\033[05C -- Ring Index\n"); +//DEBUG ::printf("\033[05C ring_rows_: %d\n", ring_rows_); +//DEBUG ::printf("\033[05C ring_cols_: %d\n", ring_cols_); +//DEBUG ::printf("\033[05C offset_: %d\n", offset_); +//DEBUG ::printf("\033[u"); // recall cursor +//DEBUG ::printf("\033[30C -- History Index\n"); +//DEBUG ::printf("\033[30C hist_rows_: %d srow=%d\n", hist_rows(), hist_srow()); +//DEBUG ::printf("\033[30C hist_cols_: %d erow=%d\n", hist_cols(), hist_erow()); +//DEBUG ::printf("\033[30C hist_use_: %d\n", hist_use()); +//DEBUG ::printf("\033[u"); // recall cursor +//DEBUG ::printf("\033[60C -- Display Index\n"); +//DEBUG ::printf("\033[60C disp_rows_: %d srow=%d\n", disp_rows(), disp_srow()); +//DEBUG ::printf("\033[60C disp_cols_: %d erow=%d\n", disp_cols(), disp_erow()); +//DEBUG ::printf("\n\n"); +//DEBUG } + +//DEBUG // Save specified row from ring buffer 'ring' to FILE* +//DEBUG void Fl_Terminal::write_row(FILE *fp, Utf8Char *u8c, int cols) const { +//DEBUG cols = (cols != 0) ? cols : ring_cols(); +//DEBUG for ( int col=0; col<cols; col++, u8c++ ) { +//DEBUG ::fprintf(fp, "%.*s", u8c->length(), u8c->text_utf8()); +//DEBUG } +//DEBUG } + +//DEBUG // Show two buffers side-by-side on stdout. +//DEBUG // Second buffer can be NULL to just show the a buffer. +//DEBUG // +//DEBUG void Fl_Terminal::show_buffers(RingBuffer *a, RingBuffer *b) const { +//DEBUG int arows = a->ring_rows(), acols = a->ring_cols(); +//DEBUG int brows = b ? b->ring_rows() : 0, bcols = b ? b->ring_cols() : 0; +//DEBUG int trows = MAX(arows,brows); +//DEBUG // Show header +//DEBUG ::printf("\033[H"); +//DEBUG if (a) ::printf("SRC %d x %d huse=%d off=%d", arows,acols, a->hist_use(), a->offset()); +//DEBUG if (b) ::printf(", DST %d x %d huse=%d off=%d", brows, bcols, b->hist_use(), b->offset()); +//DEBUG ::printf("\033[K\n"); +//DEBUG Utf8Char *u8c; +//DEBUG // Show rows +//DEBUG for (int row=0; row < trows; row++) { +//DEBUG // 'A' buffer +//DEBUG if (row >= arows) { +//DEBUG ::printf(" %*s ", acols, ""); +//DEBUG } else { +//DEBUG u8c = a->ring_chars()+(arows*acols); +//DEBUG ::printf("%3d/%3d [", row, trows-1); write_row(stdout, u8c, acols); ::printf("] "); +//DEBUG } +//DEBUG if (!b) { ::printf("\033[K\n"); continue; } +//DEBUG // 'B' buffer +//DEBUG if (row < brows) { +//DEBUG u8c = b->ring_chars()+(brows*bcols); +//DEBUG ::printf("["); write_row(stdout, u8c, bcols); ::printf("]"); +//DEBUG } +//DEBUG ::printf("\033[K\n"); +//DEBUG } +//DEBUG ::printf("--- END\033[0J\n"); // clear eos +//DEBUG ::printf(" HIT ENTER TO CONTINUE: "); getchar(); +//DEBUG fflush(stdout); +//DEBUG } + +/////////////////////////////// +////// CURSOR MANAGEMENT ////// +/////////////////////////////// + +/** Set the cursor's foreground color used for text under the cursor. */ +void Fl_Terminal::cursorfgcolor(Fl_Color val) { cursor_.fgcolor(val); } +/** Set the cursor's background color used for the cursor itself. */ +void Fl_Terminal::cursorbgcolor(Fl_Color val) { cursor_.bgcolor(val); } +/** Get the cursor's foreground color used for text under the cursor. */ +Fl_Color Fl_Terminal::cursorfgcolor(void) const { return cursor_.fgcolor(); } +/** Get the cursor's background color used for the cursor itself. */ +Fl_Color Fl_Terminal::cursorbgcolor(void) const { return cursor_.bgcolor(); } + +/** + Move cursor to the specified row \p row. + This value is clamped to the range (0..display_rows()-1). +*/ +void Fl_Terminal::cursor_row(int row) { cursor_.row( clamp(row,0,disp_rows()-1) ); } +/** Move cursor to the specified column \p col. + This value is clamped to the range (0..display_columns()-1). +*/ +void Fl_Terminal::cursor_col(int col) { cursor_.col( clamp(col,0,disp_cols()-1) ); } +/** Return the cursor's current row position on the screen. */ +int Fl_Terminal::cursor_row(void) const { return cursor_.row(); } +/** Return the cursor's current column position on the screen. */ +int Fl_Terminal::cursor_col(void) const { return cursor_.col(); } + +/** + Moves cursor up \p count lines. + If cursor hits screen top, it either stops (does not wrap) if \p do_scroll + is false, or scrolls down if \p do_scroll is true. +*/ +void Fl_Terminal::cursor_up(int count, bool do_scroll) { + count = clamp(count, 1, disp_rows() * 2); // sanity (max 2 scrns) + while (count-- > 0) { + if (cursor_.up() <= 0) { // hit screen top? + cursor_.row(0); // clamp cursor to top + if (do_scroll) scroll(-1); // scrolling on? scroll down + else return; // scrolling off? stop at top + } + } +} + +/** + Moves cursor down \p count lines. + If cursor hits screen bottom, it either stops (does not wrap) if \p do_scroll + is false, or wraps and scrolls up if \p do_scroll is true. +*/ +void Fl_Terminal::cursor_down(int count, ///< Number of lines to move cursor down + bool do_scroll ///< Enable scrolling if set to true + ) { + count = clamp(count, 1, ring_rows()); // sanity + while (count-- > 0) { + if (cursor_.down() >= disp_rows()) { // hit screen bottom? + cursor_.row(disp_rows() - 1); // clamp + if (!do_scroll) break; // don't scroll? done + scroll(1); // scroll up 1 row to make room for new line + } + } +} + +/** + Moves cursor left \p count columns, and cursor stops (does not wrap) + if it hits screen edge. +*/ +void Fl_Terminal::cursor_left(int count) { + count = clamp(count, 1, disp_cols()); // sanity + while (count-- > 0 ) + if (cursor_.left() < 0) // hit left edge of screen? + { cursor_sol(); return; } // stop, done +} + +/** + Moves cursor right \p count columns. If cursor hits right edge of screen, + it either stops (does not wrap) if \p do_scroll is false, or wraps and + scrolls up one line if \p do_scroll is true. +*/ +void Fl_Terminal::cursor_right(int count, bool do_scroll) { + while (count-- > 0 ) { + if (cursor_.right() >= disp_cols()) { // hit right edge? + if (!do_scroll) // no scroll? + { cursor_eol(); return; } // stop at EOL, done + else + { cursor_crlf(1); } // do scroll? crlf + } + } +} + +/** Move cursor to the home position (top/left). */ +void Fl_Terminal::cursor_home(void) { cursor_.col(0); cursor_.row(0); } + +/** Move cursor to the last column (at the far right) on the current line. */ +void Fl_Terminal::cursor_eol(void) { cursor_.col(disp_cols()-1); } + +/** Move cursor to the first column (at the far left) on the current line. */ +void Fl_Terminal::cursor_sol(void) { cursor_.col(0); } + +/** Move cursor as if a CR (\\r) was received. Same as cursor_sol() */ +void Fl_Terminal::cursor_cr(void) { cursor_sol(); } + +/** Move cursor as if a CR/LF pair (\\r\\n) was received. */ +void Fl_Terminal::cursor_crlf(int count) { + const bool do_scroll = true; + count = clamp(count, 1, ring_rows()); // sanity + cursor_sol(); + cursor_down(count, do_scroll); +} + +// Tab right, do not wrap beyond right edge +void Fl_Terminal::cursor_tab_right(int count) { + count = clamp(count, 1, disp_cols()); // sanity + int X = cursor_.col(); + while ( count-- > 0 ) { + // Find next tabstop + while ( ++X < disp_cols() ) { + if ( (X<tabstops_size_) && tabstops_[X] ) // found? + { cursor_.col(X); return; } // move cur, done + } + } + cursor_eol(); +} + +// Tab left, do not wrap beyond left edge +void Fl_Terminal::cursor_tab_left(int count) { + count = clamp(count, 1, disp_cols()); // sanity + int X = cursor_.col(); + while ( count-- > 0 ) + while ( --X > 0 ) // search for tabstop + if ( (X<tabstops_size_) && tabstops_[X] ) // found? + { cursor_.col(X); return; } // move cur, done + cursor_sol(); +} + +// Save current cursor position +void Fl_Terminal::save_cursor(void) { + escseq.save_cursor(cursor_.row(), cursor_.col()); +} + +// Restore previously saved cursor position, if there was one +void Fl_Terminal::restore_cursor(void) { + int row,col; + escseq.restore_cursor(row, col); + if (row != -1 && col != 1) // restore only if save first + { cursor_.row(row); cursor_.col(col); } +} + +////////////////////// +////// PRINTING ////// +////////////////////// + +void Fl_Terminal::handle_ctrl(char c) { + switch (c) { + case '\n': cursor_crlf(); return; // CRLF? + case '\b': cursor_left(); return; // BS? + case '\r': cursor_cr(); return; // CR? + case '\t': cursor_tab_right(); return; // TAB? + case 0x1b: if (ansi_) escseq.parse(c); // ESC? + else append_utf8("␛"); + return; + default: handle_unknown_char(); return; // Unknown ctrl char? + } +} + +// Is char printable? (0x20 thru 0x7e) +bool Fl_Terminal::is_printable(char c) { + return ((c>=0x20) && (c<=0x7e)); +} + +// Is char a ctrl character? (0x00 thru 0x1f) +bool Fl_Terminal::is_ctrl(char c) { + return ((c >= 0x00) && (c < 0x20)) ? true : false; +} + +// Handle ESC[m sequences. +// This is different from the others in that the list of vals +// separated by ;'s can be long, to allow combining multiple mode +// settings at once, e.g. fg and bg colors, multiple attributes, etc. +// +void Fl_Terminal::handle_SGR(void) { // ESC[...m? + // Shortcut varnames.. + EscapeSeq &esc = escseq; + int tot = esc.total_vals(); + // Handle ESC[m or ESC[;m + if (tot == 0) + { current_style_.sgr_reset(); return; } + // Handle ESC[#;#;#...m + int rgbcode = 0; // 0=none, 38=fg, 48=bg + int rgbmode = 0; // 0=none, 1="2", 2=<r>, 3=<g>, 4=<b> + int r=0,g=0,b=0; + for (int i=0; i<tot; i++) { // expect possibly many values + int val = esc.val(i); // each val one at a time + switch ( rgbmode ) { + case 0: + // RGB mode values? + switch (val) { + case 38: // fg RGB mode? e.g. ESC[38;2;<R>;<G>;<B>m + case 48: // bg RGB mode? e.g. ESC[48;2;<R>;<G>;<B>m + rgbmode = 1; + rgbcode = val; + continue; + } + break; + case 1: if (val == 2) { rgbmode++; continue; } // '2'? + rgbcode = rgbmode = 0; // not '2'? cancel + handle_unknown_char(); + break; + case 2: r=clamp(val,0,255); ++rgbmode; continue; // parse red value + case 3: g=clamp(val,0,255); ++rgbmode; continue; // parse grn value + case 4: b=clamp(val,0,255); // parse blu value + switch (rgbcode) { + case 38: current_style_.fgcolor(r,g,b); // Set fg rgb + break; + case 48: current_style_.bgcolor(r,g,b); // Set bg rgb + break; + } + rgbcode = rgbmode = 0; // done w/rgb mode parsing + continue; // continue loop to parse more vals + } + if (val < 10) { // Set attribute? (bold,underline..) + switch ( val ) { + case 0: current_style_.sgr_reset(); break; // ESC[0m - reset + case 1: current_style_.sgr_bold(1); break; // ESC[1m - bold + case 2: current_style_.sgr_dim(1); break; // ESC[2m - dim + case 3: current_style_.sgr_italic(1); break; // ESC[3m - italic + case 4: current_style_.sgr_underline(1);break; // ESC[4m - underline + case 5: current_style_.sgr_blink(1); break; // ESC[5m - blink + case 6: handle_unknown_char(); break; // ESC[6m - (unused) + case 7: current_style_.sgr_inverse(1); break; // ESC[7m - inverse + case 8: handle_unknown_char(); break; // ESC[8m - (unused) + case 9: current_style_.sgr_strike(1); break; // ESC[9m - strikeout + } + } else if (val >= 21 && val <= 29) { // attribute extras + switch ( val ) { + case 21: current_style_.sgr_dbl_under(1);break; // ESC[21m - doubly underline + case 22: current_style_.sgr_dim(0); // ESC[22m - disable bold/dim + current_style_.sgr_bold(0); break; // + case 23: current_style_.sgr_italic(0); break; // ESC[23m - disable italic + case 24: current_style_.sgr_underline(0);break; // ESC[24m - disable underline + case 25: current_style_.sgr_blink(0); break; // ESC[25m - disable blink + case 26: handle_unknown_char(); break; // ESC[26m - (unused) + case 27: current_style_.sgr_inverse(0); break; // ESC[27m - disable inverse + case 28: handle_unknown_char(); break; // ESC[28m - disable hidden + case 29: current_style_.sgr_strike(0); break; // ESC[29m - disable strikeout + } + } else if (val >= 30 && val <= 37) { // Set fg color? + current_style_.fgcolor_uchar(val - 30); + } else if (val == 39) { // ESC[39m -- "normal" fg color: + Fl_Color fg = current_style_.defaultfgcolor(); // ..get default color + current_style_.fgcolor(fg); // ..set current color + } else if (val >= 40 && val <= 47) { // Set bg color? + current_style_.bgcolor_uchar(val - 40); + } else if (val == 49) { // ESC[49m -- "normal" bg color: + Fl_Color bg = current_style_.defaultbgcolor(); // ..get default bg color + current_style_.bgcolor(bg); // ..set current bg color + } else { + handle_unknown_char(); // does an escseq.reset() // unimplemented SGR codes + } + } +} + +// Handle <ESC>[<top>;<lt>;<bot>;<rt>;<att>$t +// This one is fun! <att> is the attrib to xor, i.e. 1(bold),4,5,7(inverse). +// gnome-term doesn't support this, but xterm does. +// +void Fl_Terminal::handle_DECRARA(void) { + // TODO: MAYBE NEVER +} + +// Handle an escape sequence character +// If this char is the end of the sequence, do the operation +// if possible, and then reset() to finish parsing. +// +void Fl_Terminal::handle_escseq(char c) { + // NOTE: Use xterm to test. gnome-terminal has bugs, even in 2022. + const bool do_scroll = true; + const bool no_scroll = false; + //UNUSED const bool do_wrap = true; + //UNUSED const bool no_wrap = false; + switch (escseq.parse(c)) { // parse char, advance s.. + case EscapeSeq::fail: escseq.reset(); return; // failed? reset, done + case EscapeSeq::success: return; // keep parsing.. + case EscapeSeq::completed: break; // parsed complete esc sequence? + } + // Shortcut varnames for escseq parsing.. + EscapeSeq &esc = escseq; + char mode = esc.esc_mode(); + int tot = esc.total_vals(); + int val0 = (tot==0) ? 0 : esc.val(0); + int val1 = (tot<2) ? 0 : esc.val(1); + const int& dw = disp_cols(); + const int& dh = disp_rows(); + if (esc.is_csi()) { // Was this a CSI (ESC[..) sequence? + switch ( mode ) { + case '@': // <ESC>[#@ - (ICH) Insert blank Chars (default=1) + insert_char(' ', esc.defvalmax(1,dw)); + break; + case 'A': // <ESC>[#A - (CUU) cursor up, no scroll/wrap + cursor_up(esc.defvalmax(1,dh)); + break; + case 'B': // <ESC>[#B - (CUD) cursor down, no scroll/wrap + cursor_down(esc.defvalmax(1,dh), no_scroll); + break; + case 'C': // <ESC>[#C - (CUF) cursor right, no wrap + cursor_right(esc.defvalmax(1,dw), no_scroll); + break; + case 'D': // <ESC>[#D - (CUB) cursor left, no wrap + cursor_left(esc.defvalmax(1,dw)); + break; + case 'E': // <ESC>[#E - (CNL) cursor next line (crlf) xterm, !gnome + cursor_crlf(esc.defvalmax(1,dh)); + break; + case 'F': // <ESC>[#F - (CPL) move to sol and up # lines + cursor_cr(); + cursor_up(esc.defvalmax(1,dh)); + break; + case 'G': // <ESC>[#G - (CHA) cursor horizal absolute + switch (clamp(tot,0,1)) { // │ + case 0: // ├── <ESC>[G -- move to sol + cursor_sol(); // │ default <ESC>[1G + break; // │ + case 1: // └── <ESC>[#G -- move to column + cursor_col(clamp(val0,1,dw)-1); + break; + } + break; + case 'H': +cup: + switch (clamp(tot,0,2)) { // <ESC>[#H - (CUP) cursor position (#'s are 1 based) + case 0: // ├── <ESC>[H -- no vals? + cursor_home(); // │ default <ESC>[1H + break; // │ + case 1: // ├── <ESC>[#H -- go to (row #) + cursor_row(clamp(val0,1,dh)-1); // │ NOTE: ESC[5H == ESC[5;1H + cursor_col(0); // │ + break; // │ + case 2: // └── <ESC>[#;#H -- go to (row# ; col#) + cursor_row(clamp(val0,1,dh)-1); + cursor_col(clamp(val1,1,dw)-1); + break; + } + break; + case 'I': // <ESC>[#I - (CHT) cursor forward tab (default=1) + switch (clamp(tot,0,1)) { // │ + case 0: // ├── <ESC>[I -- no vals + cursor_tab_right(1); // │ default <ESC>[1I + break; // │ + case 1: // └── <ESC>[#I -- tab # times + cursor_tab_right(clamp(val0,1,dw)); // + break; + } + break; + case 'J': // <ESC>[#J - (ED) erase in display + switch ( clamp(tot,0,1) ) { // │ + case 0: clear_eol(); break; // ├── <ESC>[J -- no vals: default <ESC>[0J + case 1: // │ + switch ( clamp(val0,0,3) ) { // │ + case 0: clear_eod(); break; // ├── <ESC>[0J -- clear to end of display + case 1: clear_sod(); break; // ├── <ESC>[1J -- clear to start of display + case 2: clear_screen(); break; // ├── <ESC>[2J -- clear all lines + case 3: clear_history(); break; // └── <ESC>[3J -- clear screen history + } + break; + } + break; + case 'K': + switch ( clamp(tot,0,1) ) { // <ESC>[#K - (EL) Erase in Line + case 0: clear_eol(); break; // ├── <ESC>[K -- no vals + case 1: switch ( clamp(val0,0,2) ) { // │ + case 0: clear_eol(); break; // ├── <ESC>[0K -- clear to end of line + case 1: clear_sol(); break; // ├── <ESC>[1K -- clear to start of line + case 2: clear_line(); break; // └── <ESC>[2K -- clear current line + } + break; + } + break; + case 'L': // ESC[#L - Insert # lines (def=1) + insert_rows(esc.defvalmax(1,dh)); + break; + case 'M': // ESC[#M - Delete # lines (def=1) + delete_rows(esc.defvalmax(1,dh)); + break; + case 'P': // ESC[#P - Delete # chars (def=1) + delete_chars(esc.defvalmax(1,dh)); + break; + case 'S': // ESC[#S - scroll up # lines (def=1) + scroll( +(esc.defvalmax(1,dh)) ); + // ⮤ positive=scroll up + break; + case 'T': // ESC[#T - scroll dn # lines (def=1) + scroll( -(esc.defvalmax(1,dh)) ); + // ⮤ negative=scroll down + break; + case 'X': // <ESC>[#X - (ECH) Erase Characters (default=1) + repeat_char(' ', esc.defvalmax(1,dw)); + break; + case 'Z': // ESC[#Z - backtab # tabs + switch (clamp(tot,0,1)) { // │ + case 0: // ├── <ESC>[Z -- no vals + cursor_tab_left(1); // │ default <ESC>[1Z + break; // │ + case 1: // └── <ESC>[#Z -- tab # times + cursor_tab_left(clamp(val0,1,dw)); + break; + } + break; + case 'a': // TODO // ESC[#a - (HPR) move cursor relative [columns] (default=[row,col+1]) + case 'b': // TODO // ESC[#b - (REP) repeat prev graphics char # times + case 'd': // TODO // ESC[#d - (VPA) line pos absolute [row] + case 'e': // TODO // ESC[#e - line pos relative [rows] + handle_unknown_char(); // does an escseq.reset() + break; + case 'f': // <ESC>[#f - (CUP) cursor position (#'s 1 based) + goto cup; // (same as ESC[H) + case 'g': // ESC[...g? Tabulation Clear (TBC) + switch (val0) { + case 0: clear_tabstop(); break; // clears tabstop at cursor + case 3: clear_all_tabstops(); break; // clears all tabstops + default: + handle_unknown_char(); // does an escseq.reset() + break; + } + break; + case 'm': handle_SGR(); break; // ESC[#m - set character attributes (SGR) + case 's': save_cursor(); break; // ESC[s - save cur pos (xterm+gnome) + case 'u': restore_cursor(); break; // ESC[u - rest cur pos (xterm+gnome) + case 'q': // TODO? // ESC[>#q set cursor style (block/line/blink..) + case 'r': // TODO // ESC[#;#r set scroll region top;bot + // default=full window + handle_unknown_char(); // does an escseq.reset() + break; + case 't': handle_DECRARA(); break; // ESC[#..$t -- (DECRARA) + // Reverse attribs in Rect Area (row,col) + default: + handle_unknown_char(); // does an escseq.reset() + break; + } + } else { + // Not CSI? Might be C1 Control code (<ESC>D, etc) + switch ( esc.esc_mode() ) { + case 'c': // <ESC>c - Reset term to Initial State (RIS) + reset_terminal(); + break; + case 'D': cursor_down(1, do_scroll); break;// <ESC>D - down line, scroll at bottom + case 'E': cursor_crlf(); break;// <ESC>E - do a crlf + case 'H': set_tabstop(); break;// <ESC>H - set a tabstop + case 'M': cursor_up(1, true); break;// <ESC>M -- (RI) Reverse Index (up w/scroll) + default: + handle_unknown_char(); // does an escseq.reset() + break; + } + } + esc.reset(); // done handling escseq, reset() +} + +void Fl_Terminal::display_modified_clear(void) { + redraw_modified_ = false; +} + +// Display modified, trigger redraw handling.. +void Fl_Terminal::display_modified(void) { + if (is_redraw_style(RATE_LIMITED)) { + if (!redraw_modified_) { // wasn't before but now is? + if (!redraw_timer_) { + Fl::add_timeout(.01, redraw_timer_cb, this); // turn on timer + redraw_timer_ = true; + } + redraw_modified_ = true; + } + } else if (is_redraw_style(PER_WRITE)) { + if (!redraw_modified_) { + redraw_modified_ = true; + redraw(); // only call redraw once + } + } else { // NO_REDRAW? + // do nothing + } +} + +/** + Clear the character at the specified display row and column. + + No range checking done on drow,dcol: + - \p drow must be in range 0..(disp_rows()-1) + - \p dcol must be in range 0..(disp_cols()-1) + + - Does not trigger redraws +*/ +void Fl_Terminal::clear_char_at_disp(int drow, int dcol) { + Utf8Char *u8c = u8c_disp_row(drow) + dcol; + u8c->clear(current_style_); +} + +/** + Return Utf8Char* for char at specified display row and column. + This accesses any character in the display part of the ring buffer. + + No range checking done on drow,dcol: + - \p drow must be in range 0..(disp_rows()-1) + - \p dcol must be in range 0..(disp_cols()-1) + + \see u8c_disp_row() +*/ +const Fl_Terminal::Utf8Char* Fl_Terminal::utf8_char_at_disp(int drow, int dcol) const { + return u8c_disp_row(drow) + dcol; +} + +/** + Return Utf8Char* for char at specified global (grow,gcol). + This accesses any character in the ring buffer (history + display). + + No range checking done on grow,gcol: + - \p grow must be in range 0..(ring_rows()-1) + - \p gcol must be in range 0..(ring_cols()-1) + + \see u8c_ring_row() +*/ +const Fl_Terminal::Utf8Char* Fl_Terminal::utf8_char_at_glob(int grow, int gcol) const { + return u8c_ring_row(grow) + gcol; +} + +/** + Print UTF-8 character \p text of length \p len at display position \p (drow,dcol). + The character is displayed using the current text color/attributes. + + This is a very low level method. + + No range checking is done on drow,dcol: + - \p drow must be in range 0..(display_rows()-1) + - \p dcol must be in range 0..(display_columns()-1) + + - Does not trigger redraws + - Does not handle ANSI or XTERM escape sequences + - Invalid UTF-8 chars show the error character (¿) depending on show_unknown(bool). + + \see handle_unknown_char() +*/ +void Fl_Terminal::putchar(const char *text, int len, int drow, int dcol) { + Utf8Char *u8c = u8c_disp_row(drow) + dcol; + // text_utf8() warns we must do invalid checks first + if (!text || len<1 || len>u8c->max_utf8() || len!=fl_utf8len(*text)) + { handle_unknown_char(); return; } + u8c->text_utf8(text, len, current_style_); +} + +/** + Print the ASCII character \p c at the terminal's display position \p (drow,dcol). + + The character MUST be printable (in range 0x20 - 0x7e), and is displayed + using the current text color/attributes. Characters outside that range are either + ignored or print the error character (¿), depending on show_unknown(bool). + + This is a very low level method. + + No range checking is done on drow,dcol: + - \p drow must be in range 0..(display_rows()-1) + - \p dcol must be in range 0..(display_columns()-1) + + - Does not trigger redraws + - Does NOT handle control codes, ANSI or XTERM escape sequences. + + \see show_unknown(bool), handle_unknown_char(), is_printable() +*/ +void Fl_Terminal::putchar(char c, int drow, int dcol) { + if (!is_printable(c)) { handle_unknown_char(); return; } + Utf8Char *u8c = u8c_disp_row(drow) + dcol; + u8c->text_ascii(c, current_style_); +} + +/** + Prints single UTF-8 char \p text of optional byte length \p len + at current cursor position, and advances the cursor if the character + is printable. Handles ASCII and control codes (CR, LF, etc). + + The character is displayed at the current cursor position + using the current text color/attributes. + + Handles control codes and can be used to construct ANSI/XTERM + escape sequences. + + - If optional \p len isn't specified or <0, strlen(text) is used. + - \p text must not be NULL. + - \p len must not be 0. + - \p text must be a single char only (whether UTF-8 or ASCII) + - \p text can be an ASCII character, though not as efficent as print_char() + - Invalid UTF-8 chars show the error character (¿) depending on show_unknown(bool). + - Does not trigger redraws + + \see show_unknown(bool), handle_unknown_char() +*/ +void Fl_Terminal::print_char(const char *text, int len/*=-1*/) { + len = len<0 ? fl_utf8len(*text) : len; // int(strlen(text)) : len; + const bool do_scroll = true; + if (is_ctrl(text[0])) { // Handle ctrl character + handle_ctrl(*text); + } else if (escseq.parse_in_progress()) { // ESC sequence in progress? + handle_escseq(*text); + } else { // Handle printable char.. + putchar(text, len, cursor_row(), cursor_col()); + cursor_right(1, do_scroll); + } +} + +/** + Prints single ASCII char \p c at current cursor position, and advances the cursor. + + The character is displayed at the current cursor position + using the current text color/attributes. + + - \p c must be ASCII, not utf-8 + - Does not trigger redraws +*/ +void Fl_Terminal::print_char(char c) { + const bool do_scroll = true; + if (is_ctrl(c)) { // Handle ctrl character + handle_ctrl(c); + } else if (escseq.parse_in_progress()) { // ESC sequence in progress? + handle_escseq(c); + } else { // Handle printable char.. + putchar(c, cursor_row(), cursor_col()); + cursor_right(1, do_scroll); + return; + } +} + +// Clear the Partial UTF-8 Buffer cache +void Fl_Terminal::utf8_cache_clear(void) { + pub_.clear(); +} + +// Flush the Partial UTF-8 Buffer cache, and clear +void Fl_Terminal::utf8_cache_flush(void) { + if (pub_.buflen() > 0) print_char(pub_.buf(), pub_.buflen()); + pub_.clear(); +} + +/** + Append NULL terminated UTF-8 string to terminal. + + - If buf is NULL, UTF-8 cache buffer is cleared + - If optional \p len isn't specified or is -1, strlen(text) is used. + - If \p len is 0 or <-1, no changes are made + - Handles UTF-8 chars split across calls (e.g. block writes from pipes, etc) + - Redraws are triggered automatically, depending on redraw_style() +*/ +void Fl_Terminal::append_utf8(const char *buf, int len/*=-1*/) { + int mod = 0; // assume no modifications + if (!buf) { utf8_cache_clear(); return; } // clear cache, done + if (len == -1) len = int(strlen(buf)); // len optional + if (len<=0) return; // bad len? early exit + + // Handle any partial UTF-8 from last write + // Try to parse up rest of incomplete buffered char from end + // of last block, and flush it to terminal. + // + if (pub_.buflen() > 0) { // partial UTF-8 to deal with? + while (len>0 && pub_.is_continuation(*buf)) { // buffer 'continuation' chars + if (pub_.append(buf, 1) == false) // append byte to partial UTF-8 buffer + { mod |= handle_unknown_char(); break; } // overrun? break loop + else { buf++; len--; } // shrink our buffer + } + if (pub_.is_complete()) utf8_cache_flush(); // complete UTF-8 captured? flush to tty + if (len <= 0) { // check len again, we may have run out + if (mod) display_modified(); + return; + } + } + + // For sure buf is now pointing at a valid char, so walk to end of buffer + int clen; // char length + const char *p = buf; // ptr to walk buffer + while (len>0) { + clen = fl_utf8len(*p); // how many bytes long is this char? + if (clen == -1) { // not expecting bad UTF-8 here + mod |= handle_unknown_char(); + p += 1; + len -= 1; + } else { + if (len && clen>len) { // char longer than buffer? + if (pub_.append(p, len) == false) { // buffer it + mod |= handle_unknown_char(); + utf8_cache_clear(); + } + break; + } + print_char(p, clen); // write complete UTF-8 char to terminal + p += clen; // advance to next char + len -= clen; // adjust len + mod |= 1; + } + } + if (mod) display_modified(); +} + +/** + Append NULL terminated ASCII string to terminal, + slightly more efficient than append_utf8(). + + - If \p s is NULL, behavior is to do nothing + - Redraws are triggered automatically, depending on redraw_style() +*/ +void Fl_Terminal::append_ascii(const char *s) { + if (!s) return; + while ( *s ) print_char(*s++); // handles display_modified() + display_modified(); +} + +/** + Appends string \p s to the terminal at the current cursor position + using the current text color/attributes. + + If \p s is NULL, the UTF-8 character cache is cleared, which is + recommended before starting a block reading loop, and again after the + block loop has completed. + + If \p len is not specified, it's assumed \p s is a NULL terminated + string. If \p len IS specified, it can be used for writing strings + that aren't NULL terminated, such as block reads on a pipe, network, + or other block oriented data source. + + Redraws of the terminal widget are by default handled automatically, + but can be changed with redraw_rate() and redraw_style(). + + <B>Block I/O</B> + + When reading block oriented sources (such as pipes), append() will + handle partial UTF-8 chars straddling the block boundaries. It does + this using an internal byte cache, which should be cleared before + and after block I/O loops by calling <TT>append(NULL)</TT> as shown + in the example below, to prevent the possibilities of partial UTF-8 + characters left behind by an interrupted or incomplete block loop. + + \par + \code + // Example block reading a command pipe in Unix + + // Run command and read as a pipe + FILE *fp = popen("ls -la", "r"); + if (!fp) { ..error_handling.. } + + // Enable non-blocking I/O + int fd = fileno(fp); + fcntl(fd, F_SETFL, O_NONBLOCK); + + // Clear UTF-8 character cache before starting block loop + G_tty->append(NULL); // prevents leftover partial UTF-8 bytes + + // Block read loop + while (1) { + Fl::wait(0.05); // give fltk .05 secs of cpu to manage UI + ssize_t bytes = read(fd, s, sizeof(s)); // read block from pipe + if (bytes == -1 && errno == EAGAIN) continue; // no data yet? continue + if (bytes > 0) G_tty->append(s); // append output to terminal + else break; // end of pipe? + } + + // Flush cache again after block loop completes + G_tty->append(NULL); + + // Close pipe, done + pclose(fp); + + \endcode + + \note + - String can contain ASCII or UTF-8 chars + - \p len is optional; if unspecified, expects \p s to be a NULL terminated string + - Handles partial UTF-8 chars split between calls (e.g. block oriented writes) + - If \p s is NULL, this clears the "partial UTF-8" character cache + - Redraws are managed automatically by default; see redraw_style() +*/ +void Fl_Terminal::append(const char *s, int len/*=-1*/) { + append_utf8(s, len); +} + +/** + Handle an unknown char by either emitting an error symbol to the tty, or do nothing, + depending on the user configurable value of show_unknown(). + Returns 1 if tty modified, 0 if not. + \see show_unknown() +*/ +int Fl_Terminal::handle_unknown_char(void) { + const char *unknown = "¿"; + if (show_unknown_) { + escseq.reset(); // disable any pending esc seq to prevent eating unknown char + print_char(unknown); + return 1; + } + return 0; +} + +// Handle user interactive scrolling +void Fl_Terminal::scrollbar_cb(Fl_Widget*, void* userdata) { + Fl_Terminal *o = (Fl_Terminal*)userdata; + o->redraw(); +} + +// Handle mouse selection autoscrolling +void Fl_Terminal::autoscroll_timer_cb2(void) { + // Move scrollbar + // NOTE: vscroll is inverted; 0=tab at bot, so minimum() is really max + // + int amt = autoscroll_amt_; // (amt<0):above top, (amt>0):below bottom + int val = vscroll_->value(); + int max = int(vscroll_->minimum()+.5); // NOTE: minimum() is really max + val = (amt<0) ? (val+clamp((-amt/10),1,5)) : // above top edge? + (amt>0) ? (val-clamp((+amt/10),1,5)) : 0; // below bot edge? + val = clamp(val,0,max); // limit val to scroll's range + int diff = ABS(val - vscroll_->value()); // how far scroll tab moved up/dn + // Move scrollbar + vscroll_->value(val); + // Extend selection + if (diff) { // >0 if up or down + int srow = select_.srow(), scol = select_.scol(); + int erow = select_.erow(), ecol = select_.ecol(); + int ltcol = 0, rtcol = ring_cols() - 1; + if (amt<0) { erow -= diff; ecol = ltcol; } // above top? use erow: reverse-selecting + if (amt>0) { erow += diff; ecol = rtcol; } // below bot? use erow: forward-selecting + select_.select(srow, scol, erow, ecol); + } + // Restart timeout + Fl::repeat_timeout(.1, autoscroll_timer_cb, this); + redraw(); +} + +// Handle mouse selection autoscrolling +void Fl_Terminal::autoscroll_timer_cb(void *udata) { + Fl_Terminal *tty = (Fl_Terminal*)udata; + tty->autoscroll_timer_cb2(); +} + +// Handle triggering rate limited redraw() updates +// When data comes in quickly, append() sets the redraw_modified_ flag +// so our timer can trigger the redraw()s at a controlled rate. +// +void Fl_Terminal::redraw_timer_cb2(void) { + //DRAWDEBUG ::printf("--- UPDATE TICK %.02f\n", redraw_rate_); fflush(stdout); + if (redraw_modified_) { + redraw(); // Timer triggered redraw + redraw_modified_ = false; // acknowledge modified flag + Fl::repeat_timeout(redraw_rate_, redraw_timer_cb, this); // restart timer + } else { + // Timer went off and nothing to redraw? disable + Fl::remove_timeout(redraw_timer_cb, this); + redraw_timer_ = false; + } +} + +void Fl_Terminal::redraw_timer_cb(void *udata) { + Fl_Terminal *tty = (Fl_Terminal*)udata; + tty->redraw_timer_cb2(); +} + +/** + The constructor for Fl_Terminal. + + This creates an empty terminal with defaults: + - white on black text; see textfgcolor(Fl_Color), textbgcolor(Fl_Color) + - rows/cols based on the \p W and \p H values, see display_rows(), display_columns() + - scrollback history of 100 lines, see history_rows() + - redraw_style() set to RATE_LIMITED, redraw_rate() set to 0.10 seconds + + Note: While Fl_Terminal derives from Fl_Group, it's not intended for user code + to use it as a parent for other widgets, so end() is called. + + \param[in] X,Y,W,H position and size. + \param[in] L label string (optional), may be NULL. +*/ +Fl_Terminal::Fl_Terminal(int X,int Y,int W,int H,const char*L) : Fl_Group(X,Y,W,H,L) { + // scrollbar_size must be set before scrn_ + scrollbar_size_ = 0; // 0 uses Fl::scrollbar_size() + update_screen_xywh(); + // Tabs + tabstops_ = 0; + tabstops_size_ = 0; + // Init ringbuffer. Also creates default tabstops + create_ring(h_to_row(scrn_.h()), // desired row + w_to_col(scrn_.w()), // desired col + 100); // history size: use 100 for production release + // Misc + redraw_style_ = RATE_LIMITED; // NO_REDRAW, RATE_LIMITED, PER_WRITE + redraw_rate_ = 0.10f; // maximum rate in seconds (1/10=10fps) + redraw_modified_ = false; // display 'modified' flag + redraw_timer_ = false; + autoscroll_dir_ = 0; + autoscroll_amt_ = 0; + + // Create scrollbar + // Final position/size will be set by update_screen() below + // + vscroll_ = new Fl_Scrollbar(x(), y(), scrollbar_size_, h()); + vscroll_->type(FL_VERTICAL); + vscroll_->linesize(1); + vscroll_->slider_size(1); + vscroll_->range(0.0, 0.0); + vscroll_->value(0); + vscroll_->callback(scrollbar_cb, (void*)this); + resizable(0); + Fl_Group::box(FL_DOWN_FRAME); + color(0x0); // black bg by default + update_screen(true); // update internal vars after setting screen size/font + clear_screen_home(); // clear screen, home cursor + clear_history(); // clear history buffer + show_unknown_ = false; // default "off" + ansi_ = true; // default "on" + // End group + end(); +} + +/** + The destructor for Fl_Terminal. + Destroys the terminal display, scroll history, and associated widgets. +*/ +Fl_Terminal::~Fl_Terminal(void) { + // Note: RingBuffer class handles destroying itself + if (tabstops_) + { free(tabstops_); tabstops_ = 0; } + if (autoscroll_dir_) + { Fl::remove_timeout(autoscroll_timer_cb, this); autoscroll_dir_ = 0; } + if (redraw_timer_) + { Fl::remove_timeout(redraw_timer_cb, this); redraw_timer_ = false; } +} + +/** + Returns the scrollbar's actual size; actual width for vertical scrollbars, + actual height for horizontal scrollbars. +*/ +int Fl_Terminal::scrollbar_actual_size(void) const { + return scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size(); +} + +/** + Get the current size of the scrollbar's trough, in pixels. + + If this value is zero (default), this widget will use the + Fl::scrollbar_size() value as the scrollbar's width. + + \returns Scrollbar size in pixels, or 0 if the global Fl::scrollbar_size() is being used. + \see Fl::scrollbar_size(int) +*/ +int Fl_Terminal::scrollbar_size(void) const { + return scrollbar_size_; +} + +/** + Set the width of the scrollbar's trough to \p val, in pixels. + + Only use this method if you need to override the global scrollbar size. + + Setting \p val to the special value 0 causes the widget to + track the global Fl::scrollbar_size(). + + \see Fl::scrollbar_size() +*/ +void Fl_Terminal::scrollbar_size(int val) { + scrollbar_size_ = val; + update_scrollbar(); +} + +//////////////////////////// +////// SCREEN DRAWING ////// +//////////////////////////// + +// Draw the background for the specified ring_chars[] row starting at FLTK coords X,Y +// Note we may be called to draw display, or even history if we're scrolled back. +// If there's any change in bg color, we draw the filled rects here. +// +void Fl_Terminal::draw_row_bg(int grow, int X, int Y) const { + int bg_h = current_style_.fontheight(); + int bg_y = Y - current_style_.fontheight() + current_style_.fontdescent(); + Fl_Color bg_col; + int pwidth = 9; + const Utf8Char *u8c = u8c_ring_row(grow); // start of spec'd row + uchar lastattr = u8c->attrib(); + for (int gcol=0; gcol<ring_cols(); gcol++,u8c++) { // walk columns + // Attribute changed since last char? + if (gcol==0 || u8c->attrib() != lastattr) { + u8c->fl_font_set(current_style_); // pwidth_int() needs fl_font set + lastattr = u8c->attrib(); + } + pwidth = u8c->pwidth_int(); + bg_col = is_inside_selection(grow, gcol) // text in mouse select? + ? select_.selectionbgcolor() // ..use select bg color + : (u8c->attrib() & Fl_Terminal::INVERSE) // Inverse mode? + ? u8c->attr_fg_color(this) // ..use fg color for bg + : u8c->attr_bg_color(this); // ..use bg color for bg + // Draw only if color != 0x0 ('show through' color) or widget's own color(). + if (bg_col != 0xffffffff && bg_col != color()) { + fl_color(bg_col); + fl_rectf(X, bg_y, pwidth, bg_h); + } + X += pwidth; // advance X to next char + } +} + +// Draw specified global row, which is the row in ring_chars[]. +// The global row includes history + display buffers. +// +void Fl_Terminal::draw_row(int grow, int Y) const { + // Draw background color spans, if any + int X = scrn_.x(); + draw_row_bg(grow, X, Y); + + // Draw forground text + int scrollval = vscroll_->value(); + int disp_top = (disp_srow() - scrollval); // top row we need to view + int drow = grow - disp_top; // disp row + bool inside_display = is_disp_ring_row(grow); // row inside 'display'? + int strikeout_y = Y - (current_style_.fontheight() / 3); + int underline_y = Y; + const Utf8Char *u8c = u8c_ring_row(grow); + uchar lastattr = -1; + bool is_cursor; + Fl_Color fg; + for (int gcol=0; gcol<disp_cols(); gcol++,u8c++) { // walk the columns + const int &dcol = gcol; // dcol and gcol are the same + // Are we drawing the cursor? Only if inside display + is_cursor = inside_display ? cursor_.is_rowcol(drow-scrollval, dcol) : 0; + // Attribute changed since last char? + if (u8c->attrib() != lastattr) { + u8c->fl_font_set(current_style_); // pwidth_int() needs fl_font set + lastattr = u8c->attrib(); + } + int pwidth = u8c->pwidth_int(); + // DRAW CURSOR BLOCK - TODO: support other cursor types? + if (is_cursor) { + int cx = X; + int cy = Y - cursor_.h() + current_style_.fontdescent(); + int cw = pwidth; + int ch = cursor_.h(); + fl_color(cursorbgcolor()); + if (Fl::focus() == this) fl_rectf(cx, cy, cw, ch); + else fl_rect(cx, cy, cw, ch); + } + // DRAW TEXT + // 1) Color for text + if (is_cursor) fg = cursorfgcolor(); // color for text under cursor + else fg = is_inside_selection(grow, gcol) // text in mouse selection? + ? select_.selectionfgcolor() // ..use selection FG color + : (u8c->attrib() & Fl_Terminal::INVERSE) // Inverse attrib? + ? u8c->attr_bg_color(this) // ..use char's bg color for fg + : u8c->attr_fg_color(this); // ..use char's fg color for fg + fl_color(fg); + // 2) Font for text - already set by u8c->fl_font_set() in the above + if (is_cursor) { + fl_font(fl_font()|FL_BOLD, fl_size()); // force text under cursor BOLD + lastattr = -1; // (ensure font reset on next iter) + } + // 3) Draw text for UTF-8 char. No need to draw spaces + if (!u8c->is_char(' ')) fl_draw(u8c->text_utf8(), u8c->length(), X, Y); + // 4) Strike or underline? + if (u8c->attrib() & Fl_Terminal::UNDERLINE) fl_line(X, underline_y, X+pwidth, underline_y); + if (u8c->attrib() & Fl_Terminal::STRIKEOUT) fl_line(X, strikeout_y, X+pwidth, strikeout_y); + // Move to next char pixel position + X += pwidth; + } +} + +// Draw the part of the buffer we're scrolled to at FLTK position Y. +// +// This could be anywhere in the buffer, and not just the 'active diplay', +// depending on what the scrollbar is set to. +// +// Handles attributes, colors, text selections, cursor. +// +void Fl_Terminal::draw_buff(int Y) const { + int srow = disp_srow() - vscroll_->value(); + int erow = srow + disp_rows(); + const int rowheight = current_style_.fontheight(); + for (int grow=srow; (grow<erow) && (Y<scrn_.b()); grow++) { + Y += rowheight; // advance Y to bottom left corner of row + draw_row(grow, Y); // draw global row at Y + } +} + +/** + Draws the entire Fl_Terminal. + Lets the group draw itself first (scrollbar should be only member), + followed by the terminal's screen contents. +*/ +void Fl_Terminal::draw(void) { + // Draw group first, terminal last + Fl_Group::draw(); + + if (is_frame(box())) { + // Is box() a frame? Fill area inside frame with rectf(). + // FL_XXX_FRAME types allow Fl_Terminal to have a /flat/ background. + // FL_XXX_BOX types inherit Fl::scheme() which can provide unwanted gradients. + // + fl_color(color()); + // Draw flat field (inside border drawn by Fl_Group::draw() above) + int X = x() + Fl::box_dx(box()); + int Y = y() + Fl::box_dy(box()); + int W = w() - Fl::box_dw(box()); + int H = h() - Fl::box_dh(box()); + W -= vscroll_width(); + fl_rectf(X,Y,W,H); + } + + //DEBUG fl_color(0x80000000); // dark red box inside margins + //DEBUG fl_rect(scrn_); + fl_push_clip(scrn_.x(), scrn_.y(), scrn_.w(), scrn_.h()); + int Y = scrn_.y(); + draw_buff(Y); + fl_pop_clip(); +} + +// Given a width in pixels, return number of columns that "fits" +int Fl_Terminal::w_to_col(int W) const { + return int((float(W) / current_style_.charwidth()) + 0.0); // +.5 overshoots +} + +// Given a height in pixels, return number of rows that "fits" +int Fl_Terminal::h_to_row(int H) const { + return int((float(H) / current_style_.fontheight()) + -0.5); // +.5 overshoots +} + +/** + Handle widget resizing, such as if user resizes parent window. + This may increase the column width of the widget if the width + of the widget is made larger than it was. + + \note Resizing currently does not rewrap existing text. + Currently enlarging makes room for longer lines, and shrinking + the size lets long lines run off the right edge of the display, + hidden from view. This behavior may change in the future to rewrap. +*/ +void Fl_Terminal::resize(int X,int Y,int W,int H) { + // Let group resize itself + Fl_Group::resize(X,Y,W,H); + // Update screen stuff; margins, etc + update_screen(false); // no change in font, just resizing + // resize the display's rows+cols to match window size + refit_disp_to_screen(); +} + +// Handle autoscrolling vals + timer +// If mouse dragged beyond top/bottom, start/continue auto-scroll select +// +void Fl_Terminal::handle_selection_autoscroll(void) { + int Y = Fl::event_y(); + int top = scrn_.y(); + int bot = scrn_.b(); + int dist = (Y < top) ? Y - top : // <0 if above top + (Y > bot) ? Y - bot : 0; // >0 if below bottom + if (dist == 0) { + // Not off edge? stop autoscrolling, done + if (autoscroll_dir_) Fl::remove_timeout(autoscroll_timer_cb, this); + autoscroll_dir_ = 0; + } else { + // Above top/below bot? Start/continue autoscroll select + if (!autoscroll_dir_) Fl::add_timeout(.01, autoscroll_timer_cb, this); + autoscroll_amt_ = dist; // <0 if above top, >0 if below bot + autoscroll_dir_ = (dist < 0) ? 3 : 4; // 3=scrolling up, 4=scrolling dn + } +} + +// Handle mouse selection on LEFT-CLICK push/drag/release +// Returns: 1 if 'handled', 0 if not. +// +int Fl_Terminal::handle_selection(int e) { + int grow=0,gcol=0; + bool is_rowcol = (xy_to_glob_rowcol(Fl::event_x(), Fl::event_y(), grow, gcol) > 0) + ? true : false; + switch (e) { + case FL_PUSH: { + select_.push_rowcol(grow, gcol); + // SHIFT-LEFT-CLICK? Extend or start new + if (Fl::event_state(FL_SHIFT)) { + if (is_selection()) { // extend if select in progress + selection_extend(Fl::event_x(), Fl::event_y()); + return 1; // express interest in FL_DRAG + } + } else { // Start a new selection + if (select_.clear()) redraw(); // clear prev selection + if (is_rowcol) return 1; // express interest in FL_DRAG + } + // Left-Click outside terminal area? + if (!Fl::event_state(FL_SHIFT)) { + select_.push_clear(); + clear_mouse_selection(); + redraw(); + } + return 0; // event NOT handled + } + case FL_DRAG: { + if (is_rowcol) { + if (!is_selection()) { // no selection yet? + if (select_.dragged_off(grow, gcol)) { // dragged off FL_PUSH? enough to start + select_.start_push(); // ..start drag with FL_PUSH position + } + } else { + if (select_.extend(grow, gcol)) redraw(); // redraw if selection changed + } + } + // If we leave scrn area, start timer to auto-scroll+select + handle_selection_autoscroll(); + return 1; + } + case FL_RELEASE: { + select_.push_clear(); + select_.end(); + // middlemouse gets immediate copy of selection + if (is_selection()) { + const char *copy = selection_text(); + if (*copy) Fl::copy(copy, (int)strlen(copy), 0); + free((void*)copy); + } + return 1; + } + default: + break; + } + return 0; +} + +/** + Handle FLTK events. +*/ +int Fl_Terminal::handle(int e) { + int ret = Fl_Group::handle(e); + if (Fl::event_inside(vscroll_)) return ret; // early exit for scrollbar + switch (e) { + case FL_ENTER: + case FL_LEAVE: + return 1; + case FL_UNFOCUS: + case FL_FOCUS: + redraw(); + return Fl::visible_focus() ? 1 : 0; + case FL_KEYBOARD: + // ^C -- Copy? + if ((Fl::event_state()&(FL_CTRL|FL_COMMAND)) && Fl::event_key()=='c') { + const char *copy = is_selection() ? selection_text() : fl_strdup(" "); + if (*copy) Fl::copy(copy, (int)strlen(copy), 1); // paste buffer + free((void*)copy); + return 1; + } + // ^A -- Select all? + if ((Fl::event_state()&(FL_CTRL|FL_COMMAND)) && Fl::event_key()=='a') { + // Select entire screen and history buffer + int srow = disp_srow() - hist_use(); + int erow = disp_srow() + disp_rows()-1; + //DEBUG ::printf("CTRL-A: srow=%d erow=%d\n", srow, erow); + select_.select(srow, 0, erow, disp_cols()-1); + const char *copy = selection_text(); + if (*copy) Fl::copy(copy, (int) strlen(copy), 0); // middle mouse buffer + free((void*)copy); + redraw(); + return 1; + } + // Let scrollbar handle these when we have focus + if (Fl::focus() == this) { + switch (Fl::event_key()) { + case FL_Page_Up: case FL_Page_Down: + case FL_Up: case FL_Down: + case FL_Left: case FL_Right: + return vscroll_->handle(e); + } + } + break; + case FL_PUSH: + if (handle(FL_FOCUS)) Fl::focus(this); // Accepting focus? take it + if (Fl::event_button() == FL_LEFT_MOUSE) // LEFT-CLICK? + { ret = handle_selection(FL_PUSH); } + break; + case FL_DRAG: + // TODO: This logic can probably be improved to allow an FL_PUSH in margins + // to drag into terminal area to start a selection. + if (Fl::event_button() == FL_LEFT_MOUSE) // LEFT-DRAG? + { ret = handle_selection(FL_DRAG); } + break; + case FL_RELEASE: + // Selection mouse release? + if (Fl::event_button() == FL_LEFT_MOUSE) // LEFT-RELEASE? + { ret = handle_selection(FL_RELEASE); } + // Disable autoscroll timer, if any + if (autoscroll_dir_) + { Fl::remove_timeout(autoscroll_timer_cb, this); autoscroll_dir_ = 0; } + break; + } // switch + return ret; +} + +/** + Get the redraw style. + + This determines when the terminal redraws itself while text + is being added to it. + + Value will be one of the Fl_Terminal::RedrawStyle enum values. + + \see redraw_style(Fl_Terminal::RedrawStyle) +*/ +Fl_Terminal::RedrawStyle Fl_Terminal::redraw_style() const { + return redraw_style_; +} + +/** + Set how Fl_Terminal manages screen redrawing. + + This setting is relevant when Fl_Terminal is used for high bandwidth + data; too many redraws will slow things down, too few cause redraws + to be 'choppy' when realtime data comes in. + + Redrawing can be cpu intensive, depending on how many rows/cols are + being displayed; worst case: large display + small font. Speed largely + depends on the end user's graphics hardware and font drawing system. + + \par + | RedrawStyle enum | Description + | :-------------------: | :----------------------------------------------------------- + | \ref NO_REDRAW | App must call redraw() as needed to update text to screen + | \ref RATE_LIMITED | Rate limited, timer controlled redraws. (DEFAULT) See redraw_rate() + | \ref PER_WRITE | Redraw triggered *every* call to append() / printf() / etc. + + The default style is RATE_LIMITED, which is the easiest + to use, and automates redrawing to be capped at 10 redraws per second + max. See redraw_rate(float) to control this automated redraw speed. + + \see redraw_rate(), RedrawStyle +*/ +void Fl_Terminal::redraw_style(RedrawStyle val) { + redraw_style_ = val; + // Disable rate limit timer if it's being turned off + if (redraw_style_ != RATE_LIMITED && redraw_timer_) + { Fl::remove_timeout(redraw_timer_cb, this); redraw_timer_ = false; } +} + +/** + Get max rate redraw speed in floating point seconds. +*/ +float Fl_Terminal::redraw_rate(void) const { + return redraw_rate_; +} + +/** + Set the maximum rate redraw speed in floating point seconds + if redraw_style() is set to RATE_LIMITED. + + When output is sent to the terminal, rather than calling redraw() + right away, a timer is started with this value indicating how long + to wait before calling redraw(), causing the output to be shown. + 0.10 is recommended (1/10th of a second), to limit redraws to no + more than 10 redraws per second. + + The value that works best depends on how fast data arrives, and + how fast the font system can draw text at runtime. + + Values too small cause too many redraws to occur, causing the + terminal to get backlogged if large bursts of data arrive quickly. + Values too large cause realtime output to be too "choppy". +*/ +void Fl_Terminal::redraw_rate(float val) { + redraw_rate_ = val; +} + +/** + Return the "show unknown" flag. + See show_unknown(bool) for more info. +*/ +bool Fl_Terminal::show_unknown(void) const { + return show_unknown_; +} + +/** + Set the "show unknown" flag. + + If true, unknown escape sequences and unprintable control characters + will be shown with the error character "¿". + + If false, those sequences and characters will be ignored. + + \see handle_unknown_char() +*/ +void Fl_Terminal::show_unknown(bool val) { + show_unknown_ = val; +} + +/** + Return the state of the ANSI flag. + \see ansi(bool) +*/ +bool Fl_Terminal::ansi(void) const { + return ansi_; +} + +/** + Enable/disable the ANSI mode flag. + If true, ANSI and VT100/xterm codes will be processed. + If false, these codes won't be processed and will either + be ignored or print the error character "¿", depending on + the value of show_unknown(). + \see show_unknown(), \ref Fl_Terminal_escape_codes +*/ +void Fl_Terminal::ansi(bool val) { + ansi_ = val; + // If disabled, reset the class to clear old state information + if (!ansi_) escseq.reset(); +} + +/** + Return the number of lines of screen history. +*/ +int Fl_Terminal::history_lines(void) const { return history_rows(); } + +/** + Set the number of lines of screen history. + Large values can be briefly heavy on cpu and memory usage. +*/ +void Fl_Terminal::history_lines(int val) { history_rows(val); } + +/** + Appends printf formatted messages to the terminal. + + The string can contain UTF-8, crlf's, and ANSI sequences are + also supported. Example: + \par + \code + #include <FL/Fl_Terminal.H> + int main(..) { + : + // Create a terminal, and append some messages to it + Fl_Terminal *tty = new Fl_Terminal(..); + : + // Append three lines of formatted text to the buffer + tty->printf("The current date is: %s.\nThe time is: %s\n", date_str, time_str); + tty->printf("The current PID is %ld.\n", (long)getpid()); + : + \endcode + \note The expanded string is currently limited to 1024 characters (including NULL). + For longer strings use append() which has no string limits. +*/ +void Fl_Terminal::printf(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + Fl_Terminal::vprintf(fmt, ap); + va_end(ap); +} + +/** + Appends printf formatted messages to the terminal. + + Subclasses can use this to implement their own printf() + functionality. + + The string can contain UTF-8, crlf's, and ANSI sequences are + also supported when ansi(bool) is set to 'true'. + + \note The expanded string is currently limited to 1024 characters (including NULL). + For longer strings use append() which has no string limits. + \param fmt is a printf format string for the message text. + \param ap is a va_list created by va_start() and closed with va_end(), + which the caller is responsible for handling. +*/ +void Fl_Terminal::vprintf(const char *fmt, va_list ap) { + char buffer[1024]; // XXX: should be user configurable.. + ::vsnprintf(buffer, 1024, fmt, ap); + buffer[1024-1] = 0; // XXX: MICROSOFT + append(buffer); +} + diff --git a/src/Makefile b/src/Makefile index a8468e3b3..39a77d29c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -94,6 +94,7 @@ CPPFILES = \ Fl_Table.cxx \ Fl_Table_Row.cxx \ Fl_Tabs.cxx \ + Fl_Terminal.cxx \ Fl_Text_Buffer.cxx \ Fl_Text_Display.cxx \ Fl_Text_Editor.cxx \ diff --git a/src/README-Fl_Terminal.txt b/src/README-Fl_Terminal.txt new file mode 100644 index 000000000..4b1b6bc00 --- /dev/null +++ b/src/README-Fl_Terminal.txt @@ -0,0 +1,586 @@ +// vim: autoindent tabstop=8 shiftwidth=4 expandtab softtabstop=4 + +Fl_Terminal Design Document +=========================== + +When I started this project, I identified the key concepts needed to +implement Fl_Terminal: + + - Draw and manage multiline Unicode text in FLTK effectively, + allowing per-character colors and attributes like underline, + strikeout, background, etc. + + - An efficient screen buffer to handle the "scrollback history" + and "screen display" concepts; the "history" being a scrollback + history of text that scrolls up and off screen from the "display", + and the "display" being where the action is: the cursor can be + moved around and text scrolled up or down. + + - How the vertical scrollbar should provide the user with a way to + scroll back into the scrollback history to allow the user to scroll back + to view the "scrollback history", without stopping the "screen display" + from operating + + - How to manage mouse selection for copy/paste + + - Escape code management to implement VT100 style / ANSI escape codes. + + + ┌─────────────────────────────────────────┬──────────────────────────────┐ + │ NOTE: Abbreviations "hist" and "disp" │ │ + ├─────────────────────────────────────────┘ │ + │ │ + │ "history" may be abbreviated as "hist", and "display" as "disp" in │ + │ both this text and the source code. 4 character names are used so │ + │ they line up cleanly in the source, e.g. │ + │ │ + │ ring_cols = 0; ring_rows = 0; │ + │ hist_cols = 0; ring_cols = 0; │ + │ disp_cols = 0; ring_cols = 0; │ + │ └─┬┘ └─┬┘ │ + │ └────┴─── 4 characters │ + │ │ + └────────────────────────────────────────────────────────────────────────┘ + +So the of these concepts were able to fit into C++ class concepts well. +Those classes being: + + Utf8Char + ======== + Each character on the screen is a "Utf8Char" which can manage + the utf8 encoding of any character as one or more bytes. Also + in that class is a byte for an attribute (underline, bold, etc), + and two integers for fg/bg color. + + RingBuffer + ========== + The RingBuffer class keeps track of the buffer itself, a single + array of Utf8Chars called "ring_chars", and some index numbers + to keep track of how many rows are in the screen's history and + display, named "hist_rows" and "disp_rows". + + The memory layout of the Utf8Char array is: + + ___________________ _ _ + | | ʌ + | | | + | | | + | H i s t o r y | | hist_rows + | | | + | | | + |___________________| _v_ + | | ʌ + | | | + | D i s p l a y | | disp_rows + | | | + |___________________| _v_ + + |<----------------->| + ring_cols + + So it's basically a single continguous array of Utf8Char instances + where any character can be accessed by index# using the formula: + + ring_chars[ (row*ring_cols)+col ] + + ..where 'row' is the desired row, 'col' is the desired column, + and 'ring_cols' is how many columns "wide" the buffer is. + + Methods are used to give access the characters in the buffer. + + A key concept is to allow the starting point of the history and + display to be moved around to implement 'text scrolling', such + as when crlf at the screen bottom causes a 'scroll up'. + + This is simply an "index offset" integer applied to the + hist and disp indexes when drawing the display, e.g. + + Offset is 0: 2 Offset now 2: + ┌───────────────────┐ ──┐ ┌───────────────────┐ + │ │ │ │ D i s p l a y │ + │ │ └─> ├───────────────────┤ + │ │ │ │ + │ H i s t o r y │ │ │ + │ │ │ H i s t o r y │ + │ │ 2 │ │ + ├───────────────────┤ ──┐ │ │ + │ │ │ │ │ + │ │ └─> ├───────────────────┤ + │ D i s p l a y │ │ │ + │ │ │ D i s p l a y │ + │ │ │ │ + └───────────────────┘ └───────────────────┘ + + Offset is 0: 4 Offset now 4: + ┌───────────────────┐ ──┐ ┌───────────────────┐ + │ │ │ │ │ + │ │ │ │ D i s p l a y │ + │ │ │ │ │ + │ H i s t o r y │ └─> ├───────────────────┤ + │ │ │ │ + │ │ 4 │ │ + ├───────────────────┤ ──┐ │ H i s t o r y │ + │ │ │ │ │ + │ │ │ │ │ + │ D i s p l a y │ │ │ │ + │ │ └─> ├───────────────────┤ + │ │ │ D i s p l a y │ + └───────────────────┘ └───────────────────┘ + + The effect of applying an offset trivially implements "text scrolling", + so that no screen memory has to physically moved around, simply changing + the single integer "offset" is enough. The text remains where it was, and + the offset is simply incremented to scroll up. This also automatically + makes it appear the top line in the display is 'scrolled up' into the + last line of the scrollback history. + + If the offset exceeds the size of the ring buffer, it is simply wrapped + back to the beginning of the buffer with a modulo: offset =% ring_rows; + + Indexes into the display and history are also modulo their respective + rows, e.g. + + act_ring_index = (hist_rows + disp_row + offset - scrollbar_pos) % ring_rows; + + This way indexes for ranges can run beyond the bottom of the ring, + and automatically wrap around the ring, e.g. + + Offset now 4: + ┌───────────────────┐ + 2 │ │ + 3 │ D i s p l a y │ + 4 │ │ <- act_disp_row(4) + ├───────────────────┤ + │ │ + │ │ + │ H i s t o r y │ + │ │ + │ │ + │ │ + ├───────────────────┤ + 0 │ D i s p l a y │ + 1 └───────────────────┘ <- ring_rows + 2 : : + 3 : : + disp_row(5) -> 4 :...................: + + Here the "disp_row" is the desired offset into the display, but we + need the actual index into the ring from the top, since that's the + physical array. + + So some simple math calculates the row position based on the "offset", + and the "hist" vs "disp" concepts: + + act_ring_index = (histrows // the display exists AFTER the history, so offset the hist_rows + + offset // include the scroll 'offset' + + disp_row // add the desired row relative to the top of the display (0..disp_rows) + ) % ring_rows; // make sure the resulting index is within the ring buffer (0..ring_rows) + + An additional bit of math makes sure if a negative result occurs, that + negative value works relative to the end of the ring, e.g. + + if (act_ring_index < 0) act_ring_index = ring_rows + act_ring_index; + + This guaratnees the act_ring_index is within the ring buffer's address space, + with all offsets applied. + + The math that implements this can be found in the u8c_xxxx_row() methods, + where "xxxx" is one of the concept regions "ring", "hist" or "disp": + + Utf8Char *u8c; + u8c = u8c_ring_row(rrow); // address within ring, rrow can be 0..(ring_rows-1) + u8c = u8c_hist_row(hrow); // address within hist, hrow can be 0..(hist_rows-1) + u8c = u8c_disp_row(drow); // address within disp, drow can be 0..(disp_rows-1) + + The small bit of math is only involved whenever a new row address is needed, + so in a display that's 80x25, to walk all the characters in the screen, the + math above would only be called 25 times, once for each row, e.g. + + for ( int row=0; row<disp_rows(); row++ ) { // walk rows: disp_rows = 25 + Utf8Char *u8c = u8c_disp_row(row); // get first char in display 'row' + for ( int col=0; col<disp_cols(); col++ ) { // walk cols: disp_cols = 80 + u8c[col].do_something(); // work with the character at row/col + } + } + + So to recap, the concepts here are: + + - The ring buffer itself, a linear array that is conceptually + split into a 2 dimensional array of rows and columns whose + height and width are: + + ring_rows -- how many rows in the entire ring buffer + ring_cols -- how many columns in the ring buffer + nchars -- total chars in ring, e.g. (ring_rows * ring_cols) + + - The "history" within the ring. For simplicity this is thought of + as starting relative to the top of the ring buffer, occupying + ring buffer rows: + + 0..(hist_rows-1) + + - The "display", or "disp", within the ring, just after the "history". + It occupies the ring buffer rows: + + (hist_rows)..(hist_rows+disp_rows-1) + + ..or similarly: + + (hist_rows)..(ring_rows-1) + + - An "offset" used to move the "history" and "display" around within + the ring buffer to implement the "text scrolling" concept. The offset + is applied when new characters are added to the buffer, and during + drawing to find where the display actually is within the ring. + + - A "scrollbar", which only is used when redrawing the screen the user sees, + and is simply an additional offset to all the above, where a scrollback + value of zero (the scrollbar tab at the bottom) shows the display rows, + and the values increase as the user moves the scrolltab upwards, 1 per line, + which is subtracted from the normal starting index to let the user work their + way backwards into the scrollback history. + + The ring buffer allows new content to simply be appended to the ring buffer, + and the index# for the start of the display and start of scrollback history are + simply incremented. So the next time the display is "drawn", it starts at + a different position in the ring. + + This makes scrolling content at high speed trivial, without memory moves. + It also makes the concept of "scrolling" with the scrollbar simple as well, + simply being an extra index offset applied during drawing. + + If the display is enlarged vertically, that's easy too; the display + area is simply defined as being more rows, the history as less rows, + the history use decreased (since what was in the history before is now + being moved into the display), and all the math adjusts accordingly. + +Mouse Selection +=============== + +Dragging the mouse across the screen should highlight the text, allowing the user +to extend the selection either beyond or before the point started. Extending the +drag to the top of the screen should automatically 'scroll up' to select more +lines in the scrollback history, or below the bottom to do the opposite. + +The mouse selection is implemented as a class to keep track of the start/end +row/col positions of the selection, and other details such as a flag indicating +if a selection has been made, what color the fg/bg text should appear when +text is selected, and methods that allow setting and extending the selection, +clearing the selection, and "scrolling" the selection, to ensure the row/col +indexes adjust correctly to track when the screen or scrollbar is scrolled. + + +Redraw Timer +============ + +Knowing when to redraw is tricky with a terminal, because sometimes high volumes +of input will come in asynchronously, so in that case we need to determine when +to redraw the screen to show the new content; too quickly will cause the screen +to spend more time redrawing itself, preventing new input from being added. Too +slowly, the user won't see new information appear in a timely manner. + +To solve this, a rate timer is used to prevent too many redraws: + + - When new data comes in, a 1/10 sec timer is started and a modify flag is set. + + redraw() is NOT called at this time, allowing new data to continue to arrive + quickly. Once the modify flag is set, nothing changes from there. + + - When the 1/10th second timer fires, the callback checks the modify flag: + + - if set, calls redraw(), resets the modify to 0, and calls + Fl::repeat_timeout() to repeat the callback in another 1/10th sec. + + - if clear, no new data came in, so DISABLE the timer, done. + +In this way, redraws don't happen more than 10x per second, and redraw() is called +only when there's new content to see. + +The redraw rate can be set by the user application using the Fl_Terminal::redraw_rate(), +0.10 being the default. + +Some terminal operations necessarily call redraw() directly, such as interactive mouse +selection, or during user scrolling the terminal's scrollbar, where it's important there's +no delay in what the user sees while interacting directly with the widget. + + + + +OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD + OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD + OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD + OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD + + + +RING BUFFER DESCRIPTION +======================= + + The history and display are defined by row indexes into this buffer + which are adjusted whenever the text display is 'scrolled' (e.g. by crlfs) + + The scrollbar is a secondary offset on top of that applied during drawing. + the display. + + Here's what the variables managing the split ring buffer look like: + + + + RING BUFFER + ─ ring_chars[] ─┬─>┌───────────────┐ + ʌ [hist_srow] ──┘ │ │ + ┊ │ │ + ┊ │ H i s t o r y ┊ + hist_rows_┊ │ │ + ┊ │ │ + ┊ │ │ + ┊ │ │ + v [hist_erow] ───>│ │ + ─ [disp_srow] ───>├───────────────┤ + ʌ │ │ + ┊ │ │ + disp_rows_┊ │ D i s p l a y │ + ┊ │ │ + ┊ │ │ + v [ring_erow] ───>│ +│<── ring_bot_ + ─ └───────────────┘ (last valid ptr address in ring) + + │<──────┬──────>│ + ring_cols + hist_cols + disp_cols + + The concept here is the single ring buffer is split into two parts that can differ + in height (rows) but not in width (cols). For instance, typically the history is + many times larger than the display; a typical old school display might be 80x25, + but the history might be 2000 lines. + + ring_row() handles the fact that 'row' might run off the end of the buffer, + depending on where hist_srow starts. For instance, if the display has scrolled + a few lines, the ring buffer arrangement might look like: + + RING BUFFER + ring_chars[] ───>┌───────────────┐ + │ D i s p l a y │ + [disp_erow] ───>│ │ + ├───────────────┤ + [hist_srow] ───>│ │ + │ │ + │ H i s t o r y │ + │ │ + │ │ + │ │ + [hist_erow] ───>│ │ + ├───────────────┤ + [disp_srow] ───>│ │ + │ D i s p l a y │ + │ │ + [ring_erow] ───>│ +│<── ring_bot_ + └───────────────┘ (last valid ptr address in ring) + + + Note how the display 'wraps around', straddling the end of the ring buffer. + So trivially walking ring_chars[] from 'disp_srow' for 'disp_rows' would run + off the end of memory for the ring buffer. Example: + + // BAD! + for ( int row=disp_srow; row<disp_rows; row++ ) { + for ( int col=0; col<disp_cols; col++ ) { + ring_chars[row*disp_cols+col]->do_something(); // BAD! can run off end of array + } + } + + The function u8c_row() can access the Utf8Char* of each row more safely, + ensuring that even if the 'row' index runs off the end of the array, + u8c_row() handles wrapping it for you. So the safe way to walk the chars + of the display can be done this way: + + // GOOD! + for ( int row=disp_srow; row<disp_rows; row++ ) { + Utf8Char *u8c = u8c_row(row); // safe: returns utf8 char for start of each row + for ( int col=0; col<disp_cols; col++ ) { // walk the columns safely + (u8c++)->do_something(); // get/set the utf8 char + } + } + + Walking the history would be the same, just replace disp_xxxx with hist_xxxx. + + One can also use ring_row_normalize() to return an index# that can be directly + used with ring_chars[], the value kept in range of the buffer. + + +RING BUFFER "SCROLLING" +======================= + A ring buffer is used to greatly simplify the act of 'scrolling', which happens a lot + when large amounts of data come in, each CRLF triggering a "scroll" that moves the top + line up into the history buffer. The history buffer can be quite large (1000's of lines), + so it would suck if, on each line scroll, thousands of rows of Utf8Chars had to be + physically moved in memory. + + Much easier to just adjust the srow/erow pointers, which simply affect how drawing + is done, and where the display area is. This trivially handles scrolling by just + adjusting some integers by 1. + + + -- -- HOW SCROLLING UP ONE LINE IS DONE -- -- + + + ring_chars[] ─┬─>┌───────────────┐ ─┐ ring_chars[] ──>┌─────────────────┐ + [hist_srow] ──┘ │ │ │ │x x x x x x x x x│ <-- blanks + │ │ └─> [hist_srow] ──>├─────────────────┤ + │ H i s t │ │ │ + │ │ │ │ + │ │ │ H i s t │ + │ │ │ │ + │ │ │ │ + [hist_erow] ───>│ │ │ │ + [disp_srow] ───>├Line 1─────────┤ ─┐ [hist_erow] ──>│Line 1 │ + │Line 2 │ └─> [disp_srow] ──>├Line 2───────────┤ + │Line 3 │ │Line 3 │ + │ │ │ │ + │ D i s p │ │ D i s p │ + │ │ │ │ +[disp_erow][ring_erow] ───>│Line 24 +│ [ring_erow] ──>│Line 24 +│ + └───────────────┘ └─────────────────┘ + + In the above, Line 1 has effectively "moved" into history because the disp_s/erow + and hist_s/erow variables have just been incremented. + + During resize_display(), we need to preserve the display and history as much as possible + when the ring buffer is enlarged/shrank; the hist_rows size should be maintained, and only + display section changes size based on the FLTK window size. + + + +===================== OLD ====================== OLD ====================== OLD ====================== + +Conventions used for the internals +================================== +This is a large widget, and these are some breadcrumbs for anyone +working on the internals of this class. + + > There is one utf8 char buffer, buff_chars[], the top part is the 'history buffer' + (which the user can scroll back to see), and the 'display buffer' which is the + 'active display'. + > glob or global - refers to global buffer buff_chars[] + > disp or display - refers to display buffer disp_chars[] + > Abbreviations glob/disp/buff/hist used because 4 chars line up nicely + > row/col variable names use a 'g' or 'd' prefix to convey 'g'lobal or 'd'isplay. + > The 'Cursor' class uses row/col for the display (disp_chars[]) because the + cursor is only ever inside the display. + > The 'Selection' class uses row/col for the global buffer (buff_chars[]) + because it can be in the 'history' or the 'display' + > These concepts talk about the same thing: + > global buffer == buff_chars[] == "history buffer" == hist == grow/gcol + > display == disp_chars[] == "active display" == drow/dcol + > There is no hist_chars[] because it's just the top half of buff_chars[] + > There is no hist_height_ because it's the same as hist_max_ + > There is no hist_width_ because it's the same as buff_width_. + + +Fl_Terminal's Class Hierarchy +============================= + + class Fl_Terminal -- Derived from Fl_Group (to parent scrollbars, popup menus, etc) + We mainly use the group's background to draw over in draw(). + Within the terminal classes are the following private/protected classes + that help with bookkeeping and operation of the terminal class: + + class Margin -- Handles the margins around the terminal drawing area + class CharStyle -- The styling for the characters: single byte color + attribute (bold/inverse/etc) + class Cursor -- The attributes of the cursor -- position, color, etc, and some simple movement logic + class Utf8Char -- Visible screen buffer is an array of these, one per character + class RingBuffer -- The ring buffer of Utf8Char's, with the "history" and "display" concept. + class EscapeSeq -- A class to handle parsing Esc sequences, and keeping state info between chars + Single chars go in, and when a complete esc sequence is parsed, the caller + can find out all the integer values and command code easily to figure out + what op to do. + + OVERALL DESIGN: + To handle unicode, the terminal's visible display area is a linear array of pointers to + instances of the 'Utf8Char' class, one instance per character. The arrangement of the array + is much like the IBM PC's video memory, but instead of Char/Attrib byte pairs, the Utf8Char + class handles the more complex per-character data and colors/attributes. + + The cursor x,y value can be quickly converted to an index into this buffer. + Strings are printed into the buffer, again, similar to the IBM PC video memory; + one character at a time into the Utf8Char class instances. + + When the screen redraws, it just walks this array, and calls fl_draw() to draw + the text, one utf8 char at a time, with the colors/fonts/attributes from the Utf8Char class. + + As characters are added, Esc sequences are intercepted and parsed into the EscapeSeq class, + which has a single instance for the terminal. + + For the scrollback history, as lines scrolls off the top of the active display area, + the Utf8Char's are copied to the history buffer, and the active display's top line + is simply rotated to the bottom line and cleared, allowing memory reuse of the Utf8Char's, + to prevent memory churn for the display. The goal is to allow high volume output to the + terminal with a minimum affect on realloc'ing memory. + + OPTIMIZATIONS + Where possible, caching is used to prevent repeated calls to cpu expensive operations, + such as anything to do with calculating unicode character width/height/etc. + +RingBuffer + The ring buffer is split in two; the top part is the history, the bottom part is + the "display area", where new text comes in, where the cursor can be positioned, + and concepts like "scroll up" and "scroll down" all happen. The "history" is simply + a linear buffer where lines pushed up from the display are moved into. + + Methods let one access the ring with index#s: + + - The entire ring buffer can be accessed with: + + for (int i=0; i<ring.ring_rows(); i++) { + Utf8Char *u8c_row = ring.u8c_ring_row(i); + for (int col=0; i<ring.ring_cols(); i++) { + u8c_row[col].xxx(); // access each Utf8Char at the row/col + } + } + + Row#s can be given that are larger than the ring; these are automatically + wrapped around to the top. The ring, "history" and "display" can each be + accessed separately with index#s relative to their position +////////////////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////////////////// + Moved this down to the bottom of the file for now -- not sure where to put this, + but it's useful if one wants to reuse the EscapeSeq class somewhere else. -erco Dec 2022 + + Typical use pattern of EscapeSeq class. + This is unverified code, but should give the general gist; + + while ( *s ) { // walk text that may contain ESC sequences + if ( *s == 0x1b ) { + escseq.parse(*s++); // start parsing ESC seq (does a reset()) + continue; + } else if ( escseq.parse_in_progress() ) { // continuing to parse an ESC seq? + switch (escseq.parse(*s++)) { // parse char, advance s.. + case fail: escseq.reset(); continue; // failed? reset, continue.. + case success: continue; // keep parsing.. + case completed: // parsed complete esc sequence? + break; + } + // Handle parsed esc sequence here.. + switch ( escseq.esc_mode() ) { + case 'm': // ESC[...m? + for ( int i=0; i<escseq.total_vals(); i++ ) { + int val = escseq.val(i); + ..handle values here.. + } + break; + case 'J': // ESC[#J? + ..handle.. + break; + } + escseq.reset(); // done handling escseq, reset() + continue; + } else { + ..handle non-escape chars here.. + } + ++s; // advance thru string + } + +---------------------------------------------------------------------------------------- + diff --git a/src/makedepend b/src/makedepend index bbb4fe04d..0199f9d96 100644 --- a/src/makedepend +++ b/src/makedepend @@ -3878,6 +3878,28 @@ Fl_Tabs.o: ../FL/fl_utf8.h Fl_Tabs.o: ../FL/Fl_Widget.H Fl_Tabs.o: ../FL/Fl_Window.H Fl_Tabs.o: ../FL/platform_types.h +Fl_Terminal.o: ../FL/Enumerations.H +Fl_Terminal.o: ../FL/Fl.H +Fl_Terminal.o: ../FL/fl_attr.h +Fl_Terminal.o: ../FL/Fl_Bitmap.H +Fl_Terminal.o: ../FL/Fl_Cairo.H +Fl_Terminal.o: ../FL/fl_casts.H +Fl_Terminal.o: ../FL/fl_config.h +Fl_Terminal.o: ../FL/fl_draw.H +Fl_Terminal.o: ../FL/Fl_Export.H +Fl_Terminal.o: ../FL/Fl_Group.H +Fl_Terminal.o: ../FL/Fl_Image.H +Fl_Terminal.o: ../FL/Fl_Rect.H +Fl_Terminal.o: ../FL/Fl_Scrollbar.H +Fl_Terminal.o: ../FL/Fl_Slider.H +Fl_Terminal.o: ../FL/fl_string_functions.h +Fl_Terminal.o: ../FL/Fl_Terminal.H +Fl_Terminal.o: ../FL/fl_types.h +Fl_Terminal.o: ../FL/fl_utf8.h +Fl_Terminal.o: ../FL/Fl_Valuator.H +Fl_Terminal.o: ../FL/Fl_Widget.H +Fl_Terminal.o: ../FL/Fl_Window.H +Fl_Terminal.o: ../FL/platform_types.h Fl_Text_Buffer.o: ../config.h Fl_Text_Buffer.o: ../FL/Enumerations.H Fl_Text_Buffer.o: ../FL/Fl.H diff --git a/test/.gitignore b/test/.gitignore index cd29e2fca..316e790b1 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -103,6 +103,7 @@ sudoku symbols table tabs +terminal threads tile tiled_image @@ -141,6 +142,8 @@ resize.cxx resize.h tabs.cxx tabs.h +terminal.cxx +terminal.h tree.cxx tree.h valuators.cxx diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3338d3df6..d9a061a2f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -157,6 +157,7 @@ CREATE_EXAMPLE (sudoku "sudoku.cxx;sudoku.plist;sudoku.icns;sudoku.rc" "fltk_ima CREATE_EXAMPLE (symbols symbols.cxx fltk) CREATE_EXAMPLE (tabs tabs.fl fltk) CREATE_EXAMPLE (table table.cxx fltk) +CREATE_EXAMPLE (terminal terminal.fl fltk) CREATE_EXAMPLE (threads threads.cxx fltk) CREATE_EXAMPLE (tile tile.cxx fltk) CREATE_EXAMPLE (tiled_image tiled_image.cxx fltk) @@ -185,7 +186,7 @@ SET (UNITTEST_SRCS unittest_viewport.cxx unittest_scrollbarsize.cxx unittest_schemes.cxx - unittest_simple_terminal.cxx + unittest_terminal.cxx ) CREATE_EXAMPLE (unittests "${UNITTEST_SRCS}" "${GLDEMO_LIBS}") diff --git a/test/Makefile b/test/Makefile index e69b8a9fd..fd6fddf93 100644 --- a/test/Makefile +++ b/test/Makefile @@ -30,7 +30,7 @@ CPPUNITTEST = \ unittest_viewport.cxx \ unittest_scrollbarsize.cxx \ unittest_schemes.cxx \ - unittest_simple_terminal.cxx \ + unittest_terminal.cxx \ unittest_core.cxx OBJUNITTEST = \ @@ -125,6 +125,7 @@ CPPFILES =\ symbols.cxx \ table.cxx \ tabs.cxx \ + terminal.cxx \ threads.cxx \ tile.cxx \ tiled_image.cxx \ @@ -206,6 +207,7 @@ ALL = \ sudoku$(EXEEXT) \ symbols$(EXEEXT) \ table$(EXEEXT) \ + terminal$(EXEEXT) \ $(THREADS) \ tile$(EXEEXT) \ tiled_image$(EXEEXT) \ @@ -268,6 +270,7 @@ clean: $(RM) radio.cxx radio.h $(RM) resize.cxx resize.h $(RM) tabs.cxx tabs.h + $(RM) terminal.cxx terminal.h $(RM) tree.cxx tree.h $(RM) valuators.cxx valuators.h @@ -618,6 +621,9 @@ table$(EXEEXT): table.o tabs$(EXEEXT): tabs.o tabs.cxx: tabs.fl ../fluid/fluid$(EXEEXT) +terminal$(EXEEXT): terminal.o +terminal.cxx: terminal.fl ../fluid/fluid$(EXEEXT) + threads$(EXEEXT): threads.o # This ensures that we have this dependency even if threads are not # enabled in the current tree... diff --git a/test/browser.cxx b/test/browser.cxx index d269f0d57..0df67433b 100644 --- a/test/browser.cxx +++ b/test/browser.cxx @@ -64,7 +64,7 @@ That was a blank line above this. #include <FL/Fl_Button.H> #include <FL/Fl_Int_Input.H> #include <FL/Fl_Choice.H> -#include <FL/Fl_Simple_Terminal.H> +#include <FL/Fl_Terminal.H> #include <FL/fl_ask.H> #include <stdio.h> #include <string.h> @@ -81,7 +81,7 @@ Fl_Button *top, Fl_Choice *btype; Fl_Choice *wtype; Fl_Int_Input *field; -Fl_Simple_Terminal *tty = 0; +Fl_Terminal *tty = 0; typedef struct { const char *name; @@ -219,7 +219,7 @@ int main(int argc, char **argv) { wtype->value(4); // FL_WHEN_RELEASE_ALWAYS is Fl_Browser's default // Small terminal window for callback messages - tty = new Fl_Simple_Terminal(0,400,720,120); + tty = new Fl_Terminal(0,400,720,120); tty->history_lines(50); tty->ansi(true); diff --git a/test/contrast.cxx b/test/contrast.cxx index 5619635c4..cdc131a82 100644 --- a/test/contrast.cxx +++ b/test/contrast.cxx @@ -26,7 +26,7 @@ #include <FL/Fl_Color_Chooser.H> #include <FL/Fl_Output.H> #include <FL/Fl_Hor_Value_Slider.H> -#include <FL/Fl_Simple_Terminal.H> +#include <FL/Fl_Terminal.H> #include <FL/fl_draw.H> #include <math.h> @@ -93,7 +93,7 @@ public: // global variables -Fl_Simple_Terminal *term = 0; +Fl_Terminal *term = 0; double g_lfg; // perceived lightness of foreground color double g_lbg; // perceived lightness of background color @@ -428,9 +428,9 @@ int main(int argc, char **argv) { int ttw = window.w() - 20; int tth = window.h() - tty - 10; - term = new Fl_Simple_Terminal(ttx, tty, ttw, tth); + term = new Fl_Terminal(ttx, tty, ttw, tth); term->color(FL_WHITE); - term->textcolor(FL_BLACK); + term->textfgcolor(FL_BLACK); term->textsize(13); term->printf("FLTK fl_contrast() test program with different contrast algorithms, version %s\n", version); diff --git a/test/demo.cxx b/test/demo.cxx index d24fd92a2..ce9e8fd21 100644 --- a/test/demo.cxx +++ b/test/demo.cxx @@ -75,7 +75,7 @@ #include <FL/Fl_Button.H> #include <FL/Fl_Menu_Button.H> // right click popup menu #include <FL/Fl_Scheme_Choice.H> -#include <FL/Fl_Simple_Terminal.H> // tty +#include <FL/Fl_Terminal.H> // tty #include <FL/filename.H> #include <FL/platform.H> #include <FL/fl_ask.H> // fl_alert() @@ -102,7 +102,7 @@ void dobut(Fl_Widget *, long); Fl_Double_Window *form = 0; Fl_Group *demogrp = 0; -Fl_Simple_Terminal *tty = 0; +Fl_Terminal *tty = 0; Fl_Scheme_Choice *scheme_choice = 0; Fl_Button *but[9]; Fl_Button *exit_button; @@ -231,11 +231,15 @@ void create_the_forms() { demogrp->end(); // Small debug terminal window parented to window, not demogrp - tty = new Fl_Simple_Terminal(10, FORM_H - 1, FORM_W - 20, 1); + // To show/hide debug terminal, use demo's right-click menu + // + tty = new Fl_Terminal(10, FORM_H - 1, FORM_W - 20, 1); tty->history_lines(50); + tty->display_rows(2); // make display at least 2 rows high, even if not seen + tty->display_columns(100); // make display at least 100 cols wide, even if not seen tty->ansi(true); tty->hide(); - tty->textsize(10); + tty->textsize(12); // End window form->end(); diff --git a/test/file_chooser.cxx b/test/file_chooser.cxx index a79ef2023..46e76d84c 100644 --- a/test/file_chooser.cxx +++ b/test/file_chooser.cxx @@ -36,7 +36,7 @@ #include <FL/Fl_PNM_Image.H> #include <FL/Fl_Light_Button.H> #include <FL/Fl_Double_Window.H> -#include <FL/Fl_Simple_Terminal.H> +#include <FL/Fl_Terminal.H> #include <stdio.h> #include <string.h> @@ -55,7 +55,7 @@ Fl_Input *filter; Fl_File_Browser *files; Fl_File_Chooser *fc; Fl_Shared_Image *image = 0; -Fl_Simple_Terminal *tty = 0; +Fl_Terminal *tty = 0; // for choosing extra groups Fl_Choice *ch_extra; @@ -109,8 +109,9 @@ main(int argc, // I - Number of command-line arguments // Make the main window... window = new Fl_Double_Window(400, 215+TERMINAL_HEIGHT, "File Chooser Test"); - tty = new Fl_Simple_Terminal(0,215,window->w(),TERMINAL_HEIGHT); + tty = new Fl_Terminal(0,215,window->w(),TERMINAL_HEIGHT); tty->ansi(true); + tty->display_columns(100); // at least 100 cols wide, even tho actual window smaller // Group: limit resizing to filter input (not browse button) grp = new Fl_Group(0,10,400,25); diff --git a/test/input.cxx b/test/input.cxx index 70c47c852..685bd6a2f 100644 --- a/test/input.cxx +++ b/test/input.cxx @@ -26,12 +26,12 @@ #include <FL/Fl_Toggle_Button.H> #include <FL/Fl_Light_Button.H> #include <FL/Fl_Color_Chooser.H> -#include <FL/Fl_Simple_Terminal.H> +#include <FL/Fl_Terminal.H> #define TERMINAL_HEIGHT 120 // Globals -Fl_Simple_Terminal *G_tty = 0; +Fl_Terminal *G_tty = 0; void cb(Fl_Widget *ob) { G_tty->printf("Callback for %s '%s'\n",ob->label(),((Fl_Input*)ob)->value()); @@ -91,7 +91,7 @@ int main(int argc, char **argv) { Fl::args(argc, argv); Fl::get_system_colors(); Fl_Window *window = new Fl_Window(400,420+TERMINAL_HEIGHT); - G_tty = new Fl_Simple_Terminal(0,420,window->w(),TERMINAL_HEIGHT); + G_tty = new Fl_Terminal(0,420,window->w(),TERMINAL_HEIGHT); int y = 10; input[0] = new Fl_Input(70,y,300,30,"Normal:"); y += 35; diff --git a/test/input_choice.cxx b/test/input_choice.cxx index b9598d52c..853c79c3a 100644 --- a/test/input_choice.cxx +++ b/test/input_choice.cxx @@ -18,12 +18,12 @@ #include <FL/Fl_Button.H> #include <FL/Fl_Double_Window.H> #include <FL/Fl_Input_Choice.H> -#include <FL/Fl_Simple_Terminal.H> +#include <FL/Fl_Terminal.H> #define TERMINAL_HEIGHT 120 // Globals -Fl_Simple_Terminal *G_tty = 0; +Fl_Terminal *G_tty = 0; void buttcb(Fl_Widget*,void*data) { Fl_Input_Choice *in=(Fl_Input_Choice *)data; @@ -44,7 +44,7 @@ void input_choice_cb(Fl_Widget*,void*data) { int main(int argc, char **argv) { Fl_Double_Window win(300, 200+TERMINAL_HEIGHT); - G_tty = new Fl_Simple_Terminal(0,200,win.w(),TERMINAL_HEIGHT); + G_tty = new Fl_Terminal(0,200,win.w(),TERMINAL_HEIGHT); Fl_Input_Choice in(40,40,100,28,"Test"); in.callback(input_choice_cb, (void*)&in); diff --git a/test/makedepend b/test/makedepend index 24ad1c171..a152cfa0e 100644 --- a/test/makedepend +++ b/test/makedepend @@ -222,7 +222,7 @@ browser.o: ../FL/Fl_Rect.H browser.o: ../FL/Fl_RGB_Image.H browser.o: ../FL/Fl_Scrollbar.H browser.o: ../FL/Fl_Select_Browser.H -browser.o: ../FL/Fl_Simple_Terminal.H +browser.o: ../FL/Fl_Terminal.H browser.o: ../FL/Fl_Slider.H browser.o: ../FL/Fl_Text_Buffer.H browser.o: ../FL/Fl_Text_Display.H @@ -507,7 +507,7 @@ contrast.o: ../FL/Fl_Return_Button.H contrast.o: ../FL/Fl_RGB_Image.H contrast.o: ../FL/Fl_Round_Button.H contrast.o: ../FL/Fl_Scrollbar.H -contrast.o: ../FL/Fl_Simple_Terminal.H +contrast.o: ../FL/Fl_Terminal.H contrast.o: ../FL/Fl_Slider.H contrast.o: ../FL/Fl_Text_Buffer.H contrast.o: ../FL/Fl_Text_Display.H @@ -693,7 +693,7 @@ demo.o: ../FL/Fl_RGB_Image.H demo.o: ../FL/Fl_Scheme.H demo.o: ../FL/Fl_Scheme_Choice.H demo.o: ../FL/Fl_Scrollbar.H -demo.o: ../FL/Fl_Simple_Terminal.H +demo.o: ../FL/Fl_Terminal.H demo.o: ../FL/Fl_Slider.H demo.o: ../FL/Fl_Text_Buffer.H demo.o: ../FL/Fl_Text_Display.H @@ -911,7 +911,7 @@ file_chooser.o: ../FL/Fl_Return_Button.H file_chooser.o: ../FL/Fl_RGB_Image.H file_chooser.o: ../FL/Fl_Scrollbar.H file_chooser.o: ../FL/Fl_Shared_Image.H -file_chooser.o: ../FL/Fl_Simple_Terminal.H +file_chooser.o: ../FL/Fl_Terminal.H file_chooser.o: ../FL/Fl_Slider.H file_chooser.o: ../FL/Fl_Text_Buffer.H file_chooser.o: ../FL/Fl_Text_Display.H @@ -1409,7 +1409,7 @@ input.o: ../FL/Fl_Return_Button.H input.o: ../FL/Fl_RGB_Image.H input.o: ../FL/Fl_Scrollbar.H input.o: ../FL/Fl_Secret_Input.H -input.o: ../FL/Fl_Simple_Terminal.H +input.o: ../FL/Fl_Terminal.H input.o: ../FL/Fl_Slider.H input.o: ../FL/Fl_Text_Buffer.H input.o: ../FL/Fl_Text_Display.H @@ -1448,7 +1448,7 @@ input_choice.o: ../FL/Fl_Preferences.H input_choice.o: ../FL/Fl_Rect.H input_choice.o: ../FL/Fl_RGB_Image.H input_choice.o: ../FL/Fl_Scrollbar.H -input_choice.o: ../FL/Fl_Simple_Terminal.H +input_choice.o: ../FL/Fl_Terminal.H input_choice.o: ../FL/Fl_Slider.H input_choice.o: ../FL/Fl_Text_Buffer.H input_choice.o: ../FL/Fl_Text_Display.H @@ -1626,7 +1626,7 @@ menubar.o: ../FL/Fl_RGB_Image.H menubar.o: ../FL/Fl_Scheme.H menubar.o: ../FL/Fl_Scheme_Choice.H menubar.o: ../FL/Fl_Scrollbar.H -menubar.o: ../FL/Fl_Simple_Terminal.H +menubar.o: ../FL/Fl_Terminal.H menubar.o: ../FL/Fl_Slider.H menubar.o: ../FL/fl_string_functions.h menubar.o: ../FL/Fl_Sys_Menu_Bar.H @@ -1719,7 +1719,7 @@ native-filechooser.o: ../FL/Fl_Rect.H native-filechooser.o: ../FL/Fl_Return_Button.H native-filechooser.o: ../FL/Fl_RGB_Image.H native-filechooser.o: ../FL/Fl_Scrollbar.H -native-filechooser.o: ../FL/Fl_Simple_Terminal.H +native-filechooser.o: ../FL/Fl_Terminal.H native-filechooser.o: ../FL/Fl_Slider.H native-filechooser.o: ../FL/Fl_Text_Buffer.H native-filechooser.o: ../FL/Fl_Text_Display.H @@ -2446,7 +2446,7 @@ table.o: ../FL/Fl_Rect.H table.o: ../FL/Fl_RGB_Image.H table.o: ../FL/Fl_Scroll.H table.o: ../FL/Fl_Scrollbar.H -table.o: ../FL/Fl_Simple_Terminal.H +table.o: ../FL/Fl_Terminal.H table.o: ../FL/Fl_Slider.H table.o: ../FL/Fl_Table.H table.o: ../FL/Fl_Table_Row.H @@ -2595,7 +2595,7 @@ tree.o: ../FL/Fl_Rect.H tree.o: ../FL/Fl_Return_Button.H tree.o: ../FL/Fl_RGB_Image.H tree.o: ../FL/Fl_Scrollbar.H -tree.o: ../FL/Fl_Simple_Terminal.H +tree.o: ../FL/Fl_Terminal.H tree.o: ../FL/Fl_Slider.H tree.o: ../FL/Fl_Text_Buffer.H tree.o: ../FL/Fl_Text_Display.H @@ -2660,7 +2660,7 @@ unittests.o: ../FL/Fl_Preferences.H unittests.o: ../FL/Fl_Rect.H unittests.o: ../FL/Fl_RGB_Image.H unittests.o: ../FL/Fl_Scrollbar.H -unittests.o: ../FL/Fl_Simple_Terminal.H +unittests.o: ../FL/Fl_Terminal.H unittests.o: ../FL/Fl_Slider.H unittests.o: ../FL/fl_string_functions.h unittests.o: ../FL/Fl_Text_Buffer.H @@ -2788,7 +2788,7 @@ unittest_core.o: ../FL/Fl_Preferences.H unittest_core.o: ../FL/Fl_Rect.H unittest_core.o: ../FL/Fl_RGB_Image.H unittest_core.o: ../FL/Fl_Scrollbar.H -unittest_core.o: ../FL/Fl_Simple_Terminal.H +unittest_core.o: ../FL/Fl_Terminal.H unittest_core.o: ../FL/Fl_Slider.H unittest_core.o: ../FL/Fl_Text_Buffer.H unittest_core.o: ../FL/Fl_Text_Display.H @@ -2980,37 +2980,26 @@ unittest_scrollbarsize.o: ../FL/Fl_Widget.H unittest_scrollbarsize.o: ../FL/Fl_Window.H unittest_scrollbarsize.o: ../FL/platform_types.h unittest_scrollbarsize.o: unittests.h -unittest_simple_terminal.o: ../FL/Enumerations.H -unittest_simple_terminal.o: ../FL/Fl.H -unittest_simple_terminal.o: ../FL/fl_attr.h -unittest_simple_terminal.o: ../FL/Fl_Bitmap.H -unittest_simple_terminal.o: ../FL/Fl_Cairo.H -unittest_simple_terminal.o: ../FL/fl_casts.H -unittest_simple_terminal.o: ../FL/fl_config.h -unittest_simple_terminal.o: ../FL/Fl_Device.H -unittest_simple_terminal.o: ../FL/Fl_Double_Window.H -unittest_simple_terminal.o: ../FL/fl_draw.H -unittest_simple_terminal.o: ../FL/Fl_Export.H -unittest_simple_terminal.o: ../FL/Fl_Graphics_Driver.H -unittest_simple_terminal.o: ../FL/Fl_Group.H -unittest_simple_terminal.o: ../FL/Fl_Image.H -unittest_simple_terminal.o: ../FL/Fl_Pixmap.H -unittest_simple_terminal.o: ../FL/Fl_Plugin.H -unittest_simple_terminal.o: ../FL/Fl_Preferences.H -unittest_simple_terminal.o: ../FL/Fl_Rect.H -unittest_simple_terminal.o: ../FL/Fl_RGB_Image.H -unittest_simple_terminal.o: ../FL/Fl_Scrollbar.H -unittest_simple_terminal.o: ../FL/Fl_Simple_Terminal.H -unittest_simple_terminal.o: ../FL/Fl_Slider.H -unittest_simple_terminal.o: ../FL/Fl_Text_Buffer.H -unittest_simple_terminal.o: ../FL/Fl_Text_Display.H -unittest_simple_terminal.o: ../FL/fl_types.h -unittest_simple_terminal.o: ../FL/fl_utf8.h -unittest_simple_terminal.o: ../FL/Fl_Valuator.H -unittest_simple_terminal.o: ../FL/Fl_Widget.H -unittest_simple_terminal.o: ../FL/Fl_Window.H -unittest_simple_terminal.o: ../FL/platform_types.h -unittest_simple_terminal.o: unittests.h +unittest_terminal.o: ../FL/Enumerations.H +unittest_terminal.o: ../FL/Fl.H +unittest_terminal.o: ../FL/fl_attr.h +unittest_terminal.o: ../FL/Fl_Bitmap.H +unittest_terminal.o: ../FL/Fl_Cairo.H +unittest_terminal.o: ../FL/fl_casts.H +unittest_terminal.o: ../FL/fl_config.h +unittest_terminal.o: ../FL/Fl_Double_Window.H +unittest_terminal.o: ../FL/Fl_Export.H +unittest_terminal.o: ../FL/Fl_Group.H +unittest_terminal.o: ../FL/Fl_Image.H +unittest_terminal.o: ../FL/Fl_Rect.H +unittest_terminal.o: ../FL/Fl_Scrollbar.H +unittest_terminal.o: ../FL/Fl_Terminal.H +unittest_terminal.o: ../FL/fl_types.h +unittest_terminal.o: ../FL/fl_utf8.h +unittest_terminal.o: ../FL/Fl_Widget.H +unittest_terminal.o: ../FL/Fl_Window.H +unittest_terminal.o: ../FL/platform_types.h +unittest_terminal.o: unittests.h unittest_symbol.o: ../FL/Enumerations.H unittest_symbol.o: ../FL/Fl.H unittest_symbol.o: ../FL/fl_attr.h @@ -3203,7 +3192,7 @@ valuators.o: ../FL/Fl_Repeat_Button.H valuators.o: ../FL/Fl_RGB_Image.H valuators.o: ../FL/Fl_Roller.H valuators.o: ../FL/Fl_Scrollbar.H -valuators.o: ../FL/Fl_Simple_Terminal.H +valuators.o: ../FL/Fl_Terminal.H valuators.o: ../FL/Fl_Slider.H valuators.o: ../FL/Fl_Spinner.H valuators.o: ../FL/Fl_Text_Buffer.H diff --git a/test/menubar.cxx b/test/menubar.cxx index 034e716e9..e46a60be0 100644 --- a/test/menubar.cxx +++ b/test/menubar.cxx @@ -31,14 +31,14 @@ #include <stdio.h> #include <stdlib.h> #include <FL/fl_draw.H> -#include <FL/Fl_Simple_Terminal.H> +#include <FL/Fl_Terminal.H> #include <FL/fl_ask.H> #include <FL/fl_string_functions.h> #define TERMINAL_HEIGHT 120 // Globals -Fl_Simple_Terminal *G_tty = 0; +Fl_Terminal *G_tty = 0; void window_cb(Fl_Widget* w, void*) { puts("window callback called"); // end of program, so stdout instead of G_tty @@ -235,7 +235,7 @@ int main(int argc, char **argv) { Fl_Scheme_Choice scheme_choice(300, 50, 100, 25, "&scheme"); - G_tty = new Fl_Simple_Terminal(0,400,WIDTH,TERMINAL_HEIGHT); + G_tty = new Fl_Terminal(0,400,WIDTH,TERMINAL_HEIGHT); window.callback(window_cb); Fl_Menu_Bar menubar(0,0,WIDTH,30); menubar.menu(menutable); diff --git a/test/native-filechooser.cxx b/test/native-filechooser.cxx index df2433021..10194f2ca 100644 --- a/test/native-filechooser.cxx +++ b/test/native-filechooser.cxx @@ -25,14 +25,14 @@ #include <FL/Fl_Box.H> #include <FL/Fl_Native_File_Chooser.H> #include <FL/Fl_Help_View.H> -#include <FL/Fl_Simple_Terminal.H> +#include <FL/Fl_Terminal.H> #define TERMINAL_HEIGHT 120 // GLOBALS Fl_Input *G_filename = NULL; Fl_Multiline_Input *G_filter = NULL; -Fl_Simple_Terminal *G_tty = NULL; +Fl_Terminal *G_tty = NULL; void PickFile_CB(Fl_Widget*, void*) { // Create native chooser @@ -106,7 +106,7 @@ int main(int argc, char **argv) { win->size_range(win->w(), win->h(), 0, 0); win->begin(); { - G_tty = new Fl_Simple_Terminal(0,400,win->w(),TERMINAL_HEIGHT); + G_tty = new Fl_Terminal(0,400,win->w(),TERMINAL_HEIGHT); int x = 80, y = 10; G_filename = new Fl_Input(x, y, win->w()-80-10, 25, "Filename"); diff --git a/test/table.cxx b/test/table.cxx index 06df9bf86..95b9742f7 100644 --- a/test/table.cxx +++ b/test/table.cxx @@ -10,7 +10,7 @@ #include <FL/fl_draw.H> #include <FL/fl_ask.H> #include <FL/Fl_Table_Row.H> -#include <FL/Fl_Simple_Terminal.H> +#include <FL/Fl_Terminal.H> #include <stdio.h> #include <string.h> @@ -19,7 +19,7 @@ #define TERMINAL_HEIGHT 120 // Globals -Fl_Simple_Terminal *G_tty = 0; +Fl_Terminal *G_tty = 0; // Simple demonstration class to derive from Fl_Table_Row class DemoTable : public Fl_Table_Row @@ -349,7 +349,7 @@ int main(int argc, char **argv) { Fl_Window win(900, 730+TERMINAL_HEIGHT); - G_tty = new Fl_Simple_Terminal(0,730,win.w(),TERMINAL_HEIGHT); + G_tty = new Fl_Terminal(0,730,win.w(),TERMINAL_HEIGHT); G_table = new DemoTable(20, 20, 860, 460, "Demo"); G_table->selection_color(FL_YELLOW); diff --git a/test/terminal.fl b/test/terminal.fl new file mode 100644 index 000000000..920bfc0b3 --- /dev/null +++ b/test/terminal.fl @@ -0,0 +1,1972 @@ +# data file for the Fltk User Interface Designer (fluid) +version 1.0400 +utf8_in_src +header_name {.h} +code_name {.cxx} +comment {DEVELOPER README + +APPLICATION +----------- +The Application class encompasses the entire application, +and has the widget hierarchy declared by main_window(). + +SELF TESTS +---------- +The "self tests" are all named test_xxx(), and are static +functions because they were designed that way originally +(testterm.cxx), so they remain static. + +They only need access to the global G_tty to access the +terminal for printing, and handle their own state +information. + +To add /new/ tests: +1. Create the test function similar to existing test_xxx(). +2. Add the function's name to the NextEscapeTest() function's +This makes the new test become part of the self tests. + +NextEscapeTest() keeps track of the last test shown as +a static value internally, and the integer argument +tells it how to advance to the next test, either fwd or back. + +The tests themselves may keep state information, so that +a single test might have several pages. The return value +determines whether to recall the test again, or if it's +on the last page. + +} {in_source not_in_header +} + +decl {\#include <stdio.h>} {public global +} + +decl {\#include <stdlib.h> // rand()} {private global +} + +decl {\#include <errno.h>} {public global +} + +decl {\#include <FL/Fl_Window.H>} {public global +} + +decl {\#include <FL/Fl_Terminal.H>} {public global +} + +decl {\#include <FL/Fl_Scheme_Choice.H>} {public global +} + +decl {\#include <FL/fl_ask.H>} {public global +} + +decl {\#include <fcntl.h>} {public global +} + +decl {\#ifdef _WIN32} {public global +} + +decl {\#include <io.h> // _read()} {public global +} + +decl {\#else} {public global +} + +decl {\#include <unistd.h> // read()} {public global +} + +decl {\#endif} {public global +} + +decl {MyTerminal *G_tty;} { + comment {Global access to tty} private global +} + +decl {Application *G_app;} { + comment {Global access to application} private global +} + +decl {////// TERMINAL RING BUFFER DEBUG CLASS //////} {private local +} + +class MyTerminal { + comment {Fl_Terminal derived class that can access ring buffer internals for debugging} open : {public Fl_Terminal} +} { + decl {Fl_Double_Window *ring_debug_win;} {private local + } + decl {Fl_Terminal *debug_tty;} {private local + } + decl {bool interactivecursor;} {private local + } + Function {MyTerminal(int X,int Y,int W,int H, const char *L=0):Fl_Terminal(X,Y,W,H,L)} { + comment CTOR open + } { + code {ring_debug_win = 0; +debug_tty = 0; +interactivecursor = false;} {} + } + Function {show_ring_debug_window()} {open return_type void + } { + code {// Show the internal ring debug window +if (!ring_debug_win) { + ring_debug_win = new Fl_Double_Window(1250,800,"Fl_Terminal Ring Buffer"); + ring_debug_win->resizable(ring_debug_win); + ring_debug_win->callback(debug_winquit_callback, (void*)this); + debug_tty = new Fl_Terminal(0,0,ring_debug_win->w(),ring_debug_win->h()); + debug_tty->textsize(7); + debug_tty->redraw_style(NO_REDRAW); + debug_tty->display_columns(400); + ring_debug_win->end(); +} +ring_debug_win->show(); +// Start timer to update ring debug updates +Fl::add_timeout(.5, ring_debug_timer_callback, (void*)this);} {} + } + Function {interactive_cursor(bool val)} { + comment {Enable/disable interactive cursor movement in test app} open return_type void + } { + code {interactivecursor = val;} {} + } + Function {debug_winquit_callback2()} {return_type void + } { + code {// Hide debug window, disable its update timer +ring_debug_win->hide(); +Fl::remove_timeout(ring_debug_timer_callback, (void*)this);} {} + } + Function {debug_winquit_callback(Fl_Widget*,void *userdata)} {return_type {static void} + } { + code {MyTerminal *o = (MyTerminal*)userdata; +o->debug_winquit_callback2();} {} + } + Function {horiz_bracket(int w)} { + comment {Print a graphic horizontally oriented bracket of width w} return_type void + } { + code {debug_tty->append("┏"); +for (int t=0; t<w-2; t++) { debug_tty->print_char("━"); } +debug_tty->append("┓");} {} + } + Function {update_ring()} {open return_type void + } { + code {// Handle updating the ring debug window's contents +int m1,m2,m3,m4; +get_selection(m1,m2,m3,m4); + +// These reference the Fl_Terminal base class protected members +int x = 5; +debug_tty->append("\\033c"); // reset terminal (clear screen, history, home) +debug_tty->append("\\033[s"); // save cursor +debug_tty->printf("\\033[%dC \\033[7m Ring Index \\033[0m\\n", x); +debug_tty->printf("\\033[%dC ring_rows(): %-3d Mouse Selection (srow,scol,erow,ecol)\\n", x, ring_rows()); +debug_tty->printf("\\033[%dC ring_cols(): %-3d \\033[7m(%d,%d,%d,%d)\\033[0m\\n", x, ring_cols(), m1,m2,m3,m4); +debug_tty->printf("\\033[%dC offset(): %-3d\\n", x, offset()); +debug_tty->printf("\\033[%dC ", x); +horiz_bracket(disp_cols()); +debug_tty->printf("\\033[u"); // recall cursor + +x += disp_cols() + 11; +debug_tty->printf("\\033[%dC \\033[7m History Index \\033[0m\\n", x); +debug_tty->printf("\\033[%dC hist_rows(): %d hist_srow()=%d\\n", x, hist_rows(), hist_srow()); +debug_tty->printf("\\033[%dC hist_cols(): %d hist_erow()=%d\\n", x, hist_cols(), hist_erow()); +debug_tty->printf("\\033[%dC hist_use(): %d\\n", x, hist_use()); +debug_tty->printf("\\033[%dC ", x); +horiz_bracket(disp_cols()); +debug_tty->append("\\033[u"); // recall cursor + +x += disp_cols() + 11; +debug_tty->printf("\\033[%dC \\033[7m Display Index \\033[0m\\n", x); +debug_tty->printf("\\033[%dC disp_rows(): %d disp_srow()=%d\\n", x, disp_rows(), disp_srow()); +debug_tty->printf("\\033[%dC disp_cols(): %d disp_erow()=%d\\n", x, disp_cols(), disp_erow()); +debug_tty->append("\\n"); +debug_tty->printf("\\033[%dC ", x); +horiz_bracket(disp_cols()); +debug_tty->append("\\n"); + +{ + for ( int row=0; row<ring_rows(); row++ ) { + // SHOW RING + Utf8Char *u8c = u8c_ring_row(row); + debug_tty->printf("%3d R[", row); + for ( int col=0; col<ring_cols(); col++,u8c++ ) { + // Get Utf8Char's attrib,fg,bg and make that 'current' to draw the char in that style + debug_tty->textattrib(u8c->attrib()); + debug_tty->textfgcolor(u8c->fgcolor()); + debug_tty->textbgcolor(u8c->bgcolor()); + debug_tty->print_char(u8c->text_utf8()); // print the char in current style + } + debug_tty->append("\\033[0m"); // restore default fg/bg/attrib + debug_tty->append("] "); + // SHOW HISTORY + if ( row < hist_rows() ) { + u8c = u8c_ring_row(hist_srow() + row); + debug_tty->printf("%3d H[", row); + for ( int col=0; col<hist_cols(); col++,u8c++ ) { + // Get Utf8Char's attrib,fg,bg and make that 'current' to draw the char in that style + debug_tty->textattrib(u8c->attrib()); + debug_tty->textfgcolor(u8c->fgcolor()); + debug_tty->textbgcolor(u8c->bgcolor()); + debug_tty->print_char(u8c->text_utf8()); // print the char in current style + } + debug_tty->append("\\033[0m"); // restore default fg/bg/attrib + debug_tty->printf("] "); + } else { + debug_tty->append(" "); + for ( int col=0; col<hist_cols(); col++ ) { debug_tty->append(" "); } + debug_tty->append(" "); + } + // SHOW DISPLAY + if ( row < disp_rows() ) { + u8c = u8c_ring_row(disp_srow() + row); + debug_tty->printf("%3d D[", row); + for ( int col=0; col<disp_cols(); col++,u8c++ ) { + // Get Utf8Char's attrib,fg,bg and make that 'current' to draw the char in that style + debug_tty->textattrib(u8c->attrib()); + debug_tty->textfgcolor(u8c->fgcolor()); + debug_tty->textbgcolor(u8c->bgcolor()); + debug_tty->print_char(u8c->text_utf8()); // print the char in current style + } + debug_tty->append("\\033[0m"); // restore default fg/bg/attrib + debug_tty->append("]\\033[K\\n"); + } else { + debug_tty->append("\\033[K\\n"); + } + } +} +debug_tty->printf("--- END\\033[0J\\n"); // clear eos} {} + } + Function {ring_debug_timer_callback2()} {open return_type void + } { + code {// Update the ring debug window +update_ring(); + +// Redraw after all changes have been made +ring_debug_win->redraw(); +Fl::repeat_timeout(.10, ring_debug_timer_callback, (void*)this);} {} + } + Function {ring_debug_timer_callback(void* userdata)} {return_type {static void} + } { + code {MyTerminal *o = (MyTerminal*)userdata; +o->ring_debug_timer_callback2();} {} + } + Function {handle(int e)} { + comment {Event handler} return_type int + } { + code {switch (e) { + case FL_KEYBOARD: { + if (interactivecursor) { + switch (Fl::event_key()) { + case FL_Up: cursor_up(1,true); redraw(); return 1; + case FL_Down: cursor_down(1,true); redraw(); return 1; + case FL_Left: cursor_left(); redraw(); return 1; + case FL_Right: cursor_right(); redraw(); return 1; + } + } + break; + } +} +return Fl_Terminal::handle(e);} {} + } +} + +decl {////// APPLICATION CLASS //////} {private local +} + +class Application {open +} { + decl {int G_lines;} { + comment {Line counter for +50 line test +} private local + } + Function {Application()} { + comment {// Ctor} + } { + code {// CTOR +G_lines = 1; +make_window(); + +// Do widget init in make_window()'s extra 'Code' section, not here.} {} + } + Function {show(int argc=0, char **argv=0)} { + comment {Show the app window +} return_type void + } { + code {if (argv) win->show(argc,argv); +else win->show();} {} + } + Function {AppendTty(const char *s)} { + comment {Append text to Fl_Terminal (and stdout if radio button set) +} return_type void + } { + code {// Append to Fl_Terminal +G_tty->append(s); + +// Send to stdout too? +if (G_app->stdout_radio->value()) { + printf("%s", s); + fflush(stdout); +}} {} + } + decl {////// ESC SELF TESTS //////} {private local + } + Function {NextEscapeTest(int dir)} { + comment {Advance/Reverse thru escape code tests} open return_type {static void} + } { + code {// *** ADD NEW TESTS HERE *** +typedef int (TestFunc)(bool); +static int testret = 1; // last test return value +static int testnum = 0; +static TestFunc *tests[] = { + test_firstpage, + test_esc_attr, + test_esc_rgbcolors, + test_esc_fgbg_colors, + test_esc_curpos, + test_esc_tabstops, + test_esc_insert_char, + test_esc_clear_test, + test_esc_delete_row, + test_esc_insert_row, + test_esc_scrolling + // ^^^ INSERT NEW TEST FUNCS ABOVE THIS LINE ^^^ +}; + +int numtests = sizeof(tests) / sizeof(TestFunc*); +testnum += (dir * testret); // advance testnum only if last test isn't recall +if (testnum >= numtests) testnum = 0; +if (testnum < 0) testnum = numtests-1; +if (dir == 10000) { // 1000: special case to reset all tests, run first test + testnum = 0; // run first test + for ( int i=0; i<numtests; i++ ) tests[i](true); // reset all tests +} + +/*** NO: THIS SCREWS UP DELETE CURRENT LINE TESTS +// If cursor floating, do a crlf first +if (G_tty->cursor_col()!=0) G_tty->append("\\n"); +****/ + +// Run test, save return value +testret = tests[testnum](false); +G_tty->redraw();} {} + } + Function {show_test(const char **test, int &index)} { + comment {Show test in test[] starting at 'index', returns 0 or 1 +} return_type {static int} + } { + code {// Show test in test[] starting at 'index'. +// 'index' is adjusted to point to next test. +// Returns number to add to parent test index: +// 0 - test shown, call here again for next page in this test +// 1 - test shown, move on to next test for next time +// +while (test[index]) { + G_app->AppendTty(test[index++]); + if (strncmp(test[index-1], "--->", 4) == 0 ) { + if (test[index] == NULL) { index = 0; return 1; } // rewind, tell parent to move to next test + else { return 0; } // tell parent to call us again for next test + } +} +index = 0; +return 1; // hit end of test, reset} {} + } + Function {test_firstpage(bool reset)} { + comment {--- 0000: Test unicode alignment} return_type {static int} + } { + code {G_app->AppendTty( + "\\033[0m\\033[H\\033[2J\\033[3J" // color/attr reset, home, cls, clear history + "hello.\\nLine one\\nLine two\\n" + "\\033[0;0;2m Dim text:\\033[31m red\\033[32m grn\\033[33m yel" + "\\033[34m blu\\033[35m mag\\033[36m cyn\\033[37m wht\\n" + "\\033[0;0;0m Normal text:\\033[31m red\\033[32m grn\\033[33m yel" + "\\033[34m blu\\033[35m mag\\033[36m cyn\\033[37m wht\\n" + "\\033[0;0;1m Bold text:\\033[31m red\\033[32m grn\\033[33m yel" + "\\033[34m blu\\033[35m mag\\033[36m cyn\\033[37m wht\\n" + "\\033[0;1;3m Bold Italic text:\\033[31m red\\033[32m grn\\033[33m yel" + "\\033[34m blu\\033[35m mag\\033[36m cyn\\033[37m wht\\n" + "\\033[0m\\n" + // Language Language + // Alignment Alignment + // <-----> <----> + "Here's some utf8: ||| AAA || AAA\\n" + "╳╳ ██ ▏▏┌─┬┐ ┏━┳┓ ╔═╦╗ ██████ ||| AAA Hell Yeah Jim - English || AAA\\n" + "╳╳ ██ ▏▏│ ││ ┃ ┃┃ ║ ║║ ██ ██ ||| AAA Первый рабочий - Russian || AAA\\n" + "╳╳ ██ ▏▏├─┼┤ ┣━╋┫ ╠═╬╣ ██ ██ ||| AAA テキスト処理法 - Japanese || AAA\\n" + "╳╳ ██ ▏▏└─┴┘ ┗━┻┛ ╚═╩╝ ██████ ||| AAA 香港增补字符集 - Chinese || AAA\\n" + " ||| AAA || AAA\\n" + "underbar: ______ ||| AAA || AAA\\n" + " overbar: ‾‾‾‾‾‾ ||| AAA || AAA\\n" + "\\n" + "\\033[23X >> KEYBOARD KEYS: Arrow keys move cursor <<\\n" + "\\033[23X >> 's'=speed test 'c'=colorbars <<\\n" + "\\033[23X >> 'e'=next esc test 'E'=prev esc test <<\\n" + "\\033[23X >> 'q'=quit <<\\n" + "\\033[23X >> <<\\n" + "\\033[23X >> <<\\n" + "\\n" + "Some floating text. >X<\\b\\033[D"); // put cursor between > < using BS and cursor_left +return 1; // tell parent to move to next test} {} + } + Function {test_esc_attr(bool reset)} { + comment {--- 0010: Test attributes} return_type {static int} + } { + code {static int index = 0; +const char *test[] = { + // Screen 10 + "\\033[H\\033[2J" "ESC TEST 0010: Attributes\\n", + " \\033[0;2m Some Dim text \\033[0m \\033[0m Normal text \\033[0m \\033[0;1m Some Bold text \\033[0m\\n" + " \\033[2;3m Dim italic text \\033[0m \\033[3m Normal italic \\033[0m \\033[1;3m Bold italic text \\033[0m\\n" + " \\033[2;4m Dim underline \\033[0m \\033[4m Normal underline \\033[0m \\033[1;4m Bold underline \\033[0m\\n" + " \\033[2;5m Dim blinking \\033[0m \\033[5m Normal blinking \\033[0m \\033[1;5m Bold blinking \\033[0m\\n" + " \\033[2;7m Dim inverse [█] \\033[0m \\033[7m Normal inverse [█] \\033[0m \\033[1;7m Bold inverse [█] \\033[0m\\n" + " \\033[2;9m Dim strikeout \\033[0m \\033[9m Normal strikeout \\033[0m \\033[1;9m Bold strikeout \\033[0m\\n" + "\\n", + "---> Hit 'e' for next test: ", + NULL +}; +if (reset) { index = 0; return 0; } +return show_test(test, index);} {} + } + Function {test_esc_rgbcolors(bool reset)} { + comment {--- 0020: Test RGB Colors} return_type {static int} + } { + code {static int index = 0; +const char *test[] = { + // Screen 20 + "\\033[2J\\033[H" "ESC TEST 0020: RGB Colors\\n", + "\\033[0m\\n", // NORMAL + "\\tNormal Grayscale: \\033[38;2;32;32;32mXXX" "\\033[38;2;64;64;64mXXX" "\\033[38;2;96;96;96mXXX" "\\033[38;2;128;128;128mXXX", + "\\033[38;2;160;160;160mXXX" "\\033[38;2;192;192;192mXXX" "\\033[38;2;224;224;224mXXX" "\\033[38;2;255;255;255mXXX", + "\\033[38;2;255;0;0m\\n", // change to Red for next row + "\\tNormal RedScale: \\033[38;2;32;0;0mXXX" "\\033[38;2;64;0;0mXXX" "\\033[38;2;96;0;0mXXX" "\\033[38;2;128;0;0mXXX", + "\\033[38;2;160;0;0mXXX" "\\033[38;2;192;0;0mXXX" "\\033[38;2;224;0;0mXXX" "\\033[38;2;255;0;0mXXX", + "\\033[38;2;0;255;0m\\n", // change to Grn for next row + "\\tNormal GrnScale: \\033[38;2;0;32;0mXXX" "\\033[38;2;0;64;0mXXX" "\\033[38;2;0;96;0mXXX" "\\033[38;2;0;128;0mXXX", + "\\033[38;2;0;160;0mXXX" "\\033[38;2;0;192;0mXXX" "\\033[38;2;0;224;0mXXX" "\\033[38;2;0;255;0mXXX", + "\\033[38;2;0;0;255m\\n", // change to Blu for next row + "\\tNormal BluScale: \\033[38;2;0;0;32mXXX" "\\033[38;2;0;0;64mXXX" "\\033[38;2;0;0;96mXXX" "\\033[38;2;0;0;128mXXX", + "\\033[38;2;0;0;160mXXX" "\\033[38;2;0;0;192mXXX" "\\033[38;2;0;0;224mXXX" "\\033[38;2;0;0;255mXXX", + "\\n", + "\\033[0;3m\\n", // ITALIC + "\\tItalic Grayscale: \\033[38;2;32;32;32mXXX" "\\033[38;2;64;64;64mXXX" "\\033[38;2;96;96;96mXXX" "\\033[38;2;128;128;128mXXX", + "\\033[38;2;160;160;160mXXX" "\\033[38;2;192;192;192mXXX" "\\033[38;2;224;224;224mXXX" "\\033[38;2;255;255;255mXXX", + "\\033[38;2;255;0;0m\\n", // change to Red for next row + "\\tItalic RedScale: \\033[38;2;32;0;0mXXX" "\\033[38;2;64;0;0mXXX" "\\033[38;2;96;0;0mXXX" "\\033[38;2;128;0;0mXXX", + "\\033[38;2;160;0;0mXXX" "\\033[38;2;192;0;0mXXX" "\\033[38;2;224;0;0mXXX" "\\033[38;2;255;0;0mXXX", + "\\033[38;2;0;255;0m\\n", // change to Grn for next row + "\\tItalic GrnScale: \\033[38;2;0;32;0mXXX" "\\033[38;2;0;64;0mXXX" "\\033[38;2;0;96;0mXXX" "\\033[38;2;0;128;0mXXX", + "\\033[38;2;0;160;0mXXX" "\\033[38;2;0;192;0mXXX" "\\033[38;2;0;224;0mXXX" "\\033[38;2;0;255;0mXXX", + "\\033[38;2;0;0;255m\\n", // change to Blu for next row + "\\tItalic BluScale: \\033[38;2;0;0;32mXXX" "\\033[38;2;0;0;64mXXX" "\\033[38;2;0;0;96mXXX" "\\033[38;2;0;0;128mXXX", + "\\033[38;2;0;0;160mXXX" "\\033[38;2;0;0;192mXXX" "\\033[38;2;0;0;224mXXX" "\\033[38;2;0;0;255mXXX", + "\\n", + "\\033[0;4m\\n", // UNDERLINE + "\\tUnderl Grayscale: \\033[38;2;32;32;32mXXX" "\\033[38;2;64;64;64mXXX" "\\033[38;2;96;96;96mXXX" "\\033[38;2;128;128;128mXXX", + "\\033[38;2;160;160;160mXXX" "\\033[38;2;192;192;192mXXX" "\\033[38;2;224;224;224mXXX" "\\033[38;2;255;255;255mXXX", + "\\033[38;2;255;0;0m\\n", // change to Red for next row + "\\tUnderl RedScale: \\033[38;2;32;0;0mXXX" "\\033[38;2;64;0;0mXXX" "\\033[38;2;96;0;0mXXX" "\\033[38;2;128;0;0mXXX", + "\\033[38;2;160;0;0mXXX" "\\033[38;2;192;0;0mXXX" "\\033[38;2;224;0;0mXXX" "\\033[38;2;255;0;0mXXX", + "\\033[38;2;0;255;0m\\n", // change to Grn for next row + "\\tUnderl GrnScale: \\033[38;2;0;32;0mXXX" "\\033[38;2;0;64;0mXXX" "\\033[38;2;0;96;0mXXX" "\\033[38;2;0;128;0mXXX", + "\\033[38;2;0;160;0mXXX" "\\033[38;2;0;192;0mXXX" "\\033[38;2;0;224;0mXXX" "\\033[38;2;0;255;0mXXX", + "\\033[38;2;0;0;255m\\n", // change to Blu for next row + "\\tUnderl BluScale: \\033[38;2;0;0;32mXXX" "\\033[38;2;0;0;64mXXX" "\\033[38;2;0;0;96mXXX" "\\033[38;2;0;0;128mXXX", + "\\033[38;2;0;0;160mXXX" "\\033[38;2;0;0;192mXXX" "\\033[38;2;0;0;224mXXX" "\\033[38;2;0;0;255mXXX", + "\\n", + "\\033[0;7m\\n", // INVERSE + "\\tInvers Grayscale: \\033[7;38;2;32;32;32mXXX" "\\033[38;2;64;64;64mXXX" "\\033[38;2;96;96;96mXXX" "\\033[38;2;128;128;128mXXX", + "\\033[38;2;160;160;160mXXX" "\\033[38;2;192;192;192mXXX" "\\033[38;2;224;224;224mXXX" "\\033[38;2;255;255;255mXXX", + "\\033[38;2;255;0;0m\\n", // change to Red for next row + "\\tInvers RedScale: \\033[38;2;32;0;0mXXX" "\\033[38;2;64;0;0mXXX" "\\033[38;2;96;0;0mXXX" "\\033[38;2;128;0;0mXXX", + "\\033[38;2;160;0;0mXXX" "\\033[38;2;192;0;0mXXX" "\\033[38;2;224;0;0mXXX" "\\033[38;2;255;0;0mXXX", + "\\033[38;2;0;255;0m\\n", // change to Grn for next row + "\\tInvers GrnScale: \\033[38;2;0;32;0mXXX" "\\033[38;2;0;64;0mXXX" "\\033[38;2;0;96;0mXXX" "\\033[38;2;0;128;0mXXX", + "\\033[38;2;0;160;0mXXX" "\\033[38;2;0;192;0mXXX" "\\033[38;2;0;224;0mXXX" "\\033[38;2;0;255;0mXXX", + "\\033[38;2;0;0;255m\\n", // change to Blu for next row + "\\tInvers BluScale: \\033[38;2;0;0;32mXXX" "\\033[38;2;0;0;64mXXX" "\\033[38;2;0;0;96mXXX" "\\033[38;2;0;0;128mXXX", + "\\033[38;2;0;0;160mXXX" "\\033[38;2;0;0;192mXXX" "\\033[38;2;0;0;224mXXX" "\\033[38;2;0;0;255mXXX", + "\\033[0m\\n\\n", + "---> Hit 'e' for next test: ", + // Screen 21 + "\\033[2J\\033[H" "ESC TEST 0021: RGB Color Ramps\\n", + "\\n", + "Red: ", + "\\033[48;2;0;0;0m.\\033[48;2;1;0;0m.\\033[48;2;2;0;0m.\\033[48;2;3;0;0m.\\033[48;2;4;0;0m.\\033[48;2;5;0;0m.\\033[48;2;6;0;0m.\\033[48;2;7;0;0m.", + "\\033[48;2;8;0;0m.\\033[48;2;9;0;0m.\\033[48;2;10;0;0m.\\033[48;2;11;0;0m.\\033[48;2;12;0;0m.\\033[48;2;13;0;0m.\\033[48;2;14;0;0m.\\033[48;2;15;0;0m.", + "\\033[48;2;16;0;0m.\\033[48;2;17;0;0m.\\033[48;2;18;0;0m.\\033[48;2;19;0;0m.\\033[48;2;20;0;0m.\\033[48;2;21;0;0m.\\033[48;2;22;0;0m.\\033[48;2;23;0;0m.", + "\\033[48;2;24;0;0m.\\033[48;2;25;0;0m.\\033[48;2;26;0;0m.\\033[48;2;27;0;0m.\\033[48;2;28;0;0m.\\033[48;2;29;0;0m.\\033[48;2;30;0;0m.\\033[48;2;31;0;0m.", + "\\033[48;2;32;0;0m.\\033[48;2;33;0;0m.\\033[48;2;34;0;0m.\\033[48;2;35;0;0m.\\033[48;2;36;0;0m.\\033[48;2;37;0;0m.\\033[48;2;38;0;0m.\\033[48;2;39;0;0m.", + "\\033[48;2;40;0;0m.\\033[48;2;41;0;0m.\\033[48;2;42;0;0m.\\033[48;2;43;0;0m.\\033[48;2;44;0;0m.\\033[48;2;45;0;0m.\\033[48;2;46;0;0m.\\033[48;2;47;0;0m.", + "\\033[48;2;48;0;0m.\\033[48;2;49;0;0m.\\033[48;2;50;0;0m.\\033[48;2;51;0;0m.\\033[48;2;52;0;0m.\\033[48;2;53;0;0m.\\033[48;2;54;0;0m.\\033[48;2;55;0;0m.", + "\\033[48;2;56;0;0m.\\033[48;2;57;0;0m.\\033[48;2;58;0;0m.\\033[48;2;59;0;0m.\\033[48;2;60;0;0m.\\033[48;2;61;0;0m.\\033[48;2;62;0;0m.\\033[48;2;63;0;0m.", + "\\033[48;2;64;0;0m.\\033[48;2;65;0;0m.\\033[48;2;66;0;0m.\\033[48;2;67;0;0m.\\033[48;2;68;0;0m.\\033[48;2;69;0;0m.\\033[48;2;70;0;0m.\\033[48;2;71;0;0m.", + "\\033[48;2;72;0;0m.\\033[48;2;73;0;0m.\\033[48;2;74;0;0m.\\033[48;2;75;0;0m.\\033[48;2;76;0;0m.\\033[48;2;77;0;0m.\\033[48;2;78;0;0m.\\033[48;2;79;0;0m.", + "\\033[48;2;80;0;0m.\\033[48;2;81;0;0m.\\033[48;2;82;0;0m.\\033[48;2;83;0;0m.\\033[48;2;84;0;0m.\\033[48;2;85;0;0m.\\033[48;2;86;0;0m.\\033[48;2;87;0;0m.", + "\\033[48;2;88;0;0m.\\033[48;2;89;0;0m.\\033[48;2;90;0;0m.\\033[48;2;91;0;0m.\\033[48;2;92;0;0m.\\033[48;2;93;0;0m.\\033[48;2;94;0;0m.\\033[48;2;95;0;0m.", + "\\033[0m\\n ", + "\\033[48;2;96;0;0m.\\033[48;2;97;0;0m.\\033[48;2;98;0;0m.\\033[48;2;99;0;0m.\\033[48;2;100;0;0m.\\033[48;2;101;0;0m.\\033[48;2;102;0;0m.\\033[48;2;103;0;0m.", + "\\033[48;2;104;0;0m.\\033[48;2;105;0;0m.\\033[48;2;106;0;0m.\\033[48;2;107;0;0m.\\033[48;2;108;0;0m.\\033[48;2;109;0;0m.\\033[48;2;110;0;0m.\\033[48;2;111;0;0m.", + "\\033[48;2;112;0;0m.\\033[48;2;113;0;0m.\\033[48;2;114;0;0m.\\033[48;2;115;0;0m.\\033[48;2;116;0;0m.\\033[48;2;117;0;0m.\\033[48;2;118;0;0m.\\033[48;2;119;0;0m.", + "\\033[48;2;120;0;0m.\\033[48;2;121;0;0m.\\033[48;2;122;0;0m.\\033[48;2;123;0;0m.\\033[48;2;124;0;0m.\\033[48;2;125;0;0m.\\033[48;2;126;0;0m.\\033[48;2;127;0;0m.", + "\\033[48;2;128;0;0m.\\033[48;2;129;0;0m.\\033[48;2;130;0;0m.\\033[48;2;131;0;0m.\\033[48;2;132;0;0m.\\033[48;2;133;0;0m.\\033[48;2;134;0;0m.\\033[48;2;135;0;0m.", + "\\033[48;2;136;0;0m.\\033[48;2;137;0;0m.\\033[48;2;138;0;0m.\\033[48;2;139;0;0m.\\033[48;2;140;0;0m.\\033[48;2;141;0;0m.\\033[48;2;142;0;0m.\\033[48;2;143;0;0m.", + "\\033[48;2;144;0;0m.\\033[48;2;145;0;0m.\\033[48;2;146;0;0m.\\033[48;2;147;0;0m.\\033[48;2;148;0;0m.\\033[48;2;149;0;0m.\\033[48;2;150;0;0m.\\033[48;2;151;0;0m.", + "\\033[48;2;152;0;0m.\\033[48;2;153;0;0m.\\033[48;2;154;0;0m.\\033[48;2;155;0;0m.\\033[48;2;156;0;0m.\\033[48;2;157;0;0m.\\033[48;2;158;0;0m.\\033[48;2;159;0;0m.", + "\\033[48;2;160;0;0m.\\033[48;2;161;0;0m.\\033[48;2;162;0;0m.\\033[48;2;163;0;0m.\\033[48;2;164;0;0m.\\033[48;2;165;0;0m.\\033[48;2;166;0;0m.\\033[48;2;167;0;0m.", + "\\033[48;2;168;0;0m.\\033[48;2;169;0;0m.\\033[48;2;170;0;0m.\\033[48;2;171;0;0m.\\033[48;2;172;0;0m.\\033[48;2;173;0;0m.\\033[48;2;174;0;0m.\\033[48;2;175;0;0m.", + "\\033[48;2;176;0;0m.\\033[48;2;177;0;0m.\\033[48;2;178;0;0m.\\033[48;2;179;0;0m.\\033[48;2;180;0;0m.\\033[48;2;181;0;0m.\\033[48;2;182;0;0m.\\033[48;2;183;0;0m.", + "\\033[48;2;184;0;0m.\\033[48;2;185;0;0m.\\033[48;2;186;0;0m.\\033[48;2;187;0;0m.\\033[48;2;188;0;0m.\\033[48;2;189;0;0m.\\033[48;2;190;0;0m.\\033[48;2;191;0;0m.", + "\\033[0m\\n ", + "\\033[48;2;192;0;0m.\\033[48;2;193;0;0m.\\033[48;2;194;0;0m.\\033[48;2;195;0;0m.\\033[48;2;196;0;0m.\\033[48;2;197;0;0m.\\033[48;2;198;0;0m.\\033[48;2;199;0;0m.", + "\\033[48;2;200;0;0m.\\033[48;2;201;0;0m.\\033[48;2;202;0;0m.\\033[48;2;203;0;0m.\\033[48;2;204;0;0m.\\033[48;2;205;0;0m.\\033[48;2;206;0;0m.\\033[48;2;207;0;0m.", + "\\033[48;2;208;0;0m.\\033[48;2;209;0;0m.\\033[48;2;210;0;0m.\\033[48;2;211;0;0m.\\033[48;2;212;0;0m.\\033[48;2;213;0;0m.\\033[48;2;214;0;0m.\\033[48;2;215;0;0m.", + "\\033[48;2;216;0;0m.\\033[48;2;217;0;0m.\\033[48;2;218;0;0m.\\033[48;2;219;0;0m.\\033[48;2;220;0;0m.\\033[48;2;221;0;0m.\\033[48;2;222;0;0m.\\033[48;2;223;0;0m.", + "\\033[48;2;224;0;0m.\\033[48;2;225;0;0m.\\033[48;2;226;0;0m.\\033[48;2;227;0;0m.\\033[48;2;228;0;0m.\\033[48;2;229;0;0m.\\033[48;2;230;0;0m.\\033[48;2;231;0;0m.", + "\\033[48;2;232;0;0m.\\033[48;2;233;0;0m.\\033[48;2;234;0;0m.\\033[48;2;235;0;0m.\\033[48;2;236;0;0m.\\033[48;2;237;0;0m.\\033[48;2;238;0;0m.\\033[48;2;239;0;0m.", + "\\033[48;2;240;0;0m.\\033[48;2;241;0;0m.\\033[48;2;242;0;0m.\\033[48;2;243;0;0m.\\033[48;2;244;0;0m.\\033[48;2;245;0;0m.\\033[48;2;246;0;0m.\\033[48;2;247;0;0m.", + "\\033[48;2;248;0;0m.\\033[48;2;249;0;0m.\\033[48;2;250;0;0m.\\033[48;2;251;0;0m.\\033[48;2;252;0;0m.\\033[48;2;253;0;0m.\\033[48;2;254;0;0m.\\033[48;2;255;0;0m.", + "\\033[0m\\n", + "\\n", + "Grn: ", + "\\033[48;2;0;0;0m.\\033[48;2;0;1;0m.\\033[48;2;0;2;0m.\\033[48;2;0;3;0m.\\033[48;2;0;4;0m.\\033[48;2;0;5;0m.\\033[48;2;0;6;0m.\\033[48;2;0;7;0m.", + "\\033[48;2;0;8;0m.\\033[48;2;0;9;0m.\\033[48;2;0;10;0m.\\033[48;2;0;11;0m.\\033[48;2;0;12;0m.\\033[48;2;0;13;0m.\\033[48;2;0;14;0m.\\033[48;2;0;15;0m.", + "\\033[48;2;0;16;0m.\\033[48;2;0;17;0m.\\033[48;2;0;18;0m.\\033[48;2;0;19;0m.\\033[48;2;0;20;0m.\\033[48;2;0;21;0m.\\033[48;2;0;22;0m.\\033[48;2;0;23;0m.", + "\\033[48;2;0;24;0m.\\033[48;2;0;25;0m.\\033[48;2;0;26;0m.\\033[48;2;0;27;0m.\\033[48;2;0;28;0m.\\033[48;2;0;29;0m.\\033[48;2;0;30;0m.\\033[48;2;0;31;0m.", + "\\033[48;2;0;32;0m.\\033[48;2;0;33;0m.\\033[48;2;0;34;0m.\\033[48;2;0;35;0m.\\033[48;2;0;36;0m.\\033[48;2;0;37;0m.\\033[48;2;0;38;0m.\\033[48;2;0;39;0m.", + "\\033[48;2;0;40;0m.\\033[48;2;0;41;0m.\\033[48;2;0;42;0m.\\033[48;2;0;43;0m.\\033[48;2;0;44;0m.\\033[48;2;0;45;0m.\\033[48;2;0;46;0m.\\033[48;2;0;47;0m.", + "\\033[48;2;0;48;0m.\\033[48;2;0;49;0m.\\033[48;2;0;50;0m.\\033[48;2;0;51;0m.\\033[48;2;0;52;0m.\\033[48;2;0;53;0m.\\033[48;2;0;54;0m.\\033[48;2;0;55;0m.", + "\\033[48;2;0;56;0m.\\033[48;2;0;57;0m.\\033[48;2;0;58;0m.\\033[48;2;0;59;0m.\\033[48;2;0;60;0m.\\033[48;2;0;61;0m.\\033[48;2;0;62;0m.\\033[48;2;0;63;0m.", + "\\033[48;2;0;64;0m.\\033[48;2;0;65;0m.\\033[48;2;0;66;0m.\\033[48;2;0;67;0m.\\033[48;2;0;68;0m.\\033[48;2;0;69;0m.\\033[48;2;0;70;0m.\\033[48;2;0;71;0m.", + "\\033[48;2;0;72;0m.\\033[48;2;0;73;0m.\\033[48;2;0;74;0m.\\033[48;2;0;75;0m.\\033[48;2;0;76;0m.\\033[48;2;0;77;0m.\\033[48;2;0;78;0m.\\033[48;2;0;79;0m.", + "\\033[48;2;0;80;0m.\\033[48;2;0;81;0m.\\033[48;2;0;82;0m.\\033[48;2;0;83;0m.\\033[48;2;0;84;0m.\\033[48;2;0;85;0m.\\033[48;2;0;86;0m.\\033[48;2;0;87;0m.", + "\\033[48;2;0;88;0m.\\033[48;2;0;89;0m.\\033[48;2;0;90;0m.\\033[48;2;0;91;0m.\\033[48;2;0;92;0m.\\033[48;2;0;93;0m.\\033[48;2;0;94;0m.\\033[48;2;0;95;0m.", + "\\033[0m\\n ", + "\\033[48;2;0;96;0m.\\033[48;2;0;97;0m.\\033[48;2;0;98;0m.\\033[48;2;0;99;0m.\\033[48;2;0;100;0m.\\033[48;2;0;101;0m.\\033[48;2;0;102;0m.\\033[48;2;0;103;0m.", + "\\033[48;2;0;104;0m.\\033[48;2;0;105;0m.\\033[48;2;0;106;0m.\\033[48;2;0;107;0m.\\033[48;2;0;108;0m.\\033[48;2;0;109;0m.\\033[48;2;0;110;0m.\\033[48;2;0;111;0m.", + "\\033[48;2;0;112;0m.\\033[48;2;0;113;0m.\\033[48;2;0;114;0m.\\033[48;2;0;115;0m.\\033[48;2;0;116;0m.\\033[48;2;0;117;0m.\\033[48;2;0;118;0m.\\033[48;2;0;119;0m.", + "\\033[48;2;0;120;0m.\\033[48;2;0;121;0m.\\033[48;2;0;122;0m.\\033[48;2;0;123;0m.\\033[48;2;0;124;0m.\\033[48;2;0;125;0m.\\033[48;2;0;126;0m.\\033[48;2;0;127;0m.", + "\\033[48;2;0;128;0m.\\033[48;2;0;129;0m.\\033[48;2;0;130;0m.\\033[48;2;0;131;0m.\\033[48;2;0;132;0m.\\033[48;2;0;133;0m.\\033[48;2;0;134;0m.\\033[48;2;0;135;0m.", + "\\033[48;2;0;136;0m.\\033[48;2;0;137;0m.\\033[48;2;0;138;0m.\\033[48;2;0;139;0m.\\033[48;2;0;140;0m.\\033[48;2;0;141;0m.\\033[48;2;0;142;0m.\\033[48;2;0;143;0m.", + "\\033[48;2;0;144;0m.\\033[48;2;0;145;0m.\\033[48;2;0;146;0m.\\033[48;2;0;147;0m.\\033[48;2;0;148;0m.\\033[48;2;0;149;0m.\\033[48;2;0;150;0m.\\033[48;2;0;151;0m.", + "\\033[48;2;0;152;0m.\\033[48;2;0;153;0m.\\033[48;2;0;154;0m.\\033[48;2;0;155;0m.\\033[48;2;0;156;0m.\\033[48;2;0;157;0m.\\033[48;2;0;158;0m.\\033[48;2;0;159;0m.", + "\\033[48;2;0;160;0m.\\033[48;2;0;161;0m.\\033[48;2;0;162;0m.\\033[48;2;0;163;0m.\\033[48;2;0;164;0m.\\033[48;2;0;165;0m.\\033[48;2;0;166;0m.\\033[48;2;0;167;0m.", + "\\033[48;2;0;168;0m.\\033[48;2;0;169;0m.\\033[48;2;0;170;0m.\\033[48;2;0;171;0m.\\033[48;2;0;172;0m.\\033[48;2;0;173;0m.\\033[48;2;0;174;0m.\\033[48;2;0;175;0m.", + "\\033[48;2;0;176;0m.\\033[48;2;0;177;0m.\\033[48;2;0;178;0m.\\033[48;2;0;179;0m.\\033[48;2;0;180;0m.\\033[48;2;0;181;0m.\\033[48;2;0;182;0m.\\033[48;2;0;183;0m.", + "\\033[48;2;0;184;0m.\\033[48;2;0;185;0m.\\033[48;2;0;186;0m.\\033[48;2;0;187;0m.\\033[48;2;0;188;0m.\\033[48;2;0;189;0m.\\033[48;2;0;190;0m.\\033[48;2;0;191;0m.", + "\\033[0m\\n ", + "\\033[48;2;0;192;0m.\\033[48;2;0;193;0m.\\033[48;2;0;194;0m.\\033[48;2;0;195;0m.\\033[48;2;0;196;0m.\\033[48;2;0;197;0m.\\033[48;2;0;198;0m.\\033[48;2;0;199;0m.", + "\\033[48;2;0;200;0m.\\033[48;2;0;201;0m.\\033[48;2;0;202;0m.\\033[48;2;0;203;0m.\\033[48;2;0;204;0m.\\033[48;2;0;205;0m.\\033[48;2;0;206;0m.\\033[48;2;0;207;0m.", + "\\033[48;2;0;208;0m.\\033[48;2;0;209;0m.\\033[48;2;0;210;0m.\\033[48;2;0;211;0m.\\033[48;2;0;212;0m.\\033[48;2;0;213;0m.\\033[48;2;0;214;0m.\\033[48;2;0;215;0m.", + "\\033[48;2;0;216;0m.\\033[48;2;0;217;0m.\\033[48;2;0;218;0m.\\033[48;2;0;219;0m.\\033[48;2;0;220;0m.\\033[48;2;0;221;0m.\\033[48;2;0;222;0m.\\033[48;2;0;223;0m.", + "\\033[48;2;0;224;0m.\\033[48;2;0;225;0m.\\033[48;2;0;226;0m.\\033[48;2;0;227;0m.\\033[48;2;0;228;0m.\\033[48;2;0;229;0m.\\033[48;2;0;230;0m.\\033[48;2;0;231;0m.", + "\\033[48;2;0;232;0m.\\033[48;2;0;233;0m.\\033[48;2;0;234;0m.\\033[48;2;0;235;0m.\\033[48;2;0;236;0m.\\033[48;2;0;237;0m.\\033[48;2;0;238;0m.\\033[48;2;0;239;0m.", + "\\033[48;2;0;240;0m.\\033[48;2;0;241;0m.\\033[48;2;0;242;0m.\\033[48;2;0;243;0m.\\033[48;2;0;244;0m.\\033[48;2;0;245;0m.\\033[48;2;0;246;0m.\\033[48;2;0;247;0m.", + "\\033[48;2;0;248;0m.\\033[48;2;0;249;0m.\\033[48;2;0;250;0m.\\033[48;2;0;251;0m.\\033[48;2;0;252;0m.\\033[48;2;0;253;0m.\\033[48;2;0;254;0m.\\033[48;2;0;255;0m.", + "\\033[0m\\n", + "\\n", + "Blu: ", + "\\033[48;2;0;0;0m.\\033[48;2;0;0;1m.\\033[48;2;0;0;2m.\\033[48;2;0;0;3m.\\033[48;2;0;0;4m.\\033[48;2;0;0;5m.\\033[48;2;0;0;6m.\\033[48;2;0;0;7m.", + "\\033[48;2;0;0;8m.\\033[48;2;0;0;9m.\\033[48;2;0;0;10m.\\033[48;2;0;0;11m.\\033[48;2;0;0;12m.\\033[48;2;0;0;13m.\\033[48;2;0;0;14m.\\033[48;2;0;0;15m.", + "\\033[48;2;0;0;16m.\\033[48;2;0;0;17m.\\033[48;2;0;0;18m.\\033[48;2;0;0;19m.\\033[48;2;0;0;20m.\\033[48;2;0;0;21m.\\033[48;2;0;0;22m.\\033[48;2;0;0;23m.", + "\\033[48;2;0;0;24m.\\033[48;2;0;0;25m.\\033[48;2;0;0;26m.\\033[48;2;0;0;27m.\\033[48;2;0;0;28m.\\033[48;2;0;0;29m.\\033[48;2;0;0;30m.\\033[48;2;0;0;31m.", + "\\033[48;2;0;0;32m.\\033[48;2;0;0;33m.\\033[48;2;0;0;34m.\\033[48;2;0;0;35m.\\033[48;2;0;0;36m.\\033[48;2;0;0;37m.\\033[48;2;0;0;38m.\\033[48;2;0;0;39m.", + "\\033[48;2;0;0;40m.\\033[48;2;0;0;41m.\\033[48;2;0;0;42m.\\033[48;2;0;0;43m.\\033[48;2;0;0;44m.\\033[48;2;0;0;45m.\\033[48;2;0;0;46m.\\033[48;2;0;0;47m.", + "\\033[48;2;0;0;48m.\\033[48;2;0;0;49m.\\033[48;2;0;0;50m.\\033[48;2;0;0;51m.\\033[48;2;0;0;52m.\\033[48;2;0;0;53m.\\033[48;2;0;0;54m.\\033[48;2;0;0;55m.", + "\\033[48;2;0;0;56m.\\033[48;2;0;0;57m.\\033[48;2;0;0;58m.\\033[48;2;0;0;59m.\\033[48;2;0;0;60m.\\033[48;2;0;0;61m.\\033[48;2;0;0;62m.\\033[48;2;0;0;63m.", + "\\033[48;2;0;0;64m.\\033[48;2;0;0;65m.\\033[48;2;0;0;66m.\\033[48;2;0;0;67m.\\033[48;2;0;0;68m.\\033[48;2;0;0;69m.\\033[48;2;0;0;70m.\\033[48;2;0;0;71m.", + "\\033[48;2;0;0;72m.\\033[48;2;0;0;73m.\\033[48;2;0;0;74m.\\033[48;2;0;0;75m.\\033[48;2;0;0;76m.\\033[48;2;0;0;77m.\\033[48;2;0;0;78m.\\033[48;2;0;0;79m.", + "\\033[48;2;0;0;80m.\\033[48;2;0;0;81m.\\033[48;2;0;0;82m.\\033[48;2;0;0;83m.\\033[48;2;0;0;84m.\\033[48;2;0;0;85m.\\033[48;2;0;0;86m.\\033[48;2;0;0;87m.", + "\\033[48;2;0;0;88m.\\033[48;2;0;0;89m.\\033[48;2;0;0;90m.\\033[48;2;0;0;91m.\\033[48;2;0;0;92m.\\033[48;2;0;0;93m.\\033[48;2;0;0;94m.\\033[48;2;0;0;95m.", + "\\033[0m\\n ", + "\\033[48;2;0;0;96m.\\033[48;2;0;0;97m.\\033[48;2;0;0;98m.\\033[48;2;0;0;99m.\\033[48;2;0;0;100m.\\033[48;2;0;0;101m.\\033[48;2;0;0;102m.\\033[48;2;0;0;103m.", + "\\033[48;2;0;0;104m.\\033[48;2;0;0;105m.\\033[48;2;0;0;106m.\\033[48;2;0;0;107m.\\033[48;2;0;0;108m.\\033[48;2;0;0;109m.\\033[48;2;0;0;110m.\\033[48;2;0;0;111m.", + "\\033[48;2;0;0;112m.\\033[48;2;0;0;113m.\\033[48;2;0;0;114m.\\033[48;2;0;0;115m.\\033[48;2;0;0;116m.\\033[48;2;0;0;117m.\\033[48;2;0;0;118m.\\033[48;2;0;0;119m.", + "\\033[48;2;0;0;120m.\\033[48;2;0;0;121m.\\033[48;2;0;0;122m.\\033[48;2;0;0;123m.\\033[48;2;0;0;124m.\\033[48;2;0;0;125m.\\033[48;2;0;0;126m.\\033[48;2;0;0;127m.", + "\\033[48;2;0;0;128m.\\033[48;2;0;0;129m.\\033[48;2;0;0;130m.\\033[48;2;0;0;131m.\\033[48;2;0;0;132m.\\033[48;2;0;0;133m.\\033[48;2;0;0;134m.\\033[48;2;0;0;135m.", + "\\033[48;2;0;0;136m.\\033[48;2;0;0;137m.\\033[48;2;0;0;138m.\\033[48;2;0;0;139m.\\033[48;2;0;0;140m.\\033[48;2;0;0;141m.\\033[48;2;0;0;142m.\\033[48;2;0;0;143m.", + "\\033[48;2;0;0;144m.\\033[48;2;0;0;145m.\\033[48;2;0;0;146m.\\033[48;2;0;0;147m.\\033[48;2;0;0;148m.\\033[48;2;0;0;149m.\\033[48;2;0;0;150m.\\033[48;2;0;0;151m.", + "\\033[48;2;0;0;152m.\\033[48;2;0;0;153m.\\033[48;2;0;0;154m.\\033[48;2;0;0;155m.\\033[48;2;0;0;156m.\\033[48;2;0;0;157m.\\033[48;2;0;0;158m.\\033[48;2;0;0;159m.", + "\\033[48;2;0;0;160m.\\033[48;2;0;0;161m.\\033[48;2;0;0;162m.\\033[48;2;0;0;163m.\\033[48;2;0;0;164m.\\033[48;2;0;0;165m.\\033[48;2;0;0;166m.\\033[48;2;0;0;167m.", + "\\033[48;2;0;0;168m.\\033[48;2;0;0;169m.\\033[48;2;0;0;170m.\\033[48;2;0;0;171m.\\033[48;2;0;0;172m.\\033[48;2;0;0;173m.\\033[48;2;0;0;174m.\\033[48;2;0;0;175m.", + "\\033[48;2;0;0;176m.\\033[48;2;0;0;177m.\\033[48;2;0;0;178m.\\033[48;2;0;0;179m.\\033[48;2;0;0;180m.\\033[48;2;0;0;181m.\\033[48;2;0;0;182m.\\033[48;2;0;0;183m.", + "\\033[48;2;0;0;184m.\\033[48;2;0;0;185m.\\033[48;2;0;0;186m.\\033[48;2;0;0;187m.\\033[48;2;0;0;188m.\\033[48;2;0;0;189m.\\033[48;2;0;0;190m.\\033[48;2;0;0;191m.", + "\\033[0m\\n ", + "\\033[48;2;0;0;192m.\\033[48;2;0;0;193m.\\033[48;2;0;0;194m.\\033[48;2;0;0;195m.\\033[48;2;0;0;196m.\\033[48;2;0;0;197m.\\033[48;2;0;0;198m.\\033[48;2;0;0;199m.", + "\\033[48;2;0;0;200m.\\033[48;2;0;0;201m.\\033[48;2;0;0;202m.\\033[48;2;0;0;203m.\\033[48;2;0;0;204m.\\033[48;2;0;0;205m.\\033[48;2;0;0;206m.\\033[48;2;0;0;207m.", + "\\033[48;2;0;0;208m.\\033[48;2;0;0;209m.\\033[48;2;0;0;210m.\\033[48;2;0;0;211m.\\033[48;2;0;0;212m.\\033[48;2;0;0;213m.\\033[48;2;0;0;214m.\\033[48;2;0;0;215m.", + "\\033[48;2;0;0;216m.\\033[48;2;0;0;217m.\\033[48;2;0;0;218m.\\033[48;2;0;0;219m.\\033[48;2;0;0;220m.\\033[48;2;0;0;221m.\\033[48;2;0;0;222m.\\033[48;2;0;0;223m.", + "\\033[48;2;0;0;224m.\\033[48;2;0;0;225m.\\033[48;2;0;0;226m.\\033[48;2;0;0;227m.\\033[48;2;0;0;228m.\\033[48;2;0;0;229m.\\033[48;2;0;0;230m.\\033[48;2;0;0;231m.", + "\\033[48;2;0;0;232m.\\033[48;2;0;0;233m.\\033[48;2;0;0;234m.\\033[48;2;0;0;235m.\\033[48;2;0;0;236m.\\033[48;2;0;0;237m.\\033[48;2;0;0;238m.\\033[48;2;0;0;239m.", + "\\033[48;2;0;0;240m.\\033[48;2;0;0;241m.\\033[48;2;0;0;242m.\\033[48;2;0;0;243m.\\033[48;2;0;0;244m.\\033[48;2;0;0;245m.\\033[48;2;0;0;246m.\\033[48;2;0;0;247m.", + "\\033[48;2;0;0;248m.\\033[48;2;0;0;249m.\\033[48;2;0;0;250m.\\033[48;2;0;0;251m.\\033[48;2;0;0;252m.\\033[48;2;0;0;253m.\\033[48;2;0;0;254m.\\033[48;2;0;0;255m.", + "\\033[0m\\n ", + "\\n", + /** + For generating an HSV hue ramp, see: + https://en.wikipedia.org/wiki/HSL_and_HSV\#/media/File:HSV-RGB-comparison.svg + Basically: + + 255 : RRRRRRR RRRRRRRR + : R R + : R R + : R RED R + : R R + : R R + : R R + 0 : RRRRRRRRRRRRRRRR + :..|......|......|......|......|......|......| + 0 60 120 180 240 300 360 + + + + 255 : GGGGGGGGGGGGGG + : G G + : G G + : G G + : G GREEN G + : G G + : G G + 0 : G GGGGGGGGGGGGGGGG + :..|......|......|......|......|......|......| + 0 60 120 180 240 300 360 + + + 255 : BBBBBBBBBBBBBBB + : B B + : B B + : B BLUE B + : B B + : B B + : B B + 0 : GBBBBBBBBBBBBBB B + :..|......|......|......|......|......|......| + 0 60 120 180 240 300 360 + Here's some perl code that generates the Hue ramp: + $count = 0; + for ($t=0; $t<256;$t+=2) { printf("\\\\033[48;2;%03d;%03d;%03dm ",255,$t,0); if ((++$count % 8) == 0) { printf("\\n"); } } + for ($t=0; $t<256;$t+=2) { printf("\\\\033[48;2;%03d;%03d;%03dm ",255-$t,255,0); if ((++$count % 8) == 0) { printf("\\n"); } } + for ($t=0; $t<256;$t+=2) { printf("\\\\033[48;2;%03d;%03d;%03dm ",0,255,$t); if ((++$count % 8) == 0) { printf("\\n"); } } + for ($t=0; $t<256;$t+=2) { printf("\\\\033[48;2;%03d;%03d;%03dm ",0,255-$t,255); if ((++$count % 8) == 0) { printf("\\n"); } } + for ($t=0; $t<256;$t+=2) { printf("\\\\033[48;2;%03d;%03d;%03dm ",$t,0,255); if ((++$count % 8) == 0) { printf("\\n"); } } + for ($t=0; $t<256;$t+=2) { printf("\\\\033[48;2;%03d;%03d;%03dm ",255,0,255-$t); if ((++$count % 8) == 0) { printf("\\n"); } } +**/ + "Hue: ", + "\\033[48;2;255;000;000m.\\033[48;2;255;001;000m.\\033[48;2;255;002;000m.\\033[48;2;255;003;000m.\\033[48;2;255;004;000m.\\033[48;2;255;005;000m.\\033[48;2;255;006;000m.\\033[48;2;255;007;000m.", + "\\033[48;2;255;008;000m.\\033[48;2;255;009;000m.\\033[48;2;255;010;000m.\\033[48;2;255;011;000m.\\033[48;2;255;012;000m.\\033[48;2;255;013;000m.\\033[48;2;255;014;000m.\\033[48;2;255;015;000m.", + "\\033[48;2;255;016;000m.\\033[48;2;255;017;000m.\\033[48;2;255;018;000m.\\033[48;2;255;019;000m.\\033[48;2;255;020;000m.\\033[48;2;255;021;000m.\\033[48;2;255;022;000m.\\033[48;2;255;023;000m.", + "\\033[48;2;255;024;000m.\\033[48;2;255;025;000m.\\033[48;2;255;026;000m.\\033[48;2;255;027;000m.\\033[48;2;255;028;000m.\\033[48;2;255;029;000m.\\033[48;2;255;030;000m.\\033[48;2;255;031;000m.", + "\\033[48;2;255;032;000m.\\033[48;2;255;033;000m.\\033[48;2;255;034;000m.\\033[48;2;255;035;000m.\\033[48;2;255;036;000m.\\033[48;2;255;037;000m.\\033[48;2;255;038;000m.\\033[48;2;255;039;000m.", + "\\033[48;2;255;040;000m.\\033[48;2;255;041;000m.\\033[48;2;255;042;000m.\\033[48;2;255;043;000m.\\033[48;2;255;044;000m.\\033[48;2;255;045;000m.\\033[48;2;255;046;000m.\\033[48;2;255;047;000m.", + "\\033[48;2;255;048;000m.\\033[48;2;255;049;000m.\\033[48;2;255;050;000m.\\033[48;2;255;051;000m.\\033[48;2;255;052;000m.\\033[48;2;255;053;000m.\\033[48;2;255;054;000m.\\033[48;2;255;055;000m.", + "\\033[48;2;255;056;000m.\\033[48;2;255;057;000m.\\033[48;2;255;058;000m.\\033[48;2;255;059;000m.\\033[48;2;255;060;000m.\\033[48;2;255;061;000m.\\033[48;2;255;062;000m.\\033[48;2;255;063;000m.", + "\\033[48;2;255;064;000m.\\033[48;2;255;065;000m.\\033[48;2;255;066;000m.\\033[48;2;255;067;000m.\\033[48;2;255;068;000m.\\033[48;2;255;069;000m.\\033[48;2;255;070;000m.\\033[48;2;255;071;000m.", + "\\033[48;2;255;072;000m.\\033[48;2;255;073;000m.\\033[48;2;255;074;000m.\\033[48;2;255;075;000m.\\033[48;2;255;076;000m.\\033[48;2;255;077;000m.\\033[48;2;255;078;000m.\\033[48;2;255;079;000m.", + "\\033[48;2;255;080;000m.\\033[48;2;255;081;000m.\\033[48;2;255;082;000m.\\033[48;2;255;083;000m.\\033[48;2;255;084;000m.\\033[48;2;255;085;000m.\\033[48;2;255;086;000m.\\033[48;2;255;087;000m.", + "\\033[48;2;255;088;000m.\\033[48;2;255;089;000m.\\033[48;2;255;090;000m.\\033[48;2;255;091;000m.\\033[48;2;255;092;000m.\\033[48;2;255;093;000m.\\033[48;2;255;094;000m.\\033[48;2;255;095;000m.", + "\\033[0m\\n ", + "\\033[48;2;255;096;000m.\\033[48;2;255;097;000m.\\033[48;2;255;098;000m.\\033[48;2;255;099;000m.\\033[48;2;255;100;000m.\\033[48;2;255;101;000m.\\033[48;2;255;102;000m.\\033[48;2;255;103;000m.", + "\\033[48;2;255;104;000m.\\033[48;2;255;105;000m.\\033[48;2;255;106;000m.\\033[48;2;255;107;000m.\\033[48;2;255;108;000m.\\033[48;2;255;109;000m.\\033[48;2;255;110;000m.\\033[48;2;255;111;000m.", + "\\033[48;2;255;112;000m.\\033[48;2;255;113;000m.\\033[48;2;255;114;000m.\\033[48;2;255;115;000m.\\033[48;2;255;116;000m.\\033[48;2;255;117;000m.\\033[48;2;255;118;000m.\\033[48;2;255;119;000m.", + "\\033[48;2;255;120;000m.\\033[48;2;255;121;000m.\\033[48;2;255;122;000m.\\033[48;2;255;123;000m.\\033[48;2;255;124;000m.\\033[48;2;255;125;000m.\\033[48;2;255;126;000m.\\033[48;2;255;127;000m.", + "\\033[48;2;255;128;000m.\\033[48;2;255;129;000m.\\033[48;2;255;130;000m.\\033[48;2;255;131;000m.\\033[48;2;255;132;000m.\\033[48;2;255;133;000m.\\033[48;2;255;134;000m.\\033[48;2;255;135;000m.", + "\\033[48;2;255;136;000m.\\033[48;2;255;137;000m.\\033[48;2;255;138;000m.\\033[48;2;255;139;000m.\\033[48;2;255;140;000m.\\033[48;2;255;141;000m.\\033[48;2;255;142;000m.\\033[48;2;255;143;000m.", + "\\033[48;2;255;144;000m.\\033[48;2;255;145;000m.\\033[48;2;255;146;000m.\\033[48;2;255;147;000m.\\033[48;2;255;148;000m.\\033[48;2;255;149;000m.\\033[48;2;255;150;000m.\\033[48;2;255;151;000m.", + "\\033[48;2;255;152;000m.\\033[48;2;255;153;000m.\\033[48;2;255;154;000m.\\033[48;2;255;155;000m.\\033[48;2;255;156;000m.\\033[48;2;255;157;000m.\\033[48;2;255;158;000m.\\033[48;2;255;159;000m.", + "\\033[48;2;255;160;000m.\\033[48;2;255;161;000m.\\033[48;2;255;162;000m.\\033[48;2;255;163;000m.\\033[48;2;255;164;000m.\\033[48;2;255;165;000m.\\033[48;2;255;166;000m.\\033[48;2;255;167;000m.", + "\\033[48;2;255;168;000m.\\033[48;2;255;169;000m.\\033[48;2;255;170;000m.\\033[48;2;255;171;000m.\\033[48;2;255;172;000m.\\033[48;2;255;173;000m.\\033[48;2;255;174;000m.\\033[48;2;255;175;000m.", + "\\033[48;2;255;176;000m.\\033[48;2;255;177;000m.\\033[48;2;255;178;000m.\\033[48;2;255;179;000m.\\033[48;2;255;180;000m.\\033[48;2;255;181;000m.\\033[48;2;255;182;000m.\\033[48;2;255;183;000m.", + "\\033[48;2;255;184;000m.\\033[48;2;255;185;000m.\\033[48;2;255;186;000m.\\033[48;2;255;187;000m.\\033[48;2;255;188;000m.\\033[48;2;255;189;000m.\\033[48;2;255;190;000m.\\033[48;2;255;191;000m.", + "\\033[0m\\n ", + "\\033[48;2;255;192;000m.\\033[48;2;255;193;000m.\\033[48;2;255;194;000m.\\033[48;2;255;195;000m.\\033[48;2;255;196;000m.\\033[48;2;255;197;000m.\\033[48;2;255;198;000m.\\033[48;2;255;199;000m.", + "\\033[48;2;255;200;000m.\\033[48;2;255;201;000m.\\033[48;2;255;202;000m.\\033[48;2;255;203;000m.\\033[48;2;255;204;000m.\\033[48;2;255;205;000m.\\033[48;2;255;206;000m.\\033[48;2;255;207;000m.", + "\\033[48;2;255;208;000m.\\033[48;2;255;209;000m.\\033[48;2;255;210;000m.\\033[48;2;255;211;000m.\\033[48;2;255;212;000m.\\033[48;2;255;213;000m.\\033[48;2;255;214;000m.\\033[48;2;255;215;000m.", + "\\033[48;2;255;216;000m.\\033[48;2;255;217;000m.\\033[48;2;255;218;000m.\\033[48;2;255;219;000m.\\033[48;2;255;220;000m.\\033[48;2;255;221;000m.\\033[48;2;255;222;000m.\\033[48;2;255;223;000m.", + "\\033[48;2;255;224;000m.\\033[48;2;255;225;000m.\\033[48;2;255;226;000m.\\033[48;2;255;227;000m.\\033[48;2;255;228;000m.\\033[48;2;255;229;000m.\\033[48;2;255;230;000m.\\033[48;2;255;231;000m.", + "\\033[48;2;255;232;000m.\\033[48;2;255;233;000m.\\033[48;2;255;234;000m.\\033[48;2;255;235;000m.\\033[48;2;255;236;000m.\\033[48;2;255;237;000m.\\033[48;2;255;238;000m.\\033[48;2;255;239;000m.", + "\\033[48;2;255;240;000m.\\033[48;2;255;241;000m.\\033[48;2;255;242;000m.\\033[48;2;255;243;000m.\\033[48;2;255;244;000m.\\033[48;2;255;245;000m.\\033[48;2;255;246;000m.\\033[48;2;255;247;000m.", + "\\033[48;2;255;248;000m.\\033[48;2;255;249;000m.\\033[48;2;255;250;000m.\\033[48;2;255;251;000m.\\033[48;2;255;252;000m.\\033[48;2;255;253;000m.\\033[48;2;255;254;000m.\\033[48;2;255;255;000m.", + "\\033[48;2;255;255;000m.\\033[48;2;254;255;000m.\\033[48;2;253;255;000m.\\033[48;2;252;255;000m.\\033[48;2;251;255;000m.\\033[48;2;250;255;000m.\\033[48;2;249;255;000m.\\033[48;2;248;255;000m.", + "\\033[48;2;247;255;000m.\\033[48;2;246;255;000m.\\033[48;2;245;255;000m.\\033[48;2;244;255;000m.\\033[48;2;243;255;000m.\\033[48;2;242;255;000m.\\033[48;2;241;255;000m.\\033[48;2;240;255;000m.", + "\\033[48;2;239;255;000m.\\033[48;2;238;255;000m.\\033[48;2;237;255;000m.\\033[48;2;236;255;000m.\\033[48;2;235;255;000m.\\033[48;2;234;255;000m.\\033[48;2;233;255;000m.\\033[48;2;232;255;000m.", + "\\033[48;2;231;255;000m.\\033[48;2;230;255;000m.\\033[48;2;229;255;000m.\\033[48;2;228;255;000m.\\033[48;2;227;255;000m.\\033[48;2;226;255;000m.\\033[48;2;225;255;000m.\\033[48;2;224;255;000m.", + "\\033[0m\\n ", + "\\033[48;2;223;255;000m.\\033[48;2;222;255;000m.\\033[48;2;221;255;000m.\\033[48;2;220;255;000m.\\033[48;2;219;255;000m.\\033[48;2;218;255;000m.\\033[48;2;217;255;000m.\\033[48;2;216;255;000m.", + "\\033[48;2;215;255;000m.\\033[48;2;214;255;000m.\\033[48;2;213;255;000m.\\033[48;2;212;255;000m.\\033[48;2;211;255;000m.\\033[48;2;210;255;000m.\\033[48;2;209;255;000m.\\033[48;2;208;255;000m.", + "\\033[48;2;207;255;000m.\\033[48;2;206;255;000m.\\033[48;2;205;255;000m.\\033[48;2;204;255;000m.\\033[48;2;203;255;000m.\\033[48;2;202;255;000m.\\033[48;2;201;255;000m.\\033[48;2;200;255;000m.", + "\\033[48;2;199;255;000m.\\033[48;2;198;255;000m.\\033[48;2;197;255;000m.\\033[48;2;196;255;000m.\\033[48;2;195;255;000m.\\033[48;2;194;255;000m.\\033[48;2;193;255;000m.\\033[48;2;192;255;000m.", + "\\033[48;2;191;255;000m.\\033[48;2;190;255;000m.\\033[48;2;189;255;000m.\\033[48;2;188;255;000m.\\033[48;2;187;255;000m.\\033[48;2;186;255;000m.\\033[48;2;185;255;000m.\\033[48;2;184;255;000m.", + "\\033[48;2;183;255;000m.\\033[48;2;182;255;000m.\\033[48;2;181;255;000m.\\033[48;2;180;255;000m.\\033[48;2;179;255;000m.\\033[48;2;178;255;000m.\\033[48;2;177;255;000m.\\033[48;2;176;255;000m.", + "\\033[48;2;175;255;000m.\\033[48;2;174;255;000m.\\033[48;2;173;255;000m.\\033[48;2;172;255;000m.\\033[48;2;171;255;000m.\\033[48;2;170;255;000m.\\033[48;2;169;255;000m.\\033[48;2;168;255;000m.", + "\\033[48;2;167;255;000m.\\033[48;2;166;255;000m.\\033[48;2;165;255;000m.\\033[48;2;164;255;000m.\\033[48;2;163;255;000m.\\033[48;2;162;255;000m.\\033[48;2;161;255;000m.\\033[48;2;160;255;000m.", + "\\033[48;2;159;255;000m.\\033[48;2;158;255;000m.\\033[48;2;157;255;000m.\\033[48;2;156;255;000m.\\033[48;2;155;255;000m.\\033[48;2;154;255;000m.\\033[48;2;153;255;000m.\\033[48;2;152;255;000m.", + "\\033[48;2;151;255;000m.\\033[48;2;150;255;000m.\\033[48;2;149;255;000m.\\033[48;2;148;255;000m.\\033[48;2;147;255;000m.\\033[48;2;146;255;000m.\\033[48;2;145;255;000m.\\033[48;2;144;255;000m.", + "\\033[48;2;143;255;000m.\\033[48;2;142;255;000m.\\033[48;2;141;255;000m.\\033[48;2;140;255;000m.\\033[48;2;139;255;000m.\\033[48;2;138;255;000m.\\033[48;2;137;255;000m.\\033[48;2;136;255;000m.", + "\\033[48;2;135;255;000m.\\033[48;2;134;255;000m.\\033[48;2;133;255;000m.\\033[48;2;132;255;000m.\\033[48;2;131;255;000m.\\033[48;2;130;255;000m.\\033[48;2;129;255;000m.\\033[48;2;128;255;000m.", + "\\033[0m\\n ", + "\\033[48;2;127;255;000m.\\033[48;2;126;255;000m.\\033[48;2;125;255;000m.\\033[48;2;124;255;000m.\\033[48;2;123;255;000m.\\033[48;2;122;255;000m.\\033[48;2;121;255;000m.\\033[48;2;120;255;000m.", + "\\033[48;2;119;255;000m.\\033[48;2;118;255;000m.\\033[48;2;117;255;000m.\\033[48;2;116;255;000m.\\033[48;2;115;255;000m.\\033[48;2;114;255;000m.\\033[48;2;113;255;000m.\\033[48;2;112;255;000m.", + "\\033[48;2;111;255;000m.\\033[48;2;110;255;000m.\\033[48;2;109;255;000m.\\033[48;2;108;255;000m.\\033[48;2;107;255;000m.\\033[48;2;106;255;000m.\\033[48;2;105;255;000m.\\033[48;2;104;255;000m.", + "\\033[48;2;103;255;000m.\\033[48;2;102;255;000m.\\033[48;2;101;255;000m.\\033[48;2;100;255;000m.\\033[48;2;099;255;000m.\\033[48;2;098;255;000m.\\033[48;2;097;255;000m.\\033[48;2;096;255;000m.", + "\\033[48;2;095;255;000m.\\033[48;2;094;255;000m.\\033[48;2;093;255;000m.\\033[48;2;092;255;000m.\\033[48;2;091;255;000m.\\033[48;2;090;255;000m.\\033[48;2;089;255;000m.\\033[48;2;088;255;000m.", + "\\033[48;2;087;255;000m.\\033[48;2;086;255;000m.\\033[48;2;085;255;000m.\\033[48;2;084;255;000m.\\033[48;2;083;255;000m.\\033[48;2;082;255;000m.\\033[48;2;081;255;000m.\\033[48;2;080;255;000m.", + "\\033[48;2;079;255;000m.\\033[48;2;078;255;000m.\\033[48;2;077;255;000m.\\033[48;2;076;255;000m.\\033[48;2;075;255;000m.\\033[48;2;074;255;000m.\\033[48;2;073;255;000m.\\033[48;2;072;255;000m.", + "\\033[48;2;071;255;000m.\\033[48;2;070;255;000m.\\033[48;2;069;255;000m.\\033[48;2;068;255;000m.\\033[48;2;067;255;000m.\\033[48;2;066;255;000m.\\033[48;2;065;255;000m.\\033[48;2;064;255;000m.", + "\\033[48;2;063;255;000m.\\033[48;2;062;255;000m.\\033[48;2;061;255;000m.\\033[48;2;060;255;000m.\\033[48;2;059;255;000m.\\033[48;2;058;255;000m.\\033[48;2;057;255;000m.\\033[48;2;056;255;000m.", + "\\033[48;2;055;255;000m.\\033[48;2;054;255;000m.\\033[48;2;053;255;000m.\\033[48;2;052;255;000m.\\033[48;2;051;255;000m.\\033[48;2;050;255;000m.\\033[48;2;049;255;000m.\\033[48;2;048;255;000m.", + "\\033[48;2;047;255;000m.\\033[48;2;046;255;000m.\\033[48;2;045;255;000m.\\033[48;2;044;255;000m.\\033[48;2;043;255;000m.\\033[48;2;042;255;000m.\\033[48;2;041;255;000m.\\033[48;2;040;255;000m.", + "\\033[48;2;039;255;000m.\\033[48;2;038;255;000m.\\033[48;2;037;255;000m.\\033[48;2;036;255;000m.\\033[48;2;035;255;000m.\\033[48;2;034;255;000m.\\033[48;2;033;255;000m.\\033[48;2;032;255;000m.", + "\\033[0m\\n ", + "\\033[48;2;031;255;000m.\\033[48;2;030;255;000m.\\033[48;2;029;255;000m.\\033[48;2;028;255;000m.\\033[48;2;027;255;000m.\\033[48;2;026;255;000m.\\033[48;2;025;255;000m.\\033[48;2;024;255;000m.", + "\\033[48;2;023;255;000m.\\033[48;2;022;255;000m.\\033[48;2;021;255;000m.\\033[48;2;020;255;000m.\\033[48;2;019;255;000m.\\033[48;2;018;255;000m.\\033[48;2;017;255;000m.\\033[48;2;016;255;000m.", + "\\033[48;2;015;255;000m.\\033[48;2;014;255;000m.\\033[48;2;013;255;000m.\\033[48;2;012;255;000m.\\033[48;2;011;255;000m.\\033[48;2;010;255;000m.\\033[48;2;009;255;000m.\\033[48;2;008;255;000m.", + "\\033[48;2;007;255;000m.\\033[48;2;006;255;000m.\\033[48;2;005;255;000m.\\033[48;2;004;255;000m.\\033[48;2;003;255;000m.\\033[48;2;002;255;000m.\\033[48;2;001;255;000m.\\033[48;2;000;255;000m.", + "\\033[48;2;000;255;000m.\\033[48;2;000;255;001m.\\033[48;2;000;255;002m.\\033[48;2;000;255;003m.\\033[48;2;000;255;004m.\\033[48;2;000;255;005m.\\033[48;2;000;255;006m.\\033[48;2;000;255;007m.", + "\\033[48;2;000;255;008m.\\033[48;2;000;255;009m.\\033[48;2;000;255;010m.\\033[48;2;000;255;011m.\\033[48;2;000;255;012m.\\033[48;2;000;255;013m.\\033[48;2;000;255;014m.\\033[48;2;000;255;015m.", + "\\033[48;2;000;255;016m.\\033[48;2;000;255;017m.\\033[48;2;000;255;018m.\\033[48;2;000;255;019m.\\033[48;2;000;255;020m.\\033[48;2;000;255;021m.\\033[48;2;000;255;022m.\\033[48;2;000;255;023m.", + "\\033[48;2;000;255;024m.\\033[48;2;000;255;025m.\\033[48;2;000;255;026m.\\033[48;2;000;255;027m.\\033[48;2;000;255;028m.\\033[48;2;000;255;029m.\\033[48;2;000;255;030m.\\033[48;2;000;255;031m.", + "\\033[48;2;000;255;032m.\\033[48;2;000;255;033m.\\033[48;2;000;255;034m.\\033[48;2;000;255;035m.\\033[48;2;000;255;036m.\\033[48;2;000;255;037m.\\033[48;2;000;255;038m.\\033[48;2;000;255;039m.", + "\\033[48;2;000;255;040m.\\033[48;2;000;255;041m.\\033[48;2;000;255;042m.\\033[48;2;000;255;043m.\\033[48;2;000;255;044m.\\033[48;2;000;255;045m.\\033[48;2;000;255;046m.\\033[48;2;000;255;047m.", + "\\033[48;2;000;255;048m.\\033[48;2;000;255;049m.\\033[48;2;000;255;050m.\\033[48;2;000;255;051m.\\033[48;2;000;255;052m.\\033[48;2;000;255;053m.\\033[48;2;000;255;054m.\\033[48;2;000;255;055m.", + "\\033[48;2;000;255;056m.\\033[48;2;000;255;057m.\\033[48;2;000;255;058m.\\033[48;2;000;255;059m.\\033[48;2;000;255;060m.\\033[48;2;000;255;061m.\\033[48;2;000;255;062m.\\033[48;2;000;255;063m.", + "\\033[0m\\n ", + "\\033[48;2;000;255;064m.\\033[48;2;000;255;065m.\\033[48;2;000;255;066m.\\033[48;2;000;255;067m.\\033[48;2;000;255;068m.\\033[48;2;000;255;069m.\\033[48;2;000;255;070m.\\033[48;2;000;255;071m.", + "\\033[48;2;000;255;072m.\\033[48;2;000;255;073m.\\033[48;2;000;255;074m.\\033[48;2;000;255;075m.\\033[48;2;000;255;076m.\\033[48;2;000;255;077m.\\033[48;2;000;255;078m.\\033[48;2;000;255;079m.", + "\\033[48;2;000;255;080m.\\033[48;2;000;255;081m.\\033[48;2;000;255;082m.\\033[48;2;000;255;083m.\\033[48;2;000;255;084m.\\033[48;2;000;255;085m.\\033[48;2;000;255;086m.\\033[48;2;000;255;087m.", + "\\033[48;2;000;255;088m.\\033[48;2;000;255;089m.\\033[48;2;000;255;090m.\\033[48;2;000;255;091m.\\033[48;2;000;255;092m.\\033[48;2;000;255;093m.\\033[48;2;000;255;094m.\\033[48;2;000;255;095m.", + "\\033[48;2;000;255;096m.\\033[48;2;000;255;097m.\\033[48;2;000;255;098m.\\033[48;2;000;255;099m.\\033[48;2;000;255;100m.\\033[48;2;000;255;101m.\\033[48;2;000;255;102m.\\033[48;2;000;255;103m.", + "\\033[48;2;000;255;104m.\\033[48;2;000;255;105m.\\033[48;2;000;255;106m.\\033[48;2;000;255;107m.\\033[48;2;000;255;108m.\\033[48;2;000;255;109m.\\033[48;2;000;255;110m.\\033[48;2;000;255;111m.", + "\\033[48;2;000;255;112m.\\033[48;2;000;255;113m.\\033[48;2;000;255;114m.\\033[48;2;000;255;115m.\\033[48;2;000;255;116m.\\033[48;2;000;255;117m.\\033[48;2;000;255;118m.\\033[48;2;000;255;119m.", + "\\033[48;2;000;255;120m.\\033[48;2;000;255;121m.\\033[48;2;000;255;122m.\\033[48;2;000;255;123m.\\033[48;2;000;255;124m.\\033[48;2;000;255;125m.\\033[48;2;000;255;126m.\\033[48;2;000;255;127m.", + "\\033[48;2;000;255;128m.\\033[48;2;000;255;129m.\\033[48;2;000;255;130m.\\033[48;2;000;255;131m.\\033[48;2;000;255;132m.\\033[48;2;000;255;133m.\\033[48;2;000;255;134m.\\033[48;2;000;255;135m.", + "\\033[48;2;000;255;136m.\\033[48;2;000;255;137m.\\033[48;2;000;255;138m.\\033[48;2;000;255;139m.\\033[48;2;000;255;140m.\\033[48;2;000;255;141m.\\033[48;2;000;255;142m.\\033[48;2;000;255;143m.", + "\\033[48;2;000;255;144m.\\033[48;2;000;255;145m.\\033[48;2;000;255;146m.\\033[48;2;000;255;147m.\\033[48;2;000;255;148m.\\033[48;2;000;255;149m.\\033[48;2;000;255;150m.\\033[48;2;000;255;151m.", + "\\033[48;2;000;255;152m.\\033[48;2;000;255;153m.\\033[48;2;000;255;154m.\\033[48;2;000;255;155m.\\033[48;2;000;255;156m.\\033[48;2;000;255;157m.\\033[48;2;000;255;158m.\\033[48;2;000;255;159m.", + "\\033[0m\\n ", + "\\033[48;2;000;255;160m.\\033[48;2;000;255;161m.\\033[48;2;000;255;162m.\\033[48;2;000;255;163m.\\033[48;2;000;255;164m.\\033[48;2;000;255;165m.\\033[48;2;000;255;166m.\\033[48;2;000;255;167m.", + "\\033[48;2;000;255;168m.\\033[48;2;000;255;169m.\\033[48;2;000;255;170m.\\033[48;2;000;255;171m.\\033[48;2;000;255;172m.\\033[48;2;000;255;173m.\\033[48;2;000;255;174m.\\033[48;2;000;255;175m.", + "\\033[48;2;000;255;176m.\\033[48;2;000;255;177m.\\033[48;2;000;255;178m.\\033[48;2;000;255;179m.\\033[48;2;000;255;180m.\\033[48;2;000;255;181m.\\033[48;2;000;255;182m.\\033[48;2;000;255;183m.", + "\\033[48;2;000;255;184m.\\033[48;2;000;255;185m.\\033[48;2;000;255;186m.\\033[48;2;000;255;187m.\\033[48;2;000;255;188m.\\033[48;2;000;255;189m.\\033[48;2;000;255;190m.\\033[48;2;000;255;191m.", + "\\033[48;2;000;255;192m.\\033[48;2;000;255;193m.\\033[48;2;000;255;194m.\\033[48;2;000;255;195m.\\033[48;2;000;255;196m.\\033[48;2;000;255;197m.\\033[48;2;000;255;198m.\\033[48;2;000;255;199m.", + "\\033[48;2;000;255;200m.\\033[48;2;000;255;201m.\\033[48;2;000;255;202m.\\033[48;2;000;255;203m.\\033[48;2;000;255;204m.\\033[48;2;000;255;205m.\\033[48;2;000;255;206m.\\033[48;2;000;255;207m.", + "\\033[48;2;000;255;208m.\\033[48;2;000;255;209m.\\033[48;2;000;255;210m.\\033[48;2;000;255;211m.\\033[48;2;000;255;212m.\\033[48;2;000;255;213m.\\033[48;2;000;255;214m.\\033[48;2;000;255;215m.", + "\\033[48;2;000;255;216m.\\033[48;2;000;255;217m.\\033[48;2;000;255;218m.\\033[48;2;000;255;219m.\\033[48;2;000;255;220m.\\033[48;2;000;255;221m.\\033[48;2;000;255;222m.\\033[48;2;000;255;223m.", + "\\033[48;2;000;255;224m.\\033[48;2;000;255;225m.\\033[48;2;000;255;226m.\\033[48;2;000;255;227m.\\033[48;2;000;255;228m.\\033[48;2;000;255;229m.\\033[48;2;000;255;230m.\\033[48;2;000;255;231m.", + "\\033[48;2;000;255;232m.\\033[48;2;000;255;233m.\\033[48;2;000;255;234m.\\033[48;2;000;255;235m.\\033[48;2;000;255;236m.\\033[48;2;000;255;237m.\\033[48;2;000;255;238m.\\033[48;2;000;255;239m.", + "\\033[48;2;000;255;240m.\\033[48;2;000;255;241m.\\033[48;2;000;255;242m.\\033[48;2;000;255;243m.\\033[48;2;000;255;244m.\\033[48;2;000;255;245m.\\033[48;2;000;255;246m.\\033[48;2;000;255;247m.", + "\\033[48;2;000;255;248m.\\033[48;2;000;255;249m.\\033[48;2;000;255;250m.\\033[48;2;000;255;251m.\\033[48;2;000;255;252m.\\033[48;2;000;255;253m.\\033[48;2;000;255;254m.\\033[48;2;000;255;255m.", + "\\033[0m\\n ", + "\\033[48;2;000;255;255m.\\033[48;2;000;254;255m.\\033[48;2;000;253;255m.\\033[48;2;000;252;255m.\\033[48;2;000;251;255m.\\033[48;2;000;250;255m.\\033[48;2;000;249;255m.\\033[48;2;000;248;255m.", + "\\033[48;2;000;247;255m.\\033[48;2;000;246;255m.\\033[48;2;000;245;255m.\\033[48;2;000;244;255m.\\033[48;2;000;243;255m.\\033[48;2;000;242;255m.\\033[48;2;000;241;255m.\\033[48;2;000;240;255m.", + "\\033[48;2;000;239;255m.\\033[48;2;000;238;255m.\\033[48;2;000;237;255m.\\033[48;2;000;236;255m.\\033[48;2;000;235;255m.\\033[48;2;000;234;255m.\\033[48;2;000;233;255m.\\033[48;2;000;232;255m.", + "\\033[48;2;000;231;255m.\\033[48;2;000;230;255m.\\033[48;2;000;229;255m.\\033[48;2;000;228;255m.\\033[48;2;000;227;255m.\\033[48;2;000;226;255m.\\033[48;2;000;225;255m.\\033[48;2;000;224;255m.", + "\\033[48;2;000;223;255m.\\033[48;2;000;222;255m.\\033[48;2;000;221;255m.\\033[48;2;000;220;255m.\\033[48;2;000;219;255m.\\033[48;2;000;218;255m.\\033[48;2;000;217;255m.\\033[48;2;000;216;255m.", + "\\033[48;2;000;215;255m.\\033[48;2;000;214;255m.\\033[48;2;000;213;255m.\\033[48;2;000;212;255m.\\033[48;2;000;211;255m.\\033[48;2;000;210;255m.\\033[48;2;000;209;255m.\\033[48;2;000;208;255m.", + "\\033[48;2;000;207;255m.\\033[48;2;000;206;255m.\\033[48;2;000;205;255m.\\033[48;2;000;204;255m.\\033[48;2;000;203;255m.\\033[48;2;000;202;255m.\\033[48;2;000;201;255m.\\033[48;2;000;200;255m.", + "\\033[48;2;000;199;255m.\\033[48;2;000;198;255m.\\033[48;2;000;197;255m.\\033[48;2;000;196;255m.\\033[48;2;000;195;255m.\\033[48;2;000;194;255m.\\033[48;2;000;193;255m.\\033[48;2;000;192;255m.", + "\\033[48;2;000;191;255m.\\033[48;2;000;190;255m.\\033[48;2;000;189;255m.\\033[48;2;000;188;255m.\\033[48;2;000;187;255m.\\033[48;2;000;186;255m.\\033[48;2;000;185;255m.\\033[48;2;000;184;255m.", + "\\033[48;2;000;183;255m.\\033[48;2;000;182;255m.\\033[48;2;000;181;255m.\\033[48;2;000;180;255m.\\033[48;2;000;179;255m.\\033[48;2;000;178;255m.\\033[48;2;000;177;255m.\\033[48;2;000;176;255m.", + "\\033[48;2;000;175;255m.\\033[48;2;000;174;255m.\\033[48;2;000;173;255m.\\033[48;2;000;172;255m.\\033[48;2;000;171;255m.\\033[48;2;000;170;255m.\\033[48;2;000;169;255m.\\033[48;2;000;168;255m.", + "\\033[48;2;000;167;255m.\\033[48;2;000;166;255m.\\033[48;2;000;165;255m.\\033[48;2;000;164;255m.\\033[48;2;000;163;255m.\\033[48;2;000;162;255m.\\033[48;2;000;161;255m.\\033[48;2;000;160;255m.", + "\\033[0m\\n ", + "\\033[48;2;000;159;255m.\\033[48;2;000;158;255m.\\033[48;2;000;157;255m.\\033[48;2;000;156;255m.\\033[48;2;000;155;255m.\\033[48;2;000;154;255m.\\033[48;2;000;153;255m.\\033[48;2;000;152;255m.", + "\\033[48;2;000;151;255m.\\033[48;2;000;150;255m.\\033[48;2;000;149;255m.\\033[48;2;000;148;255m.\\033[48;2;000;147;255m.\\033[48;2;000;146;255m.\\033[48;2;000;145;255m.\\033[48;2;000;144;255m.", + "\\033[48;2;000;143;255m.\\033[48;2;000;142;255m.\\033[48;2;000;141;255m.\\033[48;2;000;140;255m.\\033[48;2;000;139;255m.\\033[48;2;000;138;255m.\\033[48;2;000;137;255m.\\033[48;2;000;136;255m.", + "\\033[48;2;000;135;255m.\\033[48;2;000;134;255m.\\033[48;2;000;133;255m.\\033[48;2;000;132;255m.\\033[48;2;000;131;255m.\\033[48;2;000;130;255m.\\033[48;2;000;129;255m.\\033[48;2;000;128;255m.", + "\\033[48;2;000;127;255m.\\033[48;2;000;126;255m.\\033[48;2;000;125;255m.\\033[48;2;000;124;255m.\\033[48;2;000;123;255m.\\033[48;2;000;122;255m.\\033[48;2;000;121;255m.\\033[48;2;000;120;255m.", + "\\033[48;2;000;119;255m.\\033[48;2;000;118;255m.\\033[48;2;000;117;255m.\\033[48;2;000;116;255m.\\033[48;2;000;115;255m.\\033[48;2;000;114;255m.\\033[48;2;000;113;255m.\\033[48;2;000;112;255m.", + "\\033[48;2;000;111;255m.\\033[48;2;000;110;255m.\\033[48;2;000;109;255m.\\033[48;2;000;108;255m.\\033[48;2;000;107;255m.\\033[48;2;000;106;255m.\\033[48;2;000;105;255m.\\033[48;2;000;104;255m.", + "\\033[48;2;000;103;255m.\\033[48;2;000;102;255m.\\033[48;2;000;101;255m.\\033[48;2;000;100;255m.\\033[48;2;000;099;255m.\\033[48;2;000;098;255m.\\033[48;2;000;097;255m.\\033[48;2;000;096;255m.", + "\\033[48;2;000;095;255m.\\033[48;2;000;094;255m.\\033[48;2;000;093;255m.\\033[48;2;000;092;255m.\\033[48;2;000;091;255m.\\033[48;2;000;090;255m.\\033[48;2;000;089;255m.\\033[48;2;000;088;255m.", + "\\033[48;2;000;087;255m.\\033[48;2;000;086;255m.\\033[48;2;000;085;255m.\\033[48;2;000;084;255m.\\033[48;2;000;083;255m.\\033[48;2;000;082;255m.\\033[48;2;000;081;255m.\\033[48;2;000;080;255m.", + "\\033[48;2;000;079;255m.\\033[48;2;000;078;255m.\\033[48;2;000;077;255m.\\033[48;2;000;076;255m.\\033[48;2;000;075;255m.\\033[48;2;000;074;255m.\\033[48;2;000;073;255m.\\033[48;2;000;072;255m.", + "\\033[48;2;000;071;255m.\\033[48;2;000;070;255m.\\033[48;2;000;069;255m.\\033[48;2;000;068;255m.\\033[48;2;000;067;255m.\\033[48;2;000;066;255m.\\033[48;2;000;065;255m.\\033[48;2;000;064;255m.", + "\\033[0m\\n ", + "\\033[48;2;000;063;255m.\\033[48;2;000;062;255m.\\033[48;2;000;061;255m.\\033[48;2;000;060;255m.\\033[48;2;000;059;255m.\\033[48;2;000;058;255m.\\033[48;2;000;057;255m.\\033[48;2;000;056;255m.", + "\\033[48;2;000;055;255m.\\033[48;2;000;054;255m.\\033[48;2;000;053;255m.\\033[48;2;000;052;255m.\\033[48;2;000;051;255m.\\033[48;2;000;050;255m.\\033[48;2;000;049;255m.\\033[48;2;000;048;255m.", + "\\033[48;2;000;047;255m.\\033[48;2;000;046;255m.\\033[48;2;000;045;255m.\\033[48;2;000;044;255m.\\033[48;2;000;043;255m.\\033[48;2;000;042;255m.\\033[48;2;000;041;255m.\\033[48;2;000;040;255m.", + "\\033[48;2;000;039;255m.\\033[48;2;000;038;255m.\\033[48;2;000;037;255m.\\033[48;2;000;036;255m.\\033[48;2;000;035;255m.\\033[48;2;000;034;255m.\\033[48;2;000;033;255m.\\033[48;2;000;032;255m.", + "\\033[48;2;000;031;255m.\\033[48;2;000;030;255m.\\033[48;2;000;029;255m.\\033[48;2;000;028;255m.\\033[48;2;000;027;255m.\\033[48;2;000;026;255m.\\033[48;2;000;025;255m.\\033[48;2;000;024;255m.", + "\\033[48;2;000;023;255m.\\033[48;2;000;022;255m.\\033[48;2;000;021;255m.\\033[48;2;000;020;255m.\\033[48;2;000;019;255m.\\033[48;2;000;018;255m.\\033[48;2;000;017;255m.\\033[48;2;000;016;255m.", + "\\033[48;2;000;015;255m.\\033[48;2;000;014;255m.\\033[48;2;000;013;255m.\\033[48;2;000;012;255m.\\033[48;2;000;011;255m.\\033[48;2;000;010;255m.\\033[48;2;000;009;255m.\\033[48;2;000;008;255m.", + "\\033[48;2;000;007;255m.\\033[48;2;000;006;255m.\\033[48;2;000;005;255m.\\033[48;2;000;004;255m.\\033[48;2;000;003;255m.\\033[48;2;000;002;255m.\\033[48;2;000;001;255m.\\033[48;2;000;000;255m.", + "\\033[48;2;000;000;255m.\\033[48;2;001;000;255m.\\033[48;2;002;000;255m.\\033[48;2;003;000;255m.\\033[48;2;004;000;255m.\\033[48;2;005;000;255m.\\033[48;2;006;000;255m.\\033[48;2;007;000;255m.", + "\\033[48;2;008;000;255m.\\033[48;2;009;000;255m.\\033[48;2;010;000;255m.\\033[48;2;011;000;255m.\\033[48;2;012;000;255m.\\033[48;2;013;000;255m.\\033[48;2;014;000;255m.\\033[48;2;015;000;255m.", + "\\033[48;2;016;000;255m.\\033[48;2;017;000;255m.\\033[48;2;018;000;255m.\\033[48;2;019;000;255m.\\033[48;2;020;000;255m.\\033[48;2;021;000;255m.\\033[48;2;022;000;255m.\\033[48;2;023;000;255m.", + "\\033[48;2;024;000;255m.\\033[48;2;025;000;255m.\\033[48;2;026;000;255m.\\033[48;2;027;000;255m.\\033[48;2;028;000;255m.\\033[48;2;029;000;255m.\\033[48;2;030;000;255m.\\033[48;2;031;000;255m.", + "\\033[0m\\n ", + "\\033[48;2;032;000;255m.\\033[48;2;033;000;255m.\\033[48;2;034;000;255m.\\033[48;2;035;000;255m.\\033[48;2;036;000;255m.\\033[48;2;037;000;255m.\\033[48;2;038;000;255m.\\033[48;2;039;000;255m.", + "\\033[48;2;040;000;255m.\\033[48;2;041;000;255m.\\033[48;2;042;000;255m.\\033[48;2;043;000;255m.\\033[48;2;044;000;255m.\\033[48;2;045;000;255m.\\033[48;2;046;000;255m.\\033[48;2;047;000;255m.", + "\\033[48;2;048;000;255m.\\033[48;2;049;000;255m.\\033[48;2;050;000;255m.\\033[48;2;051;000;255m.\\033[48;2;052;000;255m.\\033[48;2;053;000;255m.\\033[48;2;054;000;255m.\\033[48;2;055;000;255m.", + "\\033[48;2;056;000;255m.\\033[48;2;057;000;255m.\\033[48;2;058;000;255m.\\033[48;2;059;000;255m.\\033[48;2;060;000;255m.\\033[48;2;061;000;255m.\\033[48;2;062;000;255m.\\033[48;2;063;000;255m.", + "\\033[48;2;064;000;255m.\\033[48;2;065;000;255m.\\033[48;2;066;000;255m.\\033[48;2;067;000;255m.\\033[48;2;068;000;255m.\\033[48;2;069;000;255m.\\033[48;2;070;000;255m.\\033[48;2;071;000;255m.", + "\\033[48;2;072;000;255m.\\033[48;2;073;000;255m.\\033[48;2;074;000;255m.\\033[48;2;075;000;255m.\\033[48;2;076;000;255m.\\033[48;2;077;000;255m.\\033[48;2;078;000;255m.\\033[48;2;079;000;255m.", + "\\033[48;2;080;000;255m.\\033[48;2;081;000;255m.\\033[48;2;082;000;255m.\\033[48;2;083;000;255m.\\033[48;2;084;000;255m.\\033[48;2;085;000;255m.\\033[48;2;086;000;255m.\\033[48;2;087;000;255m.", + "\\033[48;2;088;000;255m.\\033[48;2;089;000;255m.\\033[48;2;090;000;255m.\\033[48;2;091;000;255m.\\033[48;2;092;000;255m.\\033[48;2;093;000;255m.\\033[48;2;094;000;255m.\\033[48;2;095;000;255m.", + "\\033[48;2;096;000;255m.\\033[48;2;097;000;255m.\\033[48;2;098;000;255m.\\033[48;2;099;000;255m.\\033[48;2;100;000;255m.\\033[48;2;101;000;255m.\\033[48;2;102;000;255m.\\033[48;2;103;000;255m.", + "\\033[48;2;104;000;255m.\\033[48;2;105;000;255m.\\033[48;2;106;000;255m.\\033[48;2;107;000;255m.\\033[48;2;108;000;255m.\\033[48;2;109;000;255m.\\033[48;2;110;000;255m.\\033[48;2;111;000;255m.", + "\\033[48;2;112;000;255m.\\033[48;2;113;000;255m.\\033[48;2;114;000;255m.\\033[48;2;115;000;255m.\\033[48;2;116;000;255m.\\033[48;2;117;000;255m.\\033[48;2;118;000;255m.\\033[48;2;119;000;255m.", + "\\033[48;2;120;000;255m.\\033[48;2;121;000;255m.\\033[48;2;122;000;255m.\\033[48;2;123;000;255m.\\033[48;2;124;000;255m.\\033[48;2;125;000;255m.\\033[48;2;126;000;255m.\\033[48;2;127;000;255m.", + "\\033[0m\\n ", + "\\033[48;2;128;000;255m.\\033[48;2;129;000;255m.\\033[48;2;130;000;255m.\\033[48;2;131;000;255m.\\033[48;2;132;000;255m.\\033[48;2;133;000;255m.\\033[48;2;134;000;255m.\\033[48;2;135;000;255m.", + "\\033[48;2;136;000;255m.\\033[48;2;137;000;255m.\\033[48;2;138;000;255m.\\033[48;2;139;000;255m.\\033[48;2;140;000;255m.\\033[48;2;141;000;255m.\\033[48;2;142;000;255m.\\033[48;2;143;000;255m.", + "\\033[48;2;144;000;255m.\\033[48;2;145;000;255m.\\033[48;2;146;000;255m.\\033[48;2;147;000;255m.\\033[48;2;148;000;255m.\\033[48;2;149;000;255m.\\033[48;2;150;000;255m.\\033[48;2;151;000;255m.", + "\\033[48;2;152;000;255m.\\033[48;2;153;000;255m.\\033[48;2;154;000;255m.\\033[48;2;155;000;255m.\\033[48;2;156;000;255m.\\033[48;2;157;000;255m.\\033[48;2;158;000;255m.\\033[48;2;159;000;255m.", + "\\033[48;2;160;000;255m.\\033[48;2;161;000;255m.\\033[48;2;162;000;255m.\\033[48;2;163;000;255m.\\033[48;2;164;000;255m.\\033[48;2;165;000;255m.\\033[48;2;166;000;255m.\\033[48;2;167;000;255m.", + "\\033[48;2;168;000;255m.\\033[48;2;169;000;255m.\\033[48;2;170;000;255m.\\033[48;2;171;000;255m.\\033[48;2;172;000;255m.\\033[48;2;173;000;255m.\\033[48;2;174;000;255m.\\033[48;2;175;000;255m.", + "\\033[48;2;176;000;255m.\\033[48;2;177;000;255m.\\033[48;2;178;000;255m.\\033[48;2;179;000;255m.\\033[48;2;180;000;255m.\\033[48;2;181;000;255m.\\033[48;2;182;000;255m.\\033[48;2;183;000;255m.", + "\\033[48;2;184;000;255m.\\033[48;2;185;000;255m.\\033[48;2;186;000;255m.\\033[48;2;187;000;255m.\\033[48;2;188;000;255m.\\033[48;2;189;000;255m.\\033[48;2;190;000;255m.\\033[48;2;191;000;255m.", + "\\033[48;2;192;000;255m.\\033[48;2;193;000;255m.\\033[48;2;194;000;255m.\\033[48;2;195;000;255m.\\033[48;2;196;000;255m.\\033[48;2;197;000;255m.\\033[48;2;198;000;255m.\\033[48;2;199;000;255m.", + "\\033[48;2;200;000;255m.\\033[48;2;201;000;255m.\\033[48;2;202;000;255m.\\033[48;2;203;000;255m.\\033[48;2;204;000;255m.\\033[48;2;205;000;255m.\\033[48;2;206;000;255m.\\033[48;2;207;000;255m.", + "\\033[48;2;208;000;255m.\\033[48;2;209;000;255m.\\033[48;2;210;000;255m.\\033[48;2;211;000;255m.\\033[48;2;212;000;255m.\\033[48;2;213;000;255m.\\033[48;2;214;000;255m.\\033[48;2;215;000;255m.", + "\\033[48;2;216;000;255m.\\033[48;2;217;000;255m.\\033[48;2;218;000;255m.\\033[48;2;219;000;255m.\\033[48;2;220;000;255m.\\033[48;2;221;000;255m.\\033[48;2;222;000;255m.\\033[48;2;223;000;255m.", + "\\033[0m\\n ", + "\\033[48;2;224;000;255m.\\033[48;2;225;000;255m.\\033[48;2;226;000;255m.\\033[48;2;227;000;255m.\\033[48;2;228;000;255m.\\033[48;2;229;000;255m.\\033[48;2;230;000;255m.\\033[48;2;231;000;255m.", + "\\033[48;2;232;000;255m.\\033[48;2;233;000;255m.\\033[48;2;234;000;255m.\\033[48;2;235;000;255m.\\033[48;2;236;000;255m.\\033[48;2;237;000;255m.\\033[48;2;238;000;255m.\\033[48;2;239;000;255m.", + "\\033[48;2;240;000;255m.\\033[48;2;241;000;255m.\\033[48;2;242;000;255m.\\033[48;2;243;000;255m.\\033[48;2;244;000;255m.\\033[48;2;245;000;255m.\\033[48;2;246;000;255m.\\033[48;2;247;000;255m.", + "\\033[48;2;248;000;255m.\\033[48;2;249;000;255m.\\033[48;2;250;000;255m.\\033[48;2;251;000;255m.\\033[48;2;252;000;255m.\\033[48;2;253;000;255m.\\033[48;2;254;000;255m.\\033[48;2;255;000;255m.", + "\\033[48;2;255;000;255m.\\033[48;2;255;000;254m.\\033[48;2;255;000;253m.\\033[48;2;255;000;252m.\\033[48;2;255;000;251m.\\033[48;2;255;000;250m.\\033[48;2;255;000;249m.\\033[48;2;255;000;248m.", + "\\033[48;2;255;000;247m.\\033[48;2;255;000;246m.\\033[48;2;255;000;245m.\\033[48;2;255;000;244m.\\033[48;2;255;000;243m.\\033[48;2;255;000;242m.\\033[48;2;255;000;241m.\\033[48;2;255;000;240m.", + "\\033[48;2;255;000;239m.\\033[48;2;255;000;238m.\\033[48;2;255;000;237m.\\033[48;2;255;000;236m.\\033[48;2;255;000;235m.\\033[48;2;255;000;234m.\\033[48;2;255;000;233m.\\033[48;2;255;000;232m.", + "\\033[48;2;255;000;231m.\\033[48;2;255;000;230m.\\033[48;2;255;000;229m.\\033[48;2;255;000;228m.\\033[48;2;255;000;227m.\\033[48;2;255;000;226m.\\033[48;2;255;000;225m.\\033[48;2;255;000;224m.", + "\\033[48;2;255;000;223m.\\033[48;2;255;000;222m.\\033[48;2;255;000;221m.\\033[48;2;255;000;220m.\\033[48;2;255;000;219m.\\033[48;2;255;000;218m.\\033[48;2;255;000;217m.\\033[48;2;255;000;216m.", + "\\033[48;2;255;000;215m.\\033[48;2;255;000;214m.\\033[48;2;255;000;213m.\\033[48;2;255;000;212m.\\033[48;2;255;000;211m.\\033[48;2;255;000;210m.\\033[48;2;255;000;209m.\\033[48;2;255;000;208m.", + "\\033[48;2;255;000;207m.\\033[48;2;255;000;206m.\\033[48;2;255;000;205m.\\033[48;2;255;000;204m.\\033[48;2;255;000;203m.\\033[48;2;255;000;202m.\\033[48;2;255;000;201m.\\033[48;2;255;000;200m.", + "\\033[48;2;255;000;199m.\\033[48;2;255;000;198m.\\033[48;2;255;000;197m.\\033[48;2;255;000;196m.\\033[48;2;255;000;195m.\\033[48;2;255;000;194m.\\033[48;2;255;000;193m.\\033[48;2;255;000;192m.", + "\\033[0m\\n ", + "\\033[48;2;255;000;191m.\\033[48;2;255;000;190m.\\033[48;2;255;000;189m.\\033[48;2;255;000;188m.\\033[48;2;255;000;187m.\\033[48;2;255;000;186m.\\033[48;2;255;000;185m.\\033[48;2;255;000;184m.", + "\\033[48;2;255;000;183m.\\033[48;2;255;000;182m.\\033[48;2;255;000;181m.\\033[48;2;255;000;180m.\\033[48;2;255;000;179m.\\033[48;2;255;000;178m.\\033[48;2;255;000;177m.\\033[48;2;255;000;176m.", + "\\033[48;2;255;000;175m.\\033[48;2;255;000;174m.\\033[48;2;255;000;173m.\\033[48;2;255;000;172m.\\033[48;2;255;000;171m.\\033[48;2;255;000;170m.\\033[48;2;255;000;169m.\\033[48;2;255;000;168m.", + "\\033[48;2;255;000;167m.\\033[48;2;255;000;166m.\\033[48;2;255;000;165m.\\033[48;2;255;000;164m.\\033[48;2;255;000;163m.\\033[48;2;255;000;162m.\\033[48;2;255;000;161m.\\033[48;2;255;000;160m.", + "\\033[48;2;255;000;159m.\\033[48;2;255;000;158m.\\033[48;2;255;000;157m.\\033[48;2;255;000;156m.\\033[48;2;255;000;155m.\\033[48;2;255;000;154m.\\033[48;2;255;000;153m.\\033[48;2;255;000;152m.", + "\\033[48;2;255;000;151m.\\033[48;2;255;000;150m.\\033[48;2;255;000;149m.\\033[48;2;255;000;148m.\\033[48;2;255;000;147m.\\033[48;2;255;000;146m.\\033[48;2;255;000;145m.\\033[48;2;255;000;144m.", + "\\033[48;2;255;000;143m.\\033[48;2;255;000;142m.\\033[48;2;255;000;141m.\\033[48;2;255;000;140m.\\033[48;2;255;000;139m.\\033[48;2;255;000;138m.\\033[48;2;255;000;137m.\\033[48;2;255;000;136m.", + "\\033[48;2;255;000;135m.\\033[48;2;255;000;134m.\\033[48;2;255;000;133m.\\033[48;2;255;000;132m.\\033[48;2;255;000;131m.\\033[48;2;255;000;130m.\\033[48;2;255;000;129m.\\033[48;2;255;000;128m.", + "\\033[48;2;255;000;127m.\\033[48;2;255;000;126m.\\033[48;2;255;000;125m.\\033[48;2;255;000;124m.\\033[48;2;255;000;123m.\\033[48;2;255;000;122m.\\033[48;2;255;000;121m.\\033[48;2;255;000;120m.", + "\\033[48;2;255;000;119m.\\033[48;2;255;000;118m.\\033[48;2;255;000;117m.\\033[48;2;255;000;116m.\\033[48;2;255;000;115m.\\033[48;2;255;000;114m.\\033[48;2;255;000;113m.\\033[48;2;255;000;112m.", + "\\033[48;2;255;000;111m.\\033[48;2;255;000;110m.\\033[48;2;255;000;109m.\\033[48;2;255;000;108m.\\033[48;2;255;000;107m.\\033[48;2;255;000;106m.\\033[48;2;255;000;105m.\\033[48;2;255;000;104m.", + "\\033[48;2;255;000;103m.\\033[48;2;255;000;102m.\\033[48;2;255;000;101m.\\033[48;2;255;000;100m.\\033[48;2;255;000;099m.\\033[48;2;255;000;098m.\\033[48;2;255;000;097m.\\033[48;2;255;000;096m.", + "\\033[0m\\n ", + "\\033[48;2;255;000;095m.\\033[48;2;255;000;094m.\\033[48;2;255;000;093m.\\033[48;2;255;000;092m.\\033[48;2;255;000;091m.\\033[48;2;255;000;090m.\\033[48;2;255;000;089m.\\033[48;2;255;000;088m.", + "\\033[48;2;255;000;087m.\\033[48;2;255;000;086m.\\033[48;2;255;000;085m.\\033[48;2;255;000;084m.\\033[48;2;255;000;083m.\\033[48;2;255;000;082m.\\033[48;2;255;000;081m.\\033[48;2;255;000;080m.", + "\\033[48;2;255;000;079m.\\033[48;2;255;000;078m.\\033[48;2;255;000;077m.\\033[48;2;255;000;076m.\\033[48;2;255;000;075m.\\033[48;2;255;000;074m.\\033[48;2;255;000;073m.\\033[48;2;255;000;072m.", + "\\033[48;2;255;000;071m.\\033[48;2;255;000;070m.\\033[48;2;255;000;069m.\\033[48;2;255;000;068m.\\033[48;2;255;000;067m.\\033[48;2;255;000;066m.\\033[48;2;255;000;065m.\\033[48;2;255;000;064m.", + "\\033[48;2;255;000;063m.\\033[48;2;255;000;062m.\\033[48;2;255;000;061m.\\033[48;2;255;000;060m.\\033[48;2;255;000;059m.\\033[48;2;255;000;058m.\\033[48;2;255;000;057m.\\033[48;2;255;000;056m.", + "\\033[48;2;255;000;055m.\\033[48;2;255;000;054m.\\033[48;2;255;000;053m.\\033[48;2;255;000;052m.\\033[48;2;255;000;051m.\\033[48;2;255;000;050m.\\033[48;2;255;000;049m.\\033[48;2;255;000;048m.", + "\\033[48;2;255;000;047m.\\033[48;2;255;000;046m.\\033[48;2;255;000;045m.\\033[48;2;255;000;044m.\\033[48;2;255;000;043m.\\033[48;2;255;000;042m.\\033[48;2;255;000;041m.\\033[48;2;255;000;040m.", + "\\033[48;2;255;000;039m.\\033[48;2;255;000;038m.\\033[48;2;255;000;037m.\\033[48;2;255;000;036m.\\033[48;2;255;000;035m.\\033[48;2;255;000;034m.\\033[48;2;255;000;033m.\\033[48;2;255;000;032m.", + "\\033[48;2;255;000;031m.\\033[48;2;255;000;030m.\\033[48;2;255;000;029m.\\033[48;2;255;000;028m.\\033[48;2;255;000;027m.\\033[48;2;255;000;026m.\\033[48;2;255;000;025m.\\033[48;2;255;000;024m.", + "\\033[48;2;255;000;023m.\\033[48;2;255;000;022m.\\033[48;2;255;000;021m.\\033[48;2;255;000;020m.\\033[48;2;255;000;019m.\\033[48;2;255;000;018m.\\033[48;2;255;000;017m.\\033[48;2;255;000;016m.", + "\\033[48;2;255;000;015m.\\033[48;2;255;000;014m.\\033[48;2;255;000;013m.\\033[48;2;255;000;012m.\\033[48;2;255;000;011m.\\033[48;2;255;000;010m.\\033[48;2;255;000;009m.\\033[48;2;255;000;008m.", + "\\033[48;2;255;000;007m.\\033[48;2;255;000;006m.\\033[48;2;255;000;005m.\\033[48;2;255;000;004m.\\033[48;2;255;000;003m.\\033[48;2;255;000;002m.\\033[48;2;255;000;001m.\\033[48;2;255;000;000m.", + + "\\033[0m\\n", + "\\n", + "---> Hit 'e' for next test: ", + "\\033[2J\\033[H" "ESC TEST 0023: FG/BG Specific Reset\\n", + "\\n", + "Test fg reset: \\033[31mThis is red on norm bg\\033[39m, This is ESC[39m reset (norm fg on norm bg).\\033[m\\n", + "Test bg reset: \\033[41mThis is norm fg on red bg\\033[49m, This is ESC[49m reset (norm fg on norm bg).\\033[m\\n", + "\\n", + "Test fg/bg with fg reset: \\033[32;44mThis is grn on blu\\033[39m, This is ESC[39m reset (norm fg on blu).\\033[m\\n", + "Test fg/bg with bg reset: \\033[32;44mThis is grn on blu\\033[49m, This is ESC[49m reset (grn on norm bg).\\033[m\\n", + "\\n", + "---> Hit 'e' for next test: ", + NULL +}; +if (reset) { index = 0; return 0; } +return show_test(test, index);} {} + } + Function {test_esc_fgbg_colors(bool reset)} { + comment {--- 0030: Test FG/BG Colors} return_type {static int} + } { + code {static int index = 0; +const char *test[] = { + // Screen 30 + "\\033[2J\\033[H" "ESC TEST 0030: FG/BG Colors\\n", + "\\n", + " Foreground Colors:\\n", + " \\033[2;31m" " Dim Red text \\033[0m \\033[0;32m" " Dim Grn text \\033[0m \\033[2;33m" " Dim Yel text \\033[0m\\n", + " \\033[2;34m" " Dim Blu text \\033[0m \\033[0;35m" " Dim Mag text \\033[0m \\033[2;36m" " Dim Cyn text \\033[0m" + " \\033[2;37m" " Dim Wht text \\033[0m\\n", + " \\033[0;31m" " Nor Red text \\033[0m \\033[0;32m" " Nor Grn text \\033[0m \\033[2;33m" " Nor Yel text \\033[0m\\n", + " \\033[0;34m" " Nor Blu text \\033[0m \\033[0;35m" " Nor Mag text \\033[0m \\033[0;36m" " Nor Cyn text \\033[0m" + " \\033[0;37m" " Nor Wht text \\033[0m\\n", + " \\033[1;31m" " Bold Red text \\033[0m \\033[1;32m" " Bold Grn text \\033[0m \\033[1;33m" " Bold Yel text \\033[0m\\n", + " \\033[1;34m" " Bold Blu text \\033[0m \\033[1;35m" " Bold Mag \\033[0m \\033[1;36m" " Bold Cyn text \\033[0m" + " \\033[1;37m" " Bold Wht text \\033[0m\\n", + "\\n", + " Background Colors:\\n", + " \\033[2;37;41m"" Dim Red bg \\033[0m \\033[2;37;42m"" Dim Grn bg \\033[0m \\033[2;37;43m"" Dim Yel bg \\033[0m\\n", + " \\033[2;37;44m"" Dim Blu bg \\033[0m \\033[2;37;45m"" Dim Mag bg \\033[0m \\033[2;37;46m"" Dim Cyn bg \\033[0m" + " \\033[2;37;47m"" Dim Wht bg \\033[0m\\n", + " \\033[0;37;41m"" Nor Red bg \\033[0m \\033[0;37;42m"" Nor Grn bg \\033[0m \\033[0;37;43m"" Nor Yel bg \\033[0m\\n", + " \\033[0;37;44m"" Nor Blu bg \\033[0m \\033[0;37;45m"" Nor Mag bg \\033[0m \\033[0;37;46m"" Nor Cyn bg \\033[0m" + " \\033[0;37;47m"" Nor Wht bg \\033[0m\\n", + " \\033[1;37;41m"" Bold Red bg \\033[0m \\033[1;37;42m"" Bold Grn bg \\033[0m \\033[1;37;43m"" Bold Yel bg \\033[0m\\n", + " \\033[1;37;44m"" Bold Blu bg \\033[0m \\033[1;37;45m"" Bold Mag bg \\033[0m \\033[1;37;46m"" Bold Cyn bg \\033[0m" + " \\033[1;37;47m"" Bold Wht bg \\033[0m\\n", + "\\n", + "---> Hit 'e' for next test: ", + NULL +}; +if (reset) { index = 0; return 0; } +return show_test(test, index);} {} + } + Function {test_esc_curpos(bool reset)} { + comment {--- 0040: Test Cursor Positioning} return_type {static int} + } { + code {static int index = 0; +const char *test[] = { + // Screen 40 + "\\033[2J\\033[H" "ESC TEST 0040: Cursor Positioning\\n", + "2:\\n", + "3: 10 15 20 25 30 35 40 45 50 55 60 65 70\\n", + "4: |----:----|----:----|----:----|----:----|----:----|----:----|\\n", + "5:\\n", + "6:\\n", + "7:\\n", + "\\033[5;10H" "X", "\\033[5;15H" "X", "\\033[5;20H" "X", + "\\033[5;40H" "X", "\\033[5;45H" "X", "\\033[5;50H" "X", + "\\033[6;10H" "|", "\\033[6;15H" "|", "\\033[6;20H" "|", + "\\033[6;40H" "|", "\\033[6;45H" "|", "\\033[6;50H" "|", + "\\033[7;10H" "10,5", "\\033[7;15H" "15,5", "\\033[7;20H" "20,5", + "\\033[7;40H" "40,5", "\\033[7;45H" "45,5", "\\033[7;50H" "50,5", + "\\033[8H\\n", + "---> Hit 'e' for next test: ", + NULL +}; +if (reset) { index = 0; return 0; } +return show_test(test, index);} {} + } + Function {test_esc_tabstops(bool reset)} { + comment {--- 0050: Tabstops: Default Tabstops} return_type {static int} + } { + code {static int index = 0; +const char *test[] = { + // Screen 50 + // We use <ESC>c to reset the terminal to default tabstops. + "\\033c" "ESC TEST 0050: Tabs: Default Tabstops\\n", + " A B C D E F G H I\\n", + " | | | | | | | | |\\n", + "\\tA\\tB\\tC\\tD-\\tE--\\tF-----\\tG------\\tH-------I\\n", + "\\t\\tB\\t\\tD\\t\\tF\\t\\tH-------I\\n", + "---> Hit 'e' for next test: ", + // We use <ESC>c to reset the terminal to default tabstops. + // Should also clear screen and home cursor.. + "\\033c" "ESC TEST 0051: Tabs: Change Tabstops\\n", + "\\n", + " 8 16 24 32 40 48 56 64 72\\n", + "--------|-------|-------|-------|-------|-------|-------|-------|-------|\\n", + "\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\n", + "\\n", + "Setting new tabstops..\\033[3g\\n", + ".\\033HT..\\033HT...\\033HT....\\033HT.....\\033HT......\\033HT.......\\033HT........\\033HT.........\\033HT\\n", + "-|--|---|----|-----|------|-------|--------|---------|\\n", + "\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\n", + "---> Hit 'e' for next test: ", + "\\033c" "ESC TEST 0052: Tabs: Default Tabstops Again\\n", + "\\n", + "Back to normal tabstops:\\n", + " 8 16 24 32 40 48 56 64 72\\n", + "--------|-------|-------|-------|-------|-------|-------|-------|-------|\\n", + "\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\n", + "\\n", + "---> Hit 'e' for next test: ", + NULL +}; +if (reset) { index = 0; return 0; } +return show_test(test, index);} {} + } + Function {test_esc_insert_char(bool reset)} { + comment {--- 0060: Insert Char} return_type {static int} + } { + code {static int index = 0; +const char *test[] = { + // Screen 60 + "\\033[2J\\033[HESC TEST 0060: Insert Char (ESC[@)\\n", + "\\n", + " 10> 20> 30> 40> 50> 60> 70> 80>\\n", + "4:34567890123456789012345678901234567890123456789012345678901234567890123456789\\n", + "5:34567890123456789012345678901234567890123456789012345678901234567890123456789\\n", + "6:34567890123456789012345678901234567890123456789012345678901234567890123456789\\n", + "\\033[s\\033[5;29H\\033[1;31m>\\033[0m\\033[u", // save curs, movcur, bold/red, '>', norm, recall curs + "\\n", + "---> Hit 'e' to test INSERT 1 char at row/col 5/30 (right of '>'): \\033[K", + "\\033[5;30H\\033[@\\033[8H", + "---> Hit 'e' to test INSERT 1 char at row/col 5/30 (right of '>'): \\033[K", + "\\033[5;30H\\033[@\\033[8H", + "---> Hit 'e' to test INSERT 4 chars at row/col 5/30 (right of '>'): \\033[K", + "\\033[5;30H\\033[4@\\033[8H", + "---> Hit 'e' to test INSERT 4 chars at row/col 5/30 (right of '>'): \\033[K", + "\\033[5;30H\\033[4@\\033[8H", + "---> Hit 'e' to test DELETE 1 chars at row/col 5/20 (right of '>'): \\033[K", + "\\033[5;30H\\033[P\\033[8H", + "---> Hit 'e' to test DELETE 1 chars at row/col 5/20 (right of '>'): \\033[K", + "\\033[5;30H\\033[P\\033[8H", + "---> Hit 'e' to test DELETE 2 chars at row/col 5/20 (right of '>'): \\033[K", + "\\033[5;30H\\033[2P\\033[8H", + "---> Hit 'e' to test DELETE 4 chars at row/col 5/20 (right of '>'): \\033[K", + "\\033[5;30H\\033[4P\\033[8H", + "---> Hit 'e' to test DELETE 1 char at row/col 5/20 (right of '>'): \\033[K", + "\\033[5;30H\\033[P\\033[8H", + "---> Hit 'e' to test DELETE 1 char at row/col 5/20 (right of '>'): \\033[K", + "\\033[5;30H\\033[P\\033[8H", + "---> Hit 'e' to test DELETE 1 *EXTRA* char at row/col 5/20 (right of '>'): \\033[K", + "\\033[5;30H\\033[P\\033[8H", + "---> Hit 'e' for next test: \\033[K", + NULL +}; +if (reset) { index = 0; return 0; } +return show_test(test, index);} {} + } + Function {test_esc_clear_test(bool reset)} { + comment {--- 0070: Clear Test ESC[K / ESC[K} return_type {static int} + } { + code {static int index = 0; +const char *test[] = { + // Screen 70 + "\\033[2J\\033[H" "ESC TEST 0070: Clear Test (ESC[K / ESC[J)\\n", + "2: 10> 20> 30> 40> 50> 60> 70> 80>\\n", + "3:34567890123456789012345678901234567890123456789012345678901234567890123456789\\n", + "4:3456789012345678901234 ERASE EOL->XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\\n", + "5:YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY<-ERASE SOL 012345678901234567890123456789\\n", + "6:34567890123456789012345678901234567890123456789012345678901234567890123456789\\n", + "7:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\\n", + "8:34567890123456789012345678901234567890123456789012345678901234567890123456789\\n", + "9:3456789012345678901234 ERASE EOD->ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\\n", + "0:ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\\n", + "1:ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\\n", + "2:ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\\n", + "3:ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\\n", + "4:ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\\n", + "5:ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\\n", + "6:ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\\n", + "7:ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\\n", + "\\n", + "---> Hit 'e' to test ERASE EOL (should make X's disappear): ", + "\\033[4;37H\\033[K\\n", // ERASE EOL + "\\033[19H", + "---> Hit 'e' to test ERASE SOL (should make Y's disappear): ", + "\\033[5;37H\\033[1K\\n", // ERASE SOL + "\\033[19H", + "---> Hit 'e' to test ERASE LINE (should make F's disappear): ", + "\\033[7;37H\\033[2K\\n", // ERASE LINE + "\\033[19H", + "---> Hit 'e' to test ERASE EOD (should make Z's disappear): ", + "\\033[9;37H\\033[0J\\n", // ERASE EOD + "\\033[19H", + "---> Hit 'e' for next test: ", + // Screen 71 + "\\033[2J\\033[H" "ESC TEST 0071: Clear Test (ESC[K / ESC[J)\\n", + "2: | | | | | |\\n", + "3:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\\n", + "4:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\\n", + "5:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\\n", + "6:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<-- ERASE SOD 567890123456789\\n", + "7:3456789012345678901234567890123456789012345678901234567890123456789\\n", + "8:3456789012345678901234567890123456789012345678901234567890123456789\\n", + "9:3456789012345678901234567890123456789012345678901234567890123456789\\n", + "\\033[19H", + "---> Hit 'e' to test ERASE SOD (should make X's disappear): ", + "\\033[6;40H\\033[1J\\n", // ERASE SOD + "\\033[19H", + "---> Hit 'e' for next test: \\033[K", + NULL +}; +if (reset) { index = 0; return 0; } +return show_test(test, index);} {} + } + Function {test_esc_delete_row(bool reset)} { + comment {--- 0080: Delete Row - ESC[M} return_type {static int} + } { + code {static int index = 0; +const char *test[] = { + // Screen 80 + "\\033[2J\\033[H" "ESC TEST 0080: Delete Row (ESC[M)\\n", + "02:456789012345678901234567890123456789012345678901234567890123456789\\n", + "03:456789012345678901234567890123456789012345678901234567890123456789\\n", + "04:456789012345678901234567890123456789012345678901234567890123456789\\n", + "05:456789012345678901234567890123456789012345678901234567890123456789\\n", + "06:456789012345678901234567890123456789012345678901234567890123456789\\n", + "07:456789012345678901234567890123456789012345678901234567890123456789\\n", + "08:456789012345678901234567890123456789012345678901234567890123456789\\n", + "09:456789012345678901234567890123456789012345678901234567890123456789\\n", + "10:456789012345678901234567890123456789012345678901234567890123456789\\n", + "\\n", + "\\n", + "\\n", + "\\n", + "\\n", + "\\n", + "\\n", + "\\n", + "\\n", + "\\n", + "\\n", + "\\n", + "\\n", + "24:456789012345678901234567890123456789012345678901234567890123456789\\n", + "25:456789012345678901234567890123456789012345678901234567890123456789", + "\\033[12H", // to prompt row + "---> Hit 'e' to test [1/4] DELETE LINE 3 with ESC[M: \\033[K", + "\\033[2K", // clear prompt + "\\033[3H", // move to line 3 + "\\033[M", // delete line 3 + "\\033[12H", // back to line 12H for prompt + //"\\033[2K\\033[3H\\033[M", // clear prompt, to row 3, delete row + //"\\033[12H", // to prompt row + "---> Hit 'e' to test [2/4] DELETE 3 MORE LINES with ESC[3M: \\033[K", + "\\033[2K\\033[3H\\033[3M", // clear prompt, to row 3, delete 3 rows + "\\033[12H", // to prompt row + "---> Hit 'e' to test [3/4] DELETE 3 MORE LINES with ESC[3M: \\033[K", + "\\033[2K\\033[3H\\033[3M", // clear prompt, to row 3, delete 3 rows + "\\033[12H", // to prompt row + "---> Hit 'e' to test [4/4] DELETE 3 MORE LINES with ESC[3M: \\033[K", + "\\033[2K\\033[3H\\033[3M", // clear prompt, to row 3, delete 3 rows + "\\033[12H", // to prompt row + "---> Hit 'e' for next test: ", + NULL +}; +if (reset) { index = 0; return 0; } +return show_test(test, index);} {} + } + Function {test_esc_insert_row(bool reset)} { + comment {--- 0085: Insert Row - ESC[L} return_type {static int} + } { + code {static int index = 0; +const char *test[] = { + // Screen 71 + "\\033[2J\\033[H" "ESC TEST 0085: Insert Row (ESC[L)\\n", + "2:3456789012345678901234567890123456789012345678901234567890123456789\\n", + "3:3456789012345678901234567890123456789012345678901234567890123456789\\n", + "4:3456789012345678901234567890123456789012345678901234567890123456789\\n", + "\\033[24H", + "24:456789012345678901234567890123456789012345678901234567890123456789\\n", + "25:456789012345678901234567890123456789012345678901234567890123456789", + "\\033[12H", + "---> Hit 'e' to test [1/3] INSERT 1 ROW AT LINE 3 with ESC[L: \\033[K", + "\\033[2K\\033[3H\\033[L", // clear prompt, to row 3, insert row + "\\033[12H", // to prompt row 17 + "---> Hit 'e' to test [2/3] INSERT 3 ROWS AT LINE 3 with ESC[3L: \\033[K", + "\\033[2K\\033[3H\\033[3L", // clear prompt, to row 3, insert 3 rows + "\\033[12H", // to prompt row 17 + "---> Hit 'e' for next test: ", + NULL +}; +if (reset) { index = 0; return 0; } +return show_test(test, index);} {} + } + Function {test_esc_scrolling(bool reset)} { + comment {--- 0090: Scrolling Up/Down - <ESC>[S / <ESC>M} return_type {static int} + } { + code {static int index = 0; +const char *test[] = { + // Screen 90 + "\\033[2J\\033[H" "ESC TEST 0090: Scrolling up/down\\n", + "Line 2 🡅\\n", + "Line 3 [A]\\n", + "Line 4 🡅\\n", + "Line 5 [B]\\n", + "Line 6\\n", + "Line 7\\n", + "Line 8\\n", + "Line 9\\n", + "Line 10\\n", + "Line 11\\n", + "Line 12\\n", + // Do this weird thing so the lines still appear in order in our code + "\\033[200H\\033[10ABottom Line -10", + "\\033[200H\\033[9ABottom Line -9", + "\\033[200H\\033[8ABottom Line -8", + "\\033[200H\\033[7ABottom Line -7", + "\\033[200H\\033[6ABottom Line -6", + "\\033[200H\\033[5ABottom Line -5 [D]", + "\\033[200H\\033[4ABottom Line -4 🡇", + "\\033[200H\\033[3ABottom Line -3", + "\\033[200H\\033[2ABottom Line -2 [C]", + "\\033[200H\\033[1ABottom Line -1 🡇", + "\\033[200H" "Bottom Line 0", + "\\033[15H", // Line 15 + "---> Hit 'e' to SCROLL UP 1 with <ESC>[S so top line at position [A]: \\033[K", + "\\033[H\\033[S\\033[14H", // Line 14 because up one line + "---> Hit 'e' to SCROLL UP 2 with <ESC>[2S so top line at position [B]: \\033[K", + "\\033[H\\033[2S\\033[12H", // Line 12 because up two more lines + "---> Hit 'e' to SCROLL DOWN 1 with <ESC>M at screen top: \\033[K", + "\\033[H\\033M\\033[13H", // Line 13 because down 1 line + "---> Hit 'e' to SCROLL DOWN 2 with 2x<ESC>M: \\033[K", + "\\033[H\\033M\\033M\\033[15H", // Line 15 because down 2 lines + "---> Hit 'e' to SCROLL DOWN 1 with <ESC>M for bottom line at posn [C]: \\033[K", + "\\033[H\\033M\\033[16H", // Line 16 because down 1 lines + "---> Hit 'e' to SCROLL DOWN 3 with 3x<ESC>M for bottom line at posn [D]: \\033[K", + "\\033[H\\033M\\033M\\033M\\033[19H", // Line 18 because down 3 lines + //TODO Other ESC commands to scroll up/down? + "---> Hit 'e' for next test: \\033[K", + // Screen 100 + "\\033[2J\\033[HESC TEST 0091: Cursor Save/Restore Test\\n", + "\\n", + " Cursor positon at 'A' was saved with ESC[s.\\n", + "\\033[12;39H>\\033[1;31m\\033[s""A""\\033[0m<\\033[20H", + "---> Hit 'e' to recall cursor position and change 'A' to 'B': \\033[K", + "\\033[u\\033[1;32m""B\\033[0m\\033[20H", + "---> Hit 'e' for next test: \\033[K", + NULL +}; +if (reset) { index = 0; return 0; } +return show_test(test, index);} {} + } + decl {////// OTHER SELF TESTS //////} {private local + } + Function {show_file(const char *filename)} { + comment {Show the specified file in the terminal +} return_type void + } { + code {// If cursor floating, do a crlf first +if (G_tty->cursor_col()!=0) G_tty->append("\\n"); + +char s[1024]; +FILE *fp = fopen(filename, "r"); +if ( fp == NULL ) { + snprintf(s, sizeof(s), "\\033[1;31m%s: %s\\033[0m\\n", filename, strerror(errno)); + s[1023] = 0; + G_tty->append_utf8(s); + return; +} +while ( fgets(s, sizeof(s)-1, fp) ) { + s[1023] = 0; + G_tty->append(s); // append line to display +} +// Last line in file? Done +fclose(fp); +fp = NULL;} {} + } + Function {speed_test()} { + comment {Run full screen random character update} return_type void + } { + code {// 50 iters of WxH random chars (no scrolling) + +// First, switch redraw style to none, so we can handle redraws() +G_tty->redraw_style(Fl_Terminal::NO_REDRAW); + +// Clear screen, do 50 iterations of random chars +G_tty->append("\\033[H"); // home +int rows = G_tty->display_rows(); +int cols = G_tty->display_columns(); +for (int i=0; i<50; i++ ) { + for ( int row=0; row<rows; row++ ) { + for ( int col=0; col<cols; col++ ) { + char c = ' ' + (rand() % 0x50); // random ASCII characters + G_tty->textfgcolor_xterm(rand() % 8); // random fg uchar color for each char + G_tty->putchar(c, row, col); + } + } + G_tty->redraw(); + Fl::wait(0.005); +} +// leave test with white on black +G_tty->textfgcolor(0xd0d0d000); +G_tty->textbgcolor(0xffffffff); // special see thru color +// back to rate limited +G_tty->redraw_style(Fl_Terminal::RATE_LIMITED); +// Show the Unicode alignment page +test_firstpage(false);} {} + } + Function {show_colorbars()} { + comment {Show colorbars in terminal +} return_type void + } { + code {// If cursor floating, do a crlf first +if (G_tty->cursor_col()!=0) G_tty->append("\\n"); + +Fl_Color fgsave = G_tty->textfgcolor(); +Fl_Color bgsave = G_tty->textbgcolor(); +// ECR-1-1978 +// grey yellow cyan green magenta red blue +Fl_Color bars1[] = { 0xc0c0c000, 0xbfc03700, 0x2ac0bf00, 0x25c03300, 0xbe00bb00, 0xbd051800, 0x1600ba00 }; +Fl_Color bars2[] = { 0x1600ba00, 0x11111100, 0xbe00bb00, 0x11111100, 0x2ac0bf00, 0x11111100, 0xc0c0c000 }; +Fl_Color boxes[] = { 0x05214a00, 0xffffff00, 0x32006700, 0x11111100, 0x03030300, 0x11111100, 0x1d1d1d00, 0x11111100 }; +// -I 100% wht Q+ superblack black 4% black +// \\________________________________/ +// pluge +const char *bar = "███████████"; +const char *box1= "██████████████"; +const char *box2= "█████████████"; +const char *pluge = "███"; +G_tty->append("\\033[2J\\033[H"); +// Print 75% bars: bars1[] +{ + for ( int y=0; y<16; y++ ) { + for ( int i=0; i<7; i++ ) { + G_tty->textfgcolor(bars1[i]); + G_tty->append(bar); + } + G_tty->append("\\n"); + } +} +// Print strip: bars2[] +{ + for ( int y=0; y<2; y++ ) { + for ( int i=0; i<7; i++ ) { + G_tty->textfgcolor(bars2[i]); + G_tty->append(bar); + } + G_tty->append("\\n"); + } +} +// Bottom boxes +{ + for ( int y=0; y<7; y++ ) { + for ( int i=0; i<8; i++ ) { + G_tty->textfgcolor(boxes[i]); + if ( i < 3 ) G_tty->append(box1); + else if ( i < 4 ) G_tty->append(box2); + else if ( i < 7 ) G_tty->append(pluge); + else G_tty->append(box2); + } + if ( y < 6 ) G_tty->append("\\n"); + } +} +// Restore fg/bg colors +G_tty->textfgcolor(fgsave ); +G_tty->textbgcolor(bgsave );} {} + } + Function {unicode_alignment()} { + comment {Show unicode alignment test +} return_type void + } { + code {test_firstpage(true);} {} + } + Function {add_lines(int count)} { + comment {Add specified number of test lines to tty} return_type void + } { + code {// If cursor floating, do a crlf first +if (G_tty->cursor_col()!=0) G_tty->append("\\n"); +for ( int t=0; t<count; t++ ) + G_tty->printf("Line %04d -- AAA BBB CCC DDD\\n", G_lines++);} {} + } + Function {do_command(const char *cmd)} { + comment {Run the command, output appends to terminal +} return_type void + } { + code {// Run command in pipe, return output to tty +// TODO: This should probably be reimplemented as a thread. +// Also, I doubt this will work well on Windows. +// If it's a real problem, open a file and 'tail' it ourselves instead. +// +\#ifdef _WIN32 +\#define POPEN(a,b) _popen(a,b) // effin windows +\#define PCLOSE(a) _pclose(a) +\#define READ(a,b,c) _read(a,b,c) +\#define FILENO(a) _fileno(a) +\#define SSIZE_T int +\#else +\#define POPEN(a,b) popen(a,b) +\#define PCLOSE(a) pclose(a) +\#define READ(a,b,c) read(a,b,c) +\#define FILENO(a) fileno(a) +\#define SSIZE_T ssize_t +\#endif + +// If cursor floating, do a crlf first +if (G_tty->cursor_col()!=0) G_tty->append("\\n"); + +FILE *fp = POPEN(cmd, "r"); +char s[4096]; +if ( fp == NULL ) { + sprintf(s, "\\033[0;31mCan't execute '%.200s': %s\\033[0m\\n", cmd, strerror(errno)); + G_tty->append_utf8(s); + return; +} + +int fd = FILENO(fp); +\#ifndef _WIN32 +// Non-blocking IO keeps interface "alive" during slow commands. +// Too bad Microsoft's implementation of the C library read() call +// is so poor it doesn't properly support non-blocking IO or fcntl(). +// +fcntl(fd, F_SETFL, O_NONBLOCK); +\#endif + +// Read in byte blocks +G_tty->append(NULL); // (clears utf8 cache) +while (1) { + Fl::wait(0.05); + SSIZE_T bytes = READ(fd, s, sizeof(s)); // shout in uppercase so windows can hear us + if (bytes == -1 && errno == EAGAIN) continue; // no data yet + else if (bytes > 0) G_tty->append(s, bytes); // write block to terminal, handles utf8 + else break; // pipe closed +} + +PCLOSE(fp); +G_tty->append_ascii("\\033[33;2m<<END_OF_OUTPUT>>\\033[0m\\n"); +G_tty->redraw();} {} + } + decl {////// GUI LAYOUT //////} {private local + } + Function {update_inputs()} { + comment {Resync the inputs with widget's values} return_type void + } { + code {// Scroll History +scrollhistory_input->value( G_tty->history_rows() ); + +// Redraw rate +redraw_rate_spinner->value( G_tty->redraw_rate() ); + +// Font Size +fontsize_input->value( G_tty->textsize() ); + +// Scrollbar Size +scrollbarsize_input->range(0, 80); +scrollbarsize_input->step(1); +scrollbarsize_input->value( G_tty->scrollbar_size() ); + +// show_unknown() enable/disable +showunknown_radio->value( G_tty->show_unknown() ? 1 : 0 ); + +// ansi() enable/disable +ansi_radio->value(tty ->ansi() ? 1 : 0 ); + +// box() choice +switch ( G_tty->box() ) { + case FL_UP_FRAME: box_choice->value(0); break; + case FL_DOWN_FRAME: box_choice->value(1); break; + case FL_THIN_UP_FRAME: box_choice->value(2); break; + case FL_THIN_DOWN_FRAME: box_choice->value(3); break; + case FL_ENGRAVED_FRAME: box_choice->value(4); break; + case FL_EMBOSSED_FRAME: box_choice->value(5); break; + case FL_BORDER_FRAME: box_choice->value(6); break; + // --- + case FL_UP_BOX: box_choice->value(7); break; + case FL_DOWN_BOX: box_choice->value(8); break; + case FL_FLAT_BOX: box_choice->value(9); break; + case FL_THIN_UP_BOX: box_choice->value(10); break; + case FL_THIN_DOWN_BOX: box_choice->value(11); break; + case FL_BORDER_BOX: box_choice->value(12); break; + case FL_NO_BOX: box_choice->value(13); break; + default: box_choice->value(1); break; +} + +// color() +{ + char s[80]; + sprintf(s, "\#%08x", G_tty->color()); + color_input->value(s); +} + +// textfg/bgcolor() +{ + char s[80]; + sprintf(s, "\#%08x", G_tty->textfgcolor()); + textfgcolor_input->value(s); + sprintf(s, "\#%08x", G_tty->textbgcolor()); + textbgcolor_input->value(s); +} + +// cursorfg/bg color +{ + char s[80]; + sprintf(s, "\#%08x", G_tty->cursorfgcolor()); cursorfgcolor_input->value(s); + sprintf(s, "\#%08x", G_tty->cursorbgcolor()); cursorbgcolor_input->value(s); +} + +// Margins +{ + int lt = G_tty->margin_left(); + int rt = G_tty->margin_right(); + int top = G_tty->margin_top(); + int bot = G_tty->margin_bottom(); + char s[100]; + sprintf(s, "%d, %d, %d, %d", lt,rt,top,bot); + margins_input->value(s); +} + +// selectionfg/bgcolor() +{ + char s[80]; + sprintf(s, "\#%08x", G_tty->selectionfgcolor()); + selectionfgcolor_input->value(s); + sprintf(s, "\#%08x", G_tty->selectionbgcolor()); + selectionbgcolor_input->value(s); +}} {} + } + Function {make_window()} {open + } { + Fl_Window win { + label {Fl_Terminal Test} + callback {exit(0);} open selected + xywh {0 0 772 863} type Double visible + } { + Fl_Spinner scrollhistory_input { + label {Scroll History} + callback {int val = int(scrollhistory_input->value() + .5); +G_tty->history_rows(val);} + tooltip {Scrollback history size. +10,000 max.} xywh {115 10 115 20} labelsize 12 minimum 0 maximum 10000 textsize 12 + } + Fl_Spinner fontsize_input { + label {Font Size} + callback {int val = int(fontsize_input->value() + .5); +G_tty->textsize(val);} + xywh {115 38 115 20} labelsize 12 textsize 12 + } + Fl_Spinner scrollbarsize_input { + label {Scrollbar Size} + callback {int val = int(scrollbarsize_input->value() + .5); +G_tty->scrollbar_size(val); G_tty->redraw();} + tooltip {Size of scrollbar width in pixels} xywh {115 67 115 20} labelsize 12 textsize 12 + } + Fl_Spinner redraw_rate_spinner { + label {Min Redraw Time} + callback {float rate = float(redraw_rate_spinner->value()); +//printf("Setting redraw rate to %.2f\\n", rate); +G_tty->redraw_rate(rate);} + tooltip {Minimum time between redraws +Default is 0.10 secs.} xywh {115 94 115 20} type Float labelsize 12 minimum 0.05 maximum 5 step 0.1 textsize 12 + } + Fl_Choice scheme_widget { + label scheme open + tooltip Scheme xywh {115 120 115 23} down_box BORDER_BOX labelsize 12 textsize 12 + class Fl_Scheme_Choice + } {} + Fl_Choice box_choice { + label {box()} open + tooltip {The border box for the Fl_Terminal} xywh {115 147 115 23} down_box BORDER_BOX labelsize 12 textsize 12 + } { + MenuItem {} { + label FL_UP_FRAME + user_data FL_UP_FRAME user_data_type long + callback {G_tty->box(FL_UP_FRAME); G_tty->redraw();} + xywh {10 10 100 20} labelsize 8 + } + MenuItem {} { + label FL_DOWN_FRAME + user_data FL_DOWN_FRAME user_data_type long + callback {G_tty->box(FL_DOWN_FRAME); G_tty->redraw();} + xywh {10 10 100 20} labelsize 8 + } + MenuItem {} { + label FL_THIN_UP_FRAME + user_data FL_THIN_UP_FRAME user_data_type long + callback {G_tty->box(FL_THIN_UP_FRAME); G_tty->redraw();} + xywh {10 10 100 20} labelsize 8 + } + MenuItem {} { + label FL_THIN_DOWN_FRAME + user_data FL_THIN_DOWN_FRAME user_data_type long + callback {G_tty->box(FL_THIN_DOWN_FRAME); G_tty->redraw();} + xywh {10 10 100 20} labelsize 8 + } + MenuItem {} { + label FL_ENGRAVED_FRAME + user_data FL_ENGRAVED_FRAME user_data_type long + callback {G_tty->box(FL_ENGRAVED_FRAME); G_tty->redraw();} + xywh {10 10 100 20} labelsize 8 + } + MenuItem {} { + label FL_EMBOSSED_FRAME + user_data FL_EMBOSSED_FRAME user_data_type long + callback {G_tty->box(FL_EMBOSSED_FRAME); G_tty->redraw();} + xywh {10 10 100 20} labelsize 8 + } + MenuItem {} { + label FL_BORDER_FRAME + user_data FL_BORDER_FRAME user_data_type long + callback {G_tty->box(FL_BORDER_FRAME); G_tty->redraw();} + xywh {10 10 100 20} labelsize 8 divider + } + MenuItem {} { + label FL_UP_BOX + user_data FL_UP_BOX user_data_type long + callback {G_tty->box(FL_UP_BOX); G_tty->redraw();} + xywh {10 10 100 20} labelsize 8 + } + MenuItem {} { + label FL_DOWN_BOX + callback {G_tty->box(FL_DOWN_BOX); G_tty->redraw();} + xywh {20 20 100 20} labelsize 8 + } + MenuItem {} { + label FL_FLAT_BOX + callback {G_tty->box(FL_FLAT_BOX); +G_tty->redraw();} + xywh {30 30 100 20} labelsize 8 + } + MenuItem {} { + label FL_THIN_UP_BOX + callback {G_tty->box(FL_THIN_UP_BOX); G_tty->redraw();} + xywh {40 40 100 20} labelsize 8 + } + MenuItem {} { + label FL_THIN_DOWN_BOX + callback {G_tty->box(FL_THIN_DOWN_BOX); G_tty->redraw();} + xywh {50 50 100 20} labelsize 8 + } + MenuItem {} { + label FL_BORDER_BOX + callback {G_tty->box(FL_BORDER_BOX); G_tty->redraw();} + xywh {0 0 100 20} labelsize 8 + } + MenuItem {} { + label FL_NO_BOX + callback {G_tty->box(FL_NO_BOX); G_tty->redraw();} + xywh {10 10 100 20} labelsize 8 + } + } + Fl_Input selectionfgcolor_input { + label {selectionfgcolor()} + callback {ulong ival; +const char *val_str = selectionfgcolor_input->value(); +if (sscanf(val_str, "\#%lx", &ival) != 1) + if (sscanf(val_str, "%ld", &ival) != 1) { + fl_alert("Illegal color value (can be e.g. '12' or '\#0c', etc)"); + update_inputs(); + return; +} +G_tty->selectionfgcolor(Fl_Color(ival)); +//DEBUG ::printf("IVAL is %08lx\\n",ival); +G_tty->redraw();} + tooltip {The mouse selection foreground color. +Can be decimal (e.g. 12) or hex (e.g. \#0c, \#0000000c, etc)} xywh {115 185 115 20} labelsize 10 when 28 textfont 4 textsize 12 + } + Fl_Input selectionbgcolor_input { + label {selectionbgcolor()} + callback {ulong ival; +const char *val_str = selectionbgcolor_input->value(); +if (sscanf(val_str, "\#%lx", &ival) != 1) + if (sscanf(val_str, "%ld", &ival) != 1) { + fl_alert("Illegal color value (can be e.g. '12' or '\#0c', etc)"); + update_inputs(); + return; +} +G_tty->selectionbgcolor(Fl_Color(ival)); +//DEBUG ::printf("IVAL is %08lx\\n",ival); +G_tty->redraw();} + tooltip {The mouse selection background color. +Can be decimal (e.g. 12) or hex (e.g. \#0c, \#0000000c, etc)} xywh {115 210 115 20} labelsize 10 when 28 textfont 4 textsize 12 + } + Fl_Check_Button showunknown_radio { + label {show_unknown()} + callback {G_tty->show_unknown(showunknown_radio->value() ? true : false); +G_tty->redraw();} + tooltip {Shows unknown escape sequences/unprintable chars as "¿" character} xywh {255 9 105 22} down_box DOWN_BOX labelsize 10 + } + Fl_Check_Button interactivecursor_radio { + label {Interactive Cursor} + callback {bool val = interactivecursor_radio->value() ? true : false; +G_tty->interactive_cursor(val);} + tooltip {Allow Up/Dn/Lt/Rt keys to move cursor +when terminal has focus} xywh {365 9 125 22} down_box DOWN_BOX labelsize 10 + } + Fl_Check_Button ansi_radio { + label {ansi()} + callback {G_tty->ansi(ansi_radio->value() ? true : false);} + tooltip {Handle ANSI/xterm escape sequences} xywh {255 30 95 20} down_box DOWN_BOX labelsize 10 + } + Fl_Check_Button stdout_radio { + label {Echo tests to stdout} + tooltip {Also send test output to stdout} xywh {365 28 121 20} down_box DOWN_BOX labelsize 10 + } + Fl_Box {} { + label {Lt, Rt, Top, Bot} + xywh {345 50 145 20} labelsize 10 + } + Fl_Input margins_input { + label Margins + callback {int lt,rt,top,bot; +const char *val = margins_input->value(); +if (sscanf(val, "%d,%d,%d,%d",<,&rt,&top,&bot)!=4) { + fl_alert("Margins: expected four comma separated integers"); + return; +} +G_tty->margin_left(lt); +G_tty->margin_right(rt); +G_tty->margin_top(top); +G_tty->margin_bottom(bot); +G_tty->redraw();} + xywh {345 70 145 20} labelsize 12 when 28 textsize 12 + } + Fl_Input color_input { + label {color()} + callback {ulong ival; +const char *val_str = color_input->value(); +if (sscanf(val_str, "\#%lx", &ival) != 1) + if (sscanf(val_str, "%ld", &ival) != 1) { + fl_alert("Illegal color value (can be e.g. '12' or '\#0c', etc)"); + update_inputs(); + return; +} +G_tty->color(Fl_Color(ival)); +//DEBUG ::printf("IVAL is %08lx\\n",ival); +G_tty->redraw();} + tooltip {The widget's background color() +Can be decimal (e.g. 12) or hex (e.g. \#0c, \#0000000c, etc)} xywh {345 95 145 20} labelsize 12 when 28 textfont 4 textsize 12 + } + Fl_Input textfgcolor_input { + label {textfgcolor()} + callback {ulong ival; +const char *val_str = textfgcolor_input->value(); +if (sscanf(val_str, "\#%lx", &ival) != 1) + if (sscanf(val_str, "%ld", &ival) != 1) { + fl_alert("Illegal color value (can be e.g. '12' or '\#0c', etc)"); + update_inputs(); + return; +} +G_tty->textfgcolor(Fl_Color(ival)); +G_tty->textfgcolor_default(Fl_Color(ival)); +//DEBUG ::printf("IVAL is %08lx\\n",ival); +G_tty->redraw();} + tooltip {The text foreground color. +Can be decimal (e.g. 12) or hex (e.g. \#0c, \#0000000c, etc)} xywh {345 120 145 20} labelsize 12 when 28 textfont 4 textsize 12 + } + Fl_Input textbgcolor_input { + label {textbgcolor()} + callback {ulong ival; +const char *val_str = textbgcolor_input->value(); +if (sscanf(val_str, "\#%lx", &ival) != 1) + if (sscanf(val_str, "%ld", &ival) != 1) { + fl_alert("Illegal color value (can be e.g. '12' or '\#0c', etc)"); + update_inputs(); + return; +} +G_tty->textbgcolor(Fl_Color(ival)); +G_tty->textbgcolor_default(Fl_Color(ival)); +//DEBUG ::printf("IVAL is %08lx\\n",ival); +G_tty->redraw();} + tooltip {The text background color. +Can be decimal (e.g. 12) or hex (e.g. \#0c, \#0000000c, etc)} xywh {345 145 145 20} labelsize 12 when 28 textfont 4 textsize 12 + } + Fl_Input cursorfgcolor_input { + label {cursorfgcolor()} + callback {ulong ival; +const char *val_str = cursorfgcolor_input->value(); +if (sscanf(val_str, "\#%lx", &ival) != 1) + if (sscanf(val_str, "%ld", &ival) != 1) { + fl_alert("Illegal color value (can be e.g. '12' or '\#0c', etc)"); + update_inputs(); + return; +} +G_tty->cursorfgcolor(Fl_Color(ival)); +//DEBUG ::printf("IVAL is %08lx\\n",ival); +G_tty->redraw();} + tooltip {Foreground color for text under the cursor.} xywh {345 170 145 20} labelsize 12 when 28 textfont 4 textsize 12 + } + Fl_Input cursorbgcolor_input { + label {cursorbgcolor()} + callback {ulong ival; +const char *val_str = cursorbgcolor_input->value(); +if (sscanf(val_str, "\#%lx", &ival) != 1) + if (sscanf(val_str, "%ld", &ival) != 1) { + fl_alert("Illegal color value (can be e.g. '12' or '\#0c', etc)"); + update_inputs(); + return; +} +G_tty->cursorbgcolor(Fl_Color(ival)); +//DEBUG ::printf("IVAL is %08lx\\n",ival); +G_tty->redraw();} + tooltip {Background color for the cursor. +This is the cursor block's color} xywh {345 195 145 20} labelsize 12 when 28 textfont 4 textsize 12 + } + Fl_Choice {} { + label {Terminal Color} + xywh {345 220 145 20} down_box BORDER_BOX labelsize 12 textsize 12 + } { + MenuItem {} { + label {White on DarkAmber} + callback {G_tty->color(0x30200000); +G_tty->textfgcolor(0xd0d0d000); +G_tty->textbgcolor(0xffffffff); // "see through" color +update_inputs(); +add_lines(50);} + xywh {10 10 100 20} labelsize 9 + } + MenuItem {} { + label {White on Black} + callback {G_tty->textfgcolor(0xd0d0d000); +G_tty->textbgcolor(0xffffffff); +G_tty->color(0x00000000); +update_inputs(); +add_lines(50);} + xywh {20 20 100 20} labelsize 9 + } + MenuItem {} { + label {Black on White} + callback {G_tty->textfgcolor(0x00000000); +G_tty->textbgcolor(0xd0d0d000); +G_tty->color(0xd0d0d000); +update_inputs(); +add_lines(50);} + xywh {20 20 100 20} labelsize 9 + } + MenuItem {} { + label {Green on Dark Green} + callback {G_tty->textfgcolor(0x00d04000); +G_tty->textbgcolor(0x00200800); +G_tty->color(0x00200800); +update_inputs(); +add_lines(50);} + xywh {30 30 100 20} labelsize 9 + } + MenuItem {} { + label {Orange on Dark Orange} + callback {G_tty->textfgcolor(0xd0704000); +G_tty->textbgcolor(0x20100000); +G_tty->color(0x20100000); +update_inputs(); +add_lines(50);} + xywh {40 40 100 20} labelsize 9 + } + } + Fl_Group {} { + label {Terminal Ops} + xywh {505 15 120 210} box ENGRAVED_FRAME labelsize 11 + } { + Fl_Button {} { + label {Clear Screen} + callback {G_tty->append("\\033[H\\033[2J"); // home, cls +G_tty->redraw(); +// Reset the 'Add +50' line counter to 1 +G_lines = 1;} + tooltip {Clear terminal screen. +Moves what was on the screen to the scroll history.} xywh {520 31 90 25} labelsize 11 + } + Fl_Button {} { + label {Clear History} + callback {G_tty->append("\\033[3J"); // clr history +G_tty->redraw();} + tooltip {Clear scrollback history.} xywh {520 62 90 25} labelsize 11 + } + Fl_Button {} { + label {Reset Terminal} + callback {G_tty->append("\\033c"); // reset terminal +G_tty->redraw();} + tooltip {Reset terminal. +Clears: screen, history, sets default tabstops, etc.} xywh {520 93 90 25} labelsize 11 + } + Fl_Button {} { + label {Home Cursor} + callback {G_tty->append("\\033[H");} + tooltip {Moves cursor to home position (top/left) using ESC[H} xywh {520 124 90 25} labelsize 11 + } + Fl_Button {} { + label {Speed Test} + callback {speed_test();} + tooltip {Runs a full screen random chars/colors +Shortcut: S} xywh {520 155 90 25} shortcut 0x73 labelsize 11 + } + Fl_Button {} { + label {Ring Debug} + callback {// Force scroll history to be small so we can see entire ring w/out scrolling +if (scrollhistory_input->value() > 35) { + scrollhistory_input->value(35); + scrollhistory_input->do_callback(); +} + +// Show debug window +G_tty->show_ring_debug_window();} + tooltip {Show the Fl_Terminal raw ring buffer contents. +(Warning: This slows the UI and uses continuous cpu until closed)} xywh {520 187 90 25} labelsize 11 + } + } + Fl_Group {} { + label {Terminal Tests} + xywh {640 15 120 210} box ENGRAVED_FRAME labelsize 11 + } { + Fl_Button {} { + label {Unicode +Alignment} + callback {unicode_alignment();} + tooltip {Show a Unicode screen alignment test +Checks that troublesome Unicode chars don't cause misalignment} xywh {655 25 90 30} labelsize 9 + } + Fl_Button {} { + label {+50 Lines} + callback {add_lines(50);} + tooltip {Add 50 lines of text to terminal. +Tests screen history, scrollup} xywh {655 60 90 30} labelsize 9 + } + Fl_Button {} { + label {Color Bars} + callback {show_colorbars();} + tooltip {Show colorbar test +Tests API for setting colors +Does *NOT* use ESC codes} xywh {655 95 90 30} labelsize 11 + } + Fl_Box {} { + label {Self Tests} + xywh {655 167 90 15} labelsize 10 + } + Fl_Button {} { + label {@<<} + callback {NextEscapeTest(-1);} + comment {Move to previous test} + tooltip {Reverse through the ESC code self tests +Shortcut: SHIFT+E} xywh {655 185 40 30} shortcut 0x10065 labelsize 11 + } + Fl_Button {} { + label {@>>} + callback {NextEscapeTest(1);} + comment {Move to next test} + tooltip {Advance through the ESC code self tests +Shortcut: 'e'} xywh {705 185 40 30} shortcut 0x65 labelsize 11 + } + } + Fl_Input showfile_input { + label {Show File} + callback {showfile_input->deactivate(); // causes focus loss? +command_input->deactivate(); +win->redraw(); +Fl::wait(.01); +show_file(showfile_input->value()); +showfile_input->activate(); +command_input->activate(); +Fl::focus(showfile_input); // return focus +win->redraw();} + tooltip {Type in the pathname of a file to cat to the screen} xywh {109 250 650 25} labelsize 12 when 10 textfont 4 + } + Fl_Input command_input { + label Command + callback {showfile_input->deactivate(); +command_input->deactivate(); // causes focus loss? +win->redraw(); +Fl::wait(.01); +do_command(command_input->value()); +showfile_input->activate(); +command_input->activate(); +Fl::focus(command_input); // return focus +win->redraw();} + tooltip {Command to run. +Hit ENTER to run command. +The command's stdout will appear in terminal.} xywh {109 285 650 25} labelsize 12 when 12 textfont 4 + } + Fl_Group tty {open + xywh {10 320 750 530} box DOWN_FRAME color 0 + class MyTerminal + } {} + } + code {// CTOR +G_tty = tty; // global access +G_tty->color(0x30200000); // dark orange (makes true black easy to see) +G_tty->textsize(12); // smaller text (RGB demo needs this) +win->resizable(tty); +command_input->when(FL_WHEN_ENTER_KEY_ALWAYS); +command_input->take_focus(); // ensure command prompt has keyboard focus on startup + +\#ifdef _WIN32 +showfile_input->value("c:\\\\Windows\\\\system.ini"); +command_input->value("dir c:\\\\windows"); +\#else +showfile_input->value("/etc/login.defs"); +command_input->value("ls -la --color && ping google.com -c 5"); +\#endif + +// Sync input vals with widget's current values +update_inputs();} {} + } +} + +decl {////// MAIN //////} {private local +} + +Function {} {open +} { + code {G_tty = NULL; +Application app; // sets G_tty +G_app = &app; +app.show(argc, argv); +// Run the "firstpage" test +app.test_firstpage(false);} {} +} diff --git a/test/tree.fl b/test/tree.fl index cb2c8803e..573641535 100644 --- a/test/tree.fl +++ b/test/tree.fl @@ -38,7 +38,7 @@ decl {\#include <FL/Fl_Color_Chooser.H>} {public global decl {\#include <FL/Fl_Text_Display.H>} {public global } -decl {\#include <FL/Fl_Simple_Terminal.H>} {public global +decl {\#include <FL/Fl_Terminal.H>} {public global } decl {int G_cb_counter = 0;} { @@ -1749,7 +1749,7 @@ helpwin->show();} Fl_Box tty { label label xywh {16 571 1014 149} box DOWN_BOX color 0 - class Fl_Simple_Terminal + class Fl_Terminal } } code {// Initialize Tree diff --git a/test/unittest_core.cxx b/test/unittest_core.cxx index 2d109680f..23e7ab953 100644 --- a/test/unittest_core.cxx +++ b/test/unittest_core.cxx @@ -18,8 +18,9 @@ #include <FL/Fl_Group.H> #include <FL/Fl_Button.H> -#include <FL/Fl_Simple_Terminal.H> +#include <FL/Fl_Terminal.H> #include "../src/Fl_String.H" +#include <FL/Fl_Preferences.H> #include <FL/fl_callback_macros.H> #include <FL/filename.H> #include <FL/fl_utf8.h> @@ -374,7 +375,7 @@ TEST(Fl_Callback_Macros, FL_INLINE_CALLBACK) { */ class Ut_Core_Test : public Fl_Group { - Fl_Simple_Terminal *tty; + Fl_Terminal *tty; bool suite_ran_; public: @@ -390,7 +391,7 @@ public: tty(NULL), suite_ran_(false) { - tty = new Fl_Simple_Terminal(x+4, y+4, w-8, h-8, "Unittest Log"); + tty = new Fl_Terminal(x+4, y+4, w-8, h-8, "Unittest Log"); tty->ansi(true); end(); Ut_Suite::tty = tty; diff --git a/test/unittest_simple_terminal.cxx b/test/unittest_simple_terminal.cxx deleted file mode 100644 index 9e8324926..000000000 --- a/test/unittest_simple_terminal.cxx +++ /dev/null @@ -1,121 +0,0 @@ -// -// Unit tests for the Fast Light Tool Kit (FLTK). -// -// Copyright 1998-2022 by Bill Spitzak and others. -// -// This library is free software. Distribution and use rights are outlined in -// the file "COPYING" which should have been included with this file. If this -// file is missing or damaged, see the license at: -// -// https://www.fltk.org/COPYING.php -// -// Please see the following page on how to report bugs and issues: -// -// https://www.fltk.org/bugs.php -// - -#include "unittests.h" - -#include <time.h> -#include <FL/Fl_Group.H> -#include <FL/Fl_Simple_Terminal.H> - -// -//------- test the Fl_Simple_Terminal drawing capabilities ---------- -// -class Ut_Simple_Terminal_Test : public Fl_Group { - Fl_Simple_Terminal *tty1; - Fl_Simple_Terminal *tty2; - Fl_Simple_Terminal *tty3; - void ansi_test_pattern(Fl_Simple_Terminal *tty) { - tty->append("\033[30mBlack Courier 14\033[0m Normal text\n" - "\033[31mRed Courier 14\033[0m Normal text\n" - "\033[32mGreen Courier 14\033[0m Normal text\n" - "\033[33mYellow Courier 14\033[0m Normal text\n" - "\033[34mBlue Courier 14\033[0m Normal text\n" - "\033[35mMagenta Courier 14\033[0m Normal text\n" - "\033[36mCyan Courier 14\033[0m Normal text\n" - "\033[37mWhite Courier 14\033[0m Normal text\n" - "\033[40mBright Black Courier 14\033[0m Normal text\n" - "\033[41mBright Red Courier 14\033[0m Normal text\n" - "\033[42mBright Green Courier 14\033[0m Normal text\n" - "\033[43mBright Yellow Courier 14\033[0m Normal text\n" - "\033[44mBright Blue Courier 14\033[0m Normal text\n" - "\033[45mBright Magenta Courier 14\033[0m Normal text\n" - "\033[46mBright Cyan Courier 14\033[0m Normal text\n" - "\033[47mBright White Courier 14\033[0m Normal text\n" - "\n" - "\033[31mRed\033[32mGreen\033[33mYellow\033[34mBlue\033[35mMagenta\033[36mCyan\033[37mWhite\033[0m - " - "\033[31mX\033[32mX\033[33mX\033[34mX\033[35mX\033[36mX\033[37mX\033[0m\n" - "\033[41mRed\033[42mGreen\033[43mYellow\033[44mBlue\033[45mMagenta\033[46mCyan\033[47mWhite\033[0m - " - "\033[41mX\033[42mX\033[43mX\033[44mX\033[45mX\033[46mX\033[47mX\033[0m\n"); - } - void gray_test_pattern(Fl_Simple_Terminal *tty) { - tty->append("Grayscale Test Pattern\n" - "--------------------------\n" - "\033[0m 100% white Courier 14\n" - "\033[1m 90% white Courier 14\n" - "\033[2m 80% white Courier 14\n" - "\033[3m 70% white Courier 14\n" - "\033[4m 60% white Courier 14\n" - "\033[5m 50% white Courier 14\n" - "\033[6m 40% white Courier 14\n" - "\033[7m 30% white Courier 14\n" - "\033[8m 20% white Courier 14\n" - "\033[9m 10% white Courier 14\n" - "\033[0m"); - } - static void date_timer_cb(void *data) { - Fl_Simple_Terminal *tty = (Fl_Simple_Terminal*)data; - time_t lt = time(NULL); - tty->printf("The time and date is now: %s", ctime(<)); - Fl::repeat_timeout(3.0, date_timer_cb, data); - } -public: - static Fl_Widget *create() { - return new Ut_Simple_Terminal_Test(UT_TESTAREA_X, UT_TESTAREA_Y, UT_TESTAREA_W, UT_TESTAREA_H); - } - Ut_Simple_Terminal_Test(int x, int y, int w, int h) - : Fl_Group(x, y, w, h) { - static Fl_Text_Display::Style_Table_Entry my_stable[] = { // 10 entry grayscale - // Font Color Font Face Font Size ANSI Sequence - // ---------- ---------------- --------- ------------- - { 0xffffff00, FL_COURIER_BOLD, 14 }, // "\033[0m" 0 white 100% - { 0xe6e6e600, FL_COURIER_BOLD, 14 }, // "\033[1m" 1 white 90% - { 0xcccccc00, FL_COURIER_BOLD, 14 }, // "\033[2m" 2 white 80% - { 0xb3b3b300, FL_COURIER_BOLD, 14 }, // "\033[3m" 3 white 70% - { 0x99999900, FL_COURIER_BOLD, 14 }, // "\033[4m" 4 white 60% - { 0x80808000, FL_COURIER_BOLD, 14 }, // "\033[5m" 5 white 50% "\033[0m" - { 0x66666600, FL_COURIER_BOLD, 14 }, // "\033[6m" 6 white 40% - { 0x4d4d4d00, FL_COURIER_BOLD, 14 }, // "\033[7m" 7 white 30% - { 0x33333300, FL_COURIER_BOLD, 14 }, // "\033[8m" 8 white 20% - { 0x1a1a1a00, FL_COURIER_BOLD, 14 }, // "\033[9m" 9 white 10% - }; - int tty_h = (int)(h/3.5); - int tty_y1 = y+(tty_h*0)+20; - int tty_y2 = y+(tty_h*1)+40; - int tty_y3 = y+(tty_h*2)+60; - - // TTY1 - tty1 = new Fl_Simple_Terminal(x, tty_y1, w, tty_h,"Tty 1: ANSI off"); - tty1->ansi(false); - Fl::add_timeout(0.5, date_timer_cb, (void*)tty1); - - // TTY2 - tty2 = new Fl_Simple_Terminal(x, tty_y2, w, tty_h,"Tty 2: ANSI on"); - tty2->ansi(true); - ansi_test_pattern(tty2); - Fl::add_timeout(0.5, date_timer_cb, (void*)tty2); - - // TTY3 - tty3 = new Fl_Simple_Terminal(x, tty_y3, w, tty_h, "Tty 3: Grayscale Style Table"); - tty3->style_table(my_stable, sizeof(my_stable), 0); - tty3->ansi(true); - gray_test_pattern(tty3); - Fl::add_timeout(0.5, date_timer_cb, (void*)tty3); - - end(); - } -}; - -UnitTest simple_terminal(UT_TEST_SIMPLE_TERMINAL, "Simple Terminal", Ut_Simple_Terminal_Test::create); diff --git a/test/unittest_terminal.cxx b/test/unittest_terminal.cxx new file mode 100644 index 000000000..0c6aa338b --- /dev/null +++ b/test/unittest_terminal.cxx @@ -0,0 +1,98 @@ +// +// Unit tests for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2022 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include "unittests.h" + +#include <time.h> +#include <FL/Fl_Group.H> +#include <FL/Fl_Terminal.H> + +// +//------- test the Fl_Terminal drawing capabilities ---------- +// +class Ut_Terminal_Test : public Fl_Group { + Fl_Terminal *tty1; + Fl_Terminal *tty2; + void ansi_test_pattern(Fl_Terminal *tty) { + tty->append("\033[30mBlack Courier 14\033[0m Normal text\n" + "\033[31mRed Courier 14\033[0m Normal text\n" + "\033[32mGreen Courier 14\033[0m Normal text\n" + "\033[33mYellow Courier 14\033[0m Normal text\n" + "\033[34mBlue Courier 14\033[0m Normal text\n" + "\033[35mMagenta Courier 14\033[0m Normal text\n" + "\033[36mCyan Courier 14\033[0m Normal text\n" + "\033[37mWhite Courier 14\033[0m Normal text\n" + "\033[1;30mBright Black Courier 14\033[0m Normal text\n" + "\033[1;31mBright Red Courier 14\033[0m Normal text\n" + "\033[1;32mBright Green Courier 14\033[0m Normal text\n" + "\033[1;33mBright Yellow Courier 14\033[0m Normal text\n" + "\033[1;34mBright Blue Courier 14\033[0m Normal text\n" + "\033[1;35mBright Magenta Courier 14\033[0m Normal text\n" + "\033[1;36mBright Cyan Courier 14\033[0m Normal text\n" + "\033[1;37mBright White Courier 14\033[0m Normal text\n" + "\n" + "\033[31mRed\033[32mGreen\033[33mYellow\033[34mBlue\033[35mMagenta\033[36mCyan\033[37mWhite\033[0m - " + "\033[31mX\033[32mX\033[33mX\033[34mX\033[35mX\033[36mX\033[37mX\033[0m\n" + "\033[1;31mRed\033[1;32mGreen\033[1;33mYellow\033[34mBlue\033[35mMagenta\033[36mCyan\033[1;37mWhite\033[1;0m - " + "\033[1;31mX\033[1;32mX\033[1;33mX\033[1;34mX\033[1;35mX\033[1;36mX\033[1;37mX\033[0m\n"); + } + void gray_test_pattern(Fl_Terminal *tty) { + tty->append("Grayscale Test Pattern\n" + "--------------------------\n" + "\033[38;2;255;255;255m 100% white Courier 14\n" // ESC xterm codes for setting r;g;b colors + "\033[38;2;230;230;230m 90% white Courier 14\n" + "\033[38;2;205;205;205m 80% white Courier 14\n" + "\033[38;2;179;179;179m 70% white Courier 14\n" + "\033[38;2;154;154;154m 60% white Courier 14\n" + "\033[38;2;128;128;128m 50% white Courier 14\n" + "\033[38;2;102;102;102m 40% white Courier 14\n" + "\033[38;2;77;77;77m" " 30% white Courier 14\n" + "\033[38;2;51;51;51m" " 20% white Courier 14\n" + "\033[38;2;26;26;26m" " 10% white Courier 14\n" + "\033[38;2;0;0;0m" " 0% white Courier 14\n" + "\033[0m"); + } + static void date_timer_cb(void *data) { + Fl_Terminal *tty = (Fl_Terminal*)data; + time_t lt = time(NULL); + tty->printf("The time and date is now: %s", ctime(<)); + Fl::repeat_timeout(3.0, date_timer_cb, data); + } +public: + static Fl_Widget *create() { + return new Ut_Terminal_Test(UT_TESTAREA_X, UT_TESTAREA_Y, UT_TESTAREA_W, UT_TESTAREA_H); + } + Ut_Terminal_Test(int x, int y, int w, int h) + : Fl_Group(x, y, w, h) { + int tty_h = (int)(h/2.25+.5); + int tty_y1 = y+(tty_h*0)+20; + int tty_y2 = y+(tty_h*1)+40; + + // TTY1 + tty1 = new Fl_Terminal(x, tty_y1, w, tty_h,"Tty 1: Colors"); + ansi_test_pattern(tty1); + Fl::add_timeout(0.5, date_timer_cb, (void*)tty1); + + // TTY2 + tty2 = new Fl_Terminal(x, tty_y2, w, tty_h,"Tty 2: Grayscale"); + gray_test_pattern(tty2); + Fl::add_timeout(0.5, date_timer_cb, (void*)tty2); + + end(); + } +}; + +UnitTest simple_terminal(UT_TEST_SIMPLE_TERMINAL, "Terminal", Ut_Terminal_Test::create); diff --git a/test/unittests.cxx b/test/unittests.cxx index b9103feb5..4783c223e 100644 --- a/test/unittests.cxx +++ b/test/unittests.cxx @@ -29,7 +29,7 @@ #include <FL/Fl_Double_Window.H> #include <FL/Fl_Hold_Browser.H> #include <FL/Fl_Help_View.H> -#include <FL/Fl_Simple_Terminal.H> +#include <FL/Fl_Terminal.H> #include <FL/Fl_Group.H> #include <FL/Fl_Box.H> #include <FL/fl_draw.H> // fl_text_extents() @@ -181,7 +181,7 @@ int Ut_Suite::num_failed_ = 0; const char *Ut_Suite::red = "\033[31m"; const char *Ut_Suite::green = "\033[32m"; const char *Ut_Suite::normal = "\033[0m"; -Fl_Simple_Terminal *Ut_Suite::tty = NULL; +Fl_Terminal *Ut_Suite::tty = NULL; /** Switch the user of color escape sequnces in the log text. */ void Ut_Suite::color(int v) { diff --git a/test/unittests.h b/test/unittests.h index babecde64..ab55c48af 100644 --- a/test/unittests.h +++ b/test/unittests.h @@ -22,7 +22,7 @@ #include <stdarg.h> -class Fl_Simple_Terminal; +class Fl_Terminal; // WINDOW/WIDGET SIZES const int UT_MAINWIN_W = 700; // main window w() @@ -153,7 +153,7 @@ public: static const char *red; static const char *green; static const char *normal; - static Fl_Simple_Terminal *tty; + static Fl_Terminal *tty; }; #define UT_CONCAT_(prefix, suffix) prefix##suffix diff --git a/test/valuators.fl b/test/valuators.fl index a8dcff87f..8b40eaef1 100644 --- a/test/valuators.fl +++ b/test/valuators.fl @@ -2,7 +2,7 @@ version 1.0400 header_name {.h} code_name {.cxx} -decl {\#include <FL/Fl_Simple_Terminal.H>} {public global +decl {\#include <FL/Fl_Terminal.H>} {public global } Function {} {open @@ -232,9 +232,9 @@ Function {} {open xywh {460 385 110 115} box BORDER_FRAME color 0 selection_color 0 labelsize 11 align 128 } Fl_Box tty {selected - xywh {10 513 560 117} color 0 + xywh {10 513 560 117} box DOWN_BOX color 0 code0 {o->ansi(true);} - class Fl_Simple_Terminal + class Fl_Terminal } } } @@ -242,7 +242,7 @@ Function {} {open Function {callback(Fl_Widget* o, void*)} {open return_type void } { code {const char *name = (const char*)(o->user_data() ? o->user_data() : "???"); -tty->printf("callback(): %s value() = \\033[2m%g\\033[0m\\n", +tty->printf("callback(): %s value() = \\033[1m%g\\033[0m\\n", name, ((Fl_Valuator*)o)->value());} {} } @@ -250,6 +250,6 @@ Function {callback_spinner(Fl_Widget* o, void*)} { comment {Spinner doesn't derive from Fl_Valuator..} open return_type void } { code {const char *name = (const char*)(o->user_data() ? o->user_data() : "???"); -tty->printf("callback(): %s value() = \\033[2m%g\\033[0m\\n", +tty->printf("callback(): %s value() = \\033[1m%g\\033[0m\\n", name, ((Fl_Spinner*)o)->value());} {} } |
