Browse Source

Converting terminal over to class.

This needs to be tested, and then integrated
into the other things that need it.  (render?)
bugz 4 years ago
parent
commit
28ff3bd46a
2 changed files with 413 additions and 31 deletions
  1. 388 30
      terminal.cpp
  2. 25 1
      terminal.h

+ 388 - 30
terminal.cpp

@@ -10,11 +10,385 @@ Everything else, I'm not sure I really care about.   (NNY!)
 #include "utils.h"
 #include "zf_log.h"
 #include <ctype.h>
+#include <sstream>
 #include <stdio.h> // snprintf
 #include <stdlib.h>
 #include <string.h>
 #include <vector>
 
+Terminal::Terminal() { this->init(); }
+
+void Terminal::init(void) {
+  this->posx = this->posy = 0;
+  this->in_ansi = 0;
+  this->fgcolor = 7;
+  this->bgcolor = 0;
+  this->status = 0;
+  this->saved_cursor_position.clear();
+}
+
+void Terminal::ansi_color(int color) {
+  // ZF_LOGV("ansi_color(%d)", color);
+  if (color == 0) {
+    this->status = 0;
+    this->fgcolor = 7;
+    this->bgcolor = 0;
+    return;
+  }
+  if ((color == 1) || (color == 2) || (color == 3) || (color == 4) ||
+      (color == 5)) {
+    this->status = color;
+    return;
+  }
+  if ((color >= 30) && (color <= 37)) {
+    this->fgcolor = color - 30;
+    return;
+  }
+  if ((color >= 40) && (color <= 47)) {
+    this->bgcolor = color - 40;
+    return;
+  }
+  if (color == 39) {
+    // default fg color
+    this->fgcolor = 7;
+    return;
+  }
+  if (color == 49) {
+    // default bg color
+    this->bgcolor = 0;
+    return;
+  }
+  ZF_LOGD("ansi_color( %d ) is unknown to me.", color);
+}
+
+std::string Terminal::color_restore(void) {
+  std::ostringstream oss;
+
+  // possible optimize:  If fg is 7, don't set (0 reset does that), if bg is 0,
+  // don't set (0 does that)
+
+  if (this->status == 0) {
+    oss << "\x1b[0;" << this->fgcolor + 30 << ";" << this->bgcolor + 40 << "m";
+  } else {
+    oss << "\x1b[0;" << this->status << ";" << this->fgcolor + 30 << ";"
+        << this->bgcolor + 40 << "m";
+  }
+
+  std::string buffer = oss.str();
+  return buffer;
+}
+
+Terminal::ANSI_TYPE Terminal::ansi_code(std::string ansi) {
+  std::string::iterator cp = ansi.begin();
+  std::string::iterator last = ansi.end() - 1;
+
+  int number, number2;
+
+  if (*cp == '\x1b') {
+    ++cp;
+    // Ok, that's expected.
+    if (*cp == '[') {
+      ++cp;
+
+      // https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences
+
+      switch (*last) {
+      case 'A':
+        // cursor up
+        if (cp == last) {
+          number = 1;
+        } else {
+          number = std::stoi(std::string(cp, last));
+          if (number < 1) {
+            ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi.c_str()),
+                    number);
+            number = 1;
+          }
+        };
+        this->posy -= number;
+        if (this->posy < 0) {
+          this->posy = 0;
+          ZF_LOGD(
+              "console_ansi( %s ): attempt to move above top of screen (%d)",
+              repr(ansi.c_str()), number);
+        }
+        return CURSOR;
+      case 'B':
+        // cursor down
+        if (cp == last) {
+          number = 1;
+        } else {
+          number = std::stoi(std::string(cp, last));
+          if (number < 1) {
+            ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi.c_str()),
+                    number);
+            number = 1;
+          }
+        };
+        this->posy += number;
+        // check range/"scroll"
+        return CURSOR;
+
+      case 'C':
+        // cursor forward
+        if (cp == last) {
+          number = 1;
+        } else {
+          number = std::stoi(std::string(cp, last));
+          if (number < 1) {
+            ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi.c_str()),
+                    number);
+            number = 1;
+          }
+        };
+        this->posx += number;
+
+        // Well.  According to the "spec", the screen limits are hard
+        // If the cursor is already at the edge of the screen, this has no
+        // effect. ^ This is *NOT* how any ANSI BBS terminal program acts!
+
+        while (this->posx > 79) {
+          this->posy++;
+          // check range/"scroll"
+          this->posx -= 79;
+        }
+        return CURSOR;
+
+      case 'D':
+        // cursor backwards
+        if (cp == last) {
+          number = 1;
+        } else {
+          number = std::stoi(std::string(cp, last));
+          if (number < 1) {
+            ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi.c_str()),
+                    number);
+            number = 0;
+          }
+        };
+        this->posx -= number;
+
+        // Well.  According to the "spec", the screen limits are hard
+        // If the cursor is already at the edge of the screen, this has no
+        // effect. ^ This is *NOT* how any ANSI BBS terminal program acts!
+
+        while (this->posx < 0) {
+          this->posy--;
+          if (this->posy < 0) {
+            this->posy = 0;
+          }
+          this->posx += 79;
+        }
+        return CURSOR;
+
+      case 'H':
+        // cursor position
+
+        if (*cp == ';') {
+          // Missing first number
+          number = 1;
+          ++cp;
+          if (cp == last) {
+            // missing 2nd number as well?
+            number2 = 1;
+          } else {
+            number2 = std::stoi(std::string(cp, last));
+          }
+        } else {
+          // Ok, find the first number
+          number = std::stoi(std::string(cp, last));
+
+          // covert iterator to position
+          std::size_t pos = ansi.find(';', std::distance(ansi.begin(), cp));
+
+          if (pos == std::string::npos) {
+            // Missing 2nd number
+            number2 = 1;
+          } else {
+            // 2nd number found, maybe.
+            cp = ansi.begin() + pos;
+            ++cp;
+            if (cp == last) {
+              number2 = 1;
+            } else {
+              number2 = std::stoi(std::string(cp, last));
+            }
+          }
+        }
+
+        // Our positions start at zero, not one.
+        this->posy = number - 1;
+        this->posx = number2 - 1;
+        return CURSOR;
+
+      case 'J':
+        // clear
+        if (cp == last) {
+          number = 0;
+        } else {
+          number = std::stoi(std::string(cp, last));
+        };
+
+        // clears ... part of the screen.
+        if (number == 2) {
+          this->posx = 0;
+          this->posy = 0;
+        };
+        return CLEAR;
+
+      case 'K':
+        // clear line
+        if (cp == last) {
+          number = 0;
+        } else {
+          number = std::stoi(std::string(cp, last));
+        };
+        return CLEAR;
+
+      case 'm':
+        // color
+        if (cp == last) {
+          // nothing given, default to 0.
+          number = 0;
+          ansi_color(number);
+        } else {
+          while (cp != last) {
+            number = std::stoi(std::string(cp, last));
+            ansi_color(number);
+            ++cp;
+
+            while ((cp != last) && (isdigit(*cp))) {
+              ++cp;
+            };
+
+            if (cp != last) {
+              if (*cp == ';') {
+                cp++;
+              }
+            }
+          }
+        }
+        return COLOR;
+
+      case 's':
+        // save position
+        saved_cursor_position.push_back(std::make_pair(this->posx, this->posy));
+        return CURSOR;
+      case 'u':
+        // restore position
+        if (saved_cursor_position.empty()) {
+          ZF_LOGE("Restore cursor position from empty history.");
+        } else {
+          std::pair<int, int> pos = saved_cursor_position.back();
+          this->posx = pos.first;
+          this->posy = pos.second;
+          saved_cursor_position.pop_back();
+        }
+        return CURSOR;
+
+      case 't':
+      case 'r':
+      case 'h':
+      case '!':
+        // These are ones that I don't care about.
+      case 'n': // This is terminal detect -- give me cursor position
+        return OTHER;
+
+      default:
+        // unsure -- possibly not important
+        ZF_LOGD("console_ansi( %s ): ???", repr(ansi.c_str()));
+        return OTHER;
+      }
+    }
+  }
+  ZF_LOGD("console_ansi( %s ) : ???", repr(ansi.c_str()));
+  return OTHER;
+}
+
+Terminal::termchar Terminal::putchar(char ch) {
+  Terminal::termchar tc;
+
+  if (this->in_ansi) {
+    // Ok, append this char
+    this->ansi.append(1, ch);
+
+    if (isalpha(ch)) {
+      // Ok!  end of ANSI code, process it.
+      tc.ansi = ansi_code(this->ansi);
+      this->in_ansi = 0;
+      this->ansi.clear();
+      tc.in_ansi = 1;
+      return tc;
+    }
+    tc.ansi = START;
+    tc.in_ansi = 1;
+    return tc;
+  } else {
+    tc.ansi = START;
+    tc.in_ansi = 0;
+
+    if (ch == '\x1b') {
+      this->ansi.append(1, ch);
+      this->in_ansi = 1;
+      tc.in_ansi = 1;
+      return tc;
+    }
+
+    // should I try reporting MOTION non-ANSI ?
+    if (ch == '\r') {
+      // Carriage return
+      this->posx = 0;
+      return tc;
+    }
+    if (ch == '\n') {
+      this->posy++;
+      // check range/"scroll"
+      return tc;
+    }
+    if (ch == '\b') {
+      // Backspace.
+      if (this->posx > 0) {
+        this->posx--;
+      }
+      return tc;
+    }
+    if (ch == '\f') {
+      // form feed
+      // treat as clear screen
+      this->posx = 0;
+      this->posy = 0;
+      return tc;
+    }
+
+    /*
+      I don't believe that anything else can possibly be here, other then an
+      actual printable character.  So!
+
+      FUTURE: Store the screen text + colors
+    */
+
+    this->posx++;
+
+    if (this->posx > 79) {
+      this->posx = 0;
+      this->posy++;
+      // check range/"scroll"
+    }
+    return tc;
+  }
+}
+
+void Terminal::putstring(std::string text) {
+  // This gets noisy when called from render effect ^D
+  // ZF_LOGI("console_char %lu chars", chars.size());
+
+  for (auto strit = text.begin(); strit != text.end(); strit++)
+    putchar(*strit);
+}
+
+
+// Old non-C++ ways
+
 void console_init(struct console_details *cdp) {
   cdp->posx = 0;
   cdp->posy = 0;
@@ -26,6 +400,10 @@ void console_init(struct console_details *cdp) {
   cdp->status = 0;
 }
 
+/*
+ * Given the ansi number codes
+ * Figure out fg, bg and status.
+ */
 void ansi_color(struct console_details *cdp, int color) {
   // ZF_LOGV("ansi_color(%d)", color);
   if (color == 0) {
@@ -85,6 +463,7 @@ const char *color_restore(struct console_details *cdp) {
   return buffer;
 }
 
+/* store cursor position X,Y */
 std::vector<std::pair<int, int>> cursor_position_history;
 
 ANSI_TYPE console_ansi(struct console_details *cdp, const char *ansi) {
@@ -313,6 +692,12 @@ ANSI_TYPE console_ansi(struct console_details *cdp, const char *ansi) {
 /*
  * console_char()
  *  return whether or not we are still in_ansi
+ *
+ * in_ansi TRUE: START (receiving start of/fragment of ANSI)
+ *               CURSOR, COLOR, CLEAR, OTHER
+ *               Results of the last ANSI parse.
+ * in_ansi FALSE: START Normal character.
+ *
  */
 termchar console_char(struct console_details *cdp, char ch) {
   char *cp;
@@ -325,7 +710,7 @@ termchar console_char(struct console_details *cdp, char ch) {
     cp++;
     *cp = 0;
     if (isalpha(ch)) {
-      // Ok!
+      // Ok!  end of ANSI code, process it.
       tc.ansi = console_ansi(cdp, cdp->ansi);
       cdp->in_ansi = 0;
       cdp->ansi[0] = 0;
@@ -379,6 +764,8 @@ termchar console_char(struct console_details *cdp, char ch) {
     /*
       I don't believe that anything else can possibly be here, other then an
       actual printable character.  So!
+
+      FUTURE: Store the screen text + colors
     */
 
     cdp->posx++;
@@ -392,39 +779,10 @@ termchar console_char(struct console_details *cdp, char ch) {
   }
 }
 
-#ifdef UNWANTED
-void console_string(struct console_details *cdp, const char *chars) {
-  int x;
-  for (x = 0; x < strlen(chars); x++) {
-    console_char(cdp, chars[x]);
-  }
-}
-#endif
-
-// extern void log_flush(void);
-
 void console_receive(struct console_details *cdp, std::string chars) {
-
   // This gets noisy when called from render effect ^D
   // ZF_LOGI("console_char %lu chars", chars.size());
 
-  /*  // the c way
-  for (int x = 0; x < chars.size(); x++) {
-    console_char(cdp, chars[x]);
-  }
-  */
-
-  // the C++ way
   for (auto strit = chars.begin(); strit != chars.end(); strit++)
     console_char(cdp, *strit);
 }
-
-#ifdef UNWANTED
-void console_receive(struct console_details *cdp, const char *chars, int len) {
-  int x;
-
-  for (x = 0; x < len; x++) {
-    console_char(cdp, chars[x]);
-  }
-}
-#endif

+ 25 - 1
terminal.h

@@ -2,8 +2,32 @@
 #define TERMINAL_H
 
 #include <string>
+#include <vector>
 
-// TODO:  add class.  :P
+class Terminal {
+private:
+  int posx, posy;
+  std::vector<std::pair<int, int>> saved_cursor_position;
+  std::string ansi;
+  int in_ansi;
+  int fgcolor, bgcolor, status;
+  enum ANSI_TYPE { START, CURSOR, COLOR, CLEAR, OTHER };
+  struct termchar {
+    int in_ansi;
+    ANSI_TYPE ansi;
+  };
+
+public:
+  Terminal(void);
+  void init(void);
+  std::string color_restore(void);
+  termchar putchar(char ch);
+  void putstring(std::string text);
+
+private:
+  void ansi_color(int color);
+  ANSI_TYPE ansi_code(std::string ansi);
+};
 
 struct console_details {
   int posx, posy;