Advertisement
Guest User

watte

a guest
Nov 6th, 2021
22
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 10.38 KB | None | 0 0
  1. /*
  2.  *  Watte - weird and trivially tiny editor
  3.  *  Copyright (C) 2020 Akiko <akiko@linux-addicted.net>
  4.  *
  5.  *  This program is free software: you can redistribute it and/or modify
  6.  *  it under the terms of the GNU General Public License as published by
  7.  *  the Free Software Foundation, either version 3 of the License, or
  8.  *  (at your option) any later version.
  9.  *
  10.  *  This program is distributed in the hope that it will be useful,
  11.  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  12.  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13.  *  GNU General Public License for more details.
  14.  *
  15.  *  You should have received a copy of the GNU General Public License
  16.  *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
  17.  */
  18.  
  19. #include <clocale>
  20. #include <fstream>
  21. #include <iterator>
  22. #include <list>
  23. #include <string>
  24. #include <ncurses.h>
  25.  
  26. // TODO: TAB key handling is really weird, so I ignore it for now
  27. // TODO: shifted keys are not ignored (and all printabled function keys)
  28.  
  29. const std::string title = "Watte - weird and trivially tiny editor";
  30. const std::string version = "0.9.1";
  31.  
  32. class Editor {
  33. public:
  34.     //--- public constructors ---
  35.     Editor(const char *filename)
  36.     : _data(), _filename(filename),
  37. #if DEBUG
  38.       _last_action(),
  39. #endif
  40.       _xpos(0), _ypos(1), _sline(0), _running(true)
  41.     {
  42.         std::setlocale(LC_ALL, "");
  43.         ::initscr();
  44.         ::start_color();
  45.         ::keypad(stdscr, true);
  46.         ::noecho();
  47.         ::cbreak();
  48.         ::raw();
  49.  
  50.         ::init_pair(1, COLOR_WHITE, COLOR_BLUE);
  51.         ::init_pair(2, COLOR_WHITE, COLOR_BLUE);
  52.  
  53.         if (loadFile())
  54.         {
  55. #if DEBUG
  56.             _last_action = "opened " + _filename;
  57. #endif
  58.         }
  59.         else
  60.         {
  61.             _data.push_back("");
  62. #if DEBUG
  63.             _last_action = "started new file " + _filename;
  64. #endif
  65.         }
  66.  
  67.         drawGUI();
  68.         ::refresh();
  69.     }
  70.  
  71.     Editor(const Editor &rhs) = delete;
  72.     Editor(Editor &&rhs) = delete;
  73.  
  74.     ~Editor() noexcept
  75.     {
  76.         ::endwin();
  77.     }
  78.  
  79.     //--- public operators ---
  80.     Editor &operator=(const Editor &rhs) = delete;
  81.     Editor &operator=(Editor &&rhs) = delete;
  82.  
  83.     //--- public methods ---
  84.     int32_t run()
  85.     {
  86.         while (_running)
  87.         {
  88.             drawGUI();
  89.             ::refresh();
  90.             processInput(::getch());
  91.         }
  92.  
  93.         return 0;
  94.     }
  95.  
  96.     void drawGUI()
  97.     {
  98.         const int32_t max_height = std::min(LINES - 2, static_cast<int32_t>(_data.size() - _sline));
  99.         std::string header = title + " (" + version + ") '" + _filename + "'";
  100.         std::string status = std::to_string(_data.size()) + " lines - "
  101.                              + std::to_string(_xpos) + "," + std::to_string(_ypos - 1 + _sline);
  102.         std::string footer = "(F1) reload file | (F2) save file | (F12) quit";
  103.         std::string buffer;
  104.  
  105.         // header = title
  106.         header.resize(COLS - status.size(), ' ');
  107.         header += status;
  108.         ::attron(COLOR_PAIR(1));
  109.         mvaddnstr(0, 0, header.c_str(), header.size());
  110.         ::attroff(COLOR_PAIR(1));
  111.  
  112.         // footer = status + buttons
  113. #if DEBUG
  114.         footer += " <> " + _last_action;
  115. #endif
  116.         footer.resize(COLS, ' ');
  117.         ::attron(COLOR_PAIR(2) | A_BOLD);
  118.         mvaddnstr(LINES - 1, 0, footer.c_str(), footer.size());
  119.         ::attroff(COLOR_PAIR(2) | A_BOLD);
  120.  
  121.         // editor space
  122.         for (int32_t i = 0; i < max_height; ++i)
  123.         {
  124.             buffer = *std::next(_data.begin(), i + _sline);
  125.             buffer.resize(COLS, ' ');
  126.             mvaddnstr(i + 1, 0, buffer.c_str(), buffer.size());
  127.         }
  128.  
  129.         ::move(_ypos, _xpos);
  130.     }
  131.  
  132.     void processInput(const int32_t key) noexcept
  133.     {
  134.         const char chr = key;
  135.         int32_t lines_below = std::max(0, static_cast<int32_t>(_data.size()) - _sline);
  136.         int32_t max_height = std::min(LINES - 2, lines_below);
  137.         int32_t max_width = std::min(COLS, static_cast<int32_t>(std::next(_data.begin(),
  138.                                            _ypos + _sline - 1)->size()));
  139.         int32_t old_xpos = _xpos;
  140.         int32_t old_ypos = _ypos;
  141.  
  142.         switch (key)
  143.         {
  144.             case KEY_F(1):
  145.                 loadFile();
  146. #if DEBUG
  147.                 _last_action = "reloaded " + _filename;
  148. #endif
  149.                 break;
  150.  
  151.             case KEY_F(2):
  152.                 saveFile();
  153. #if DEBUG
  154.                 _last_action = "saved " + _filename;
  155. #endif
  156.                 break;
  157.  
  158.             case KEY_F(12):
  159.                 _running = false;
  160.                 break;
  161.  
  162.             case KEY_UP:
  163.                 _ypos = std::max(_ypos - 1, 1);
  164.                 if ((old_ypos == _ypos) && (_sline > 0))
  165.                     --_sline;
  166.                 max_width = std::min(COLS, static_cast<int32_t>(std::next(_data.begin(),
  167.                                            _ypos + _sline - 1)->size()));
  168.                 _xpos = std::min(_xpos, max_width);
  169.                 break;
  170.  
  171.             case KEY_DOWN:
  172.                 _ypos = std::min(_ypos + 1, max_height);
  173.                 if ((old_ypos == _ypos) && ((lines_below - LINES + 2) > 0))
  174.                     ++_sline;
  175.                 max_width = std::min(COLS, static_cast<int32_t>(std::next(_data.begin(),
  176.                                            _ypos + _sline - 1)->size()));
  177.                 _xpos = std::min(_xpos, max_width);
  178.                 break;
  179.  
  180.             case KEY_LEFT:
  181.                 _xpos = std::max(_xpos - 1, 0);
  182.                 break;
  183.  
  184.             case KEY_RIGHT:
  185.                 _xpos = std::min(_xpos + 1, max_width);
  186.                 break;
  187.  
  188.             case KEY_DC: // delete char = delete
  189.                 if (_xpos < max_width)
  190.                     std::next(_data.begin(), _ypos + _sline - 1)->erase(_xpos, 1);
  191.                 else if ((_ypos > 1) || (_sline > 0)) // line wrapping delete
  192.                 {
  193.                     auto &next_line = *std::next(_data.begin(), _ypos + _sline);
  194.  
  195.                     *std::next(_data.begin(), _ypos + _sline - 1) += next_line;
  196.                     _data.erase(std::next(_data.begin(), _ypos + _sline));
  197.                 }
  198.                 break;
  199.  
  200.             case KEY_BACKSPACE:
  201.                 if (_xpos > 0)
  202.                     std::next(_data.begin(), _ypos + _sline - 1)->erase(_xpos-- - 1, 1);
  203.                 else if ((_ypos > 1) || (_sline > 0)) // line wrapping backspace
  204.                 {
  205.                     auto &prev_line = *std::next(_data.begin(), _ypos + _sline - 2);
  206.  
  207.                     old_xpos = prev_line.size();
  208.                     prev_line += *std::next(_data.begin(), _ypos + _sline - 1);
  209.                     _data.erase(std::next(_data.begin(), _ypos + _sline - 1));
  210.  
  211.                     _ypos = std::max(_ypos - 1, 1);
  212.                     if ((old_ypos == _ypos) && (_sline > 0))
  213.                         --_sline;
  214.                     max_width = std::min(COLS, static_cast<int32_t>(std::next(_data.begin(),
  215.                                                _ypos + _sline - 1)->size()));
  216.                     _xpos = std::min(old_xpos, max_width);
  217.                 }
  218.                 break;
  219.  
  220.             case KEY_HOME:
  221.                 _xpos = 0;
  222.                 break;
  223.  
  224.             case KEY_END:
  225.                 _xpos = max_width;
  226.                 break;
  227.  
  228.             case KEY_PPAGE: // prev page = uses half page scrolling
  229.                 _ypos = std::max(_ypos - (LINES / 2), 1);
  230.                 if ((old_ypos == _ypos) && (_sline > 0))
  231.                     _sline = std::max(_sline - (LINES / 2), 0);
  232.                 max_width = std::min(COLS, static_cast<int32_t>(std::next(_data.begin(),
  233.                                            _ypos + _sline - 1)->size()));
  234.                 _xpos = std::min(_xpos, max_width);
  235.                 break;
  236.  
  237.             case KEY_NPAGE:
  238.                 _ypos = std::min(_ypos + (LINES / 2), max_height);
  239.                 if ((old_ypos == _ypos) && ((lines_below + LINES + 2) > 0))
  240.                     _sline = std::min(_sline + (LINES / 2), lines_below);
  241.                 max_width = std::min(COLS, static_cast<int32_t>(std::next(_data.begin(),
  242.                                            _ypos + _sline - 1)->size()));
  243.                 _xpos = std::min(_xpos, max_width);
  244.                 break;
  245.  
  246.             case KEY_ENTER:
  247.             case 10:
  248.                 if (_xpos == max_width)
  249.                     _data.insert(std::next(_data.begin(), _ypos + _sline), "");
  250.                 else
  251.                 {
  252.                     const std::string substr = std::next(_data.begin(),
  253.                                                _ypos + _sline - 1)->substr(_xpos);
  254.  
  255.                     std::next(_data.begin(), _ypos + _sline - 1)->erase(_xpos);
  256.                     _data.insert(std::next(_data.begin(), _ypos + _sline), substr);
  257.                 }
  258.                 lines_below = std::max(0, static_cast<int32_t>(_data.size()) - _sline);
  259.                 max_height = std::min(LINES - 2, lines_below);
  260.                 _ypos = std::min(_ypos + 1, max_height);
  261.                 if ((old_ypos == _ypos) && ((lines_below - LINES + 2) > 0))
  262.                     ++_sline;
  263.                 _xpos = 0;
  264.                 break;
  265.  
  266.             default:
  267.                 if (std::isprint(chr))
  268.                     std::next(_data.begin(), _ypos + _sline -1)->insert(_xpos++, 1, chr);
  269.         }
  270.     }
  271.  
  272.     bool loadFile()
  273.     {
  274.         if (std::ifstream ifile(_filename); ifile.is_open() && ifile.good())
  275.         {
  276.             std::string line;
  277.  
  278.             _data.clear();
  279.             while (std::getline(ifile, line))
  280.                 _data.insert(_data.end(), line);
  281.             ifile.close();
  282.  
  283.             return true;
  284.         }
  285.  
  286.         return false;
  287.     }
  288.  
  289.     bool saveFile()
  290.     {
  291.         if (std::ofstream ofile(_filename); ofile.is_open() && ofile.good())
  292.         {
  293.             for (auto &line : _data)
  294.                 ofile << line << '\n';
  295.             ofile.close();
  296.  
  297.             return true;
  298.         }
  299.  
  300.         return false;
  301.     }
  302.  
  303. private:
  304.     //--- private properties ---
  305.     std::list<std::string> _data;
  306.     std::string _filename;
  307. #if DEBUG
  308.     std::string _last_action;
  309. #endif
  310.     int32_t _xpos;
  311.     int32_t _ypos;
  312.     int32_t _sline;
  313.     bool _running;
  314. };
  315.  
  316. int32_t main(int32_t argc, char **argv)
  317. {
  318.     Editor ed((argc == 2) ? argv[1] : "noname.txt");
  319.  
  320.     return ed.run();
  321. }
  322.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement