文章目录

Table of Contens

状态栏

熟悉 vim 的朋友都知道, vim 有一个状态栏,位于下边。状态栏的功能主要时显示一些基 本的信息,如文件名、光标位置等。

腾出空间

在之前的操作中,我们把整个窗口都用来显示文件内容了。所以我们必须将 rows_ 减去 一行用来显示状态信息。

inline int editor::get_window_size__() {
  ...
    rows_ = ws.ws_row - 1; /* 腾出状态栏 */
  ...
}

写入 buffer

现在将我们要显示的信息写入 buffer 的最后一行,也就是状态栏。为了区分状态栏和文本 区的不同,因此将颜色反转。

void editor::status_bar__() {
  append_buf__("\x1b[7m"); /* 颜色反转 */
  std::string stats((filename_.empty() ? "[New File]" : filename_) +
                    " - total: " + std::to_string(numrows_) +
                    " lines current: (" + std::to_string(cx_) + ", " +
                    std::to_string(cy_) + ")");
  append_buf__(stats);
  for (int i = 0; i < cols_ - stats.size(); ++i) append_buf__(" ");
  append_buf__("\x1b[m"); /* 恢复颜色 */
  append_buf__("\r\n");
}

消息框

消息栏也是必须的,在和编辑的交互过程中,有个消息栏是比较方便的,如命令运行结果反 馈等。信息框的处理和状态栏一样。先空出空间,然后将消息写入 buffer 。

腾出空间

inline int editor::get_window_size__() {
  ...
    rows_ = ws.ws_row - 2; /* 腾出状态栏和消息框 */
  ...
}

滞留时间

一般来说,消息最好不要一直显示,因为没什么用,还可能会影响视觉焦点。因此以时间戳为准,超过一段时 间(如5s)就不再显示消息即可。实现起来也比较简单,因为每次显示一个消息,所以每次有消息时 只需要记住消息出现的当前时间点,然后和当前的时间点比较即可。

struct textor{
  /* 消息 */
  std::string status_msg_;
  std::time_t msg_time_;
...
}

写入消息

将消息存入 status_msg_ 变量中,同时更新最新消息的时间。

void editor::status_msg__(const char *fmt, ...) {
  va_list ap;
  va_start(ap, fmt);
  char msg[80];
  vsnprintf(msg, sizeof msg, fmt, ap);
  va_end(ap);
  status_msg_ = msg;
  msg_time_ = time(nullptr);
}

写入 buffer

将消息写入 buffer 中便于显示。 K 命令是清除内容,因为每次刷新的时候先清空,然 后如果时间在规定时间内,则重新写入。

void editor::draw_msg__() {
  append_buf__("\x1b[K"); /* 清除消息框 */
  if (status_msg_.size() > cols_) status_msg_.resize(cols_);
  if (!status_msg_.empty() && time(nullptr) - msg_time_ < 5)  /* 5s 后不再显示 */
    append_buf__(status_msg_);
}

滚动

在上一篇博客中,虽然实现了基本的查看文件内容的功能。但这明显是不够的,当我们打开的 文件比较大的时候,那么一个窗口是无法把所有的内容显示在上面。因此我们必须将文件存 入 buffer 中,然后根据相对文件中的坐标来显示窗口大小的内容。因此上一篇中的坐标 (cx_, cy_) 不再是相对窗口的坐标,而是相对文件的坐标。

垂直滚动

要实现垂直滚动,首先在全局配置中记录当前的窗口起始位置在文件中的偏移 rowoff_

struct textor{
  int rowoff;
  ...
}

在初始化时值为 0, 也就是文件的第一行。

在窗口最上面和最下面的时候,我们继续向上或向下移动光标的时,需要更新文件的相对偏移位置。

void editor::scroll__() {
  if (cx_ < rowoff_)
    rowoff_ = cx_;
  else if (cx_ >= rowoff_ + rows_)
    rowoff_ = cx_ - rows_ + 1;
...
}

水平滚动

说了垂直滚动,那么水平滚动相对来说也会比较统一了,和垂直一样,定义一个水平的相对 起始位置 coloff_ ,同样初始化为第一列。

struct textor{
  int coloff_;
}

当光标位于第一列并向左移动和最后一列向右移动时,光标就会移到屏幕之外。因此就需要 更新相关的列号。

void editor::scroll__() {
...
  if (cy_ < coloff_)
    coloff_ = cy_;
  else if (cy_ >= coloff_ + cols_)
    coloff_ = cy_ - cols_ + 1;
}

然后将这些更新放在 flush__ 即可,每次刷新屏幕就会更新文件内容。

修改边界检查

在之前的实现中,我们都是以窗口为单位。所以判断是否越界也是以窗口的 size 为基准。 但是现在以文件作为基准,因此需要做点修改。

void editor::move_cursor__() {
  switch (key_) {
    case ARROW_UP:
      if (cx_ != 0) cx_--;
      break;
    case ARROW_DOWN:
      if (cx_ < numrows_ - 1) cx_++;
      if (!text_.empty() && cy_ > text_[cx_].size()) cy_ = text_[cx_].size();
      break;
    case ARROW_LEFT:
      if (cy_ > 0)
        cy_--;
      else if (cx_ != 0) {
        cx_--;
        cy_ = text_[cx_].size();
      }
      break;
    case ARROW_RIGHT:
      if (!text_.empty() && cy_ < text_[cx_].size())
        cy_++;
      else if (cx_ < numrows_ - 1) {
        cy_ = 0;
        cx_++;
      }
      break;
  }
}

写入 buffer

我们目前已经知道如何实现滚动和更新滚动的相对起始位置,但是我们还没有将其应用到 buffer 中,所以必须修改 buffer 要显示的内容,才能实现滚动的效果。

下面我们就要修改 draw_rows__ 函数,使其显示文件中的某个窗口大小的内容。

void editor::draw_rows__() {
  for (int i = 0; i < rows_; i++) {
    int filerow = i + rowoff_;      /* 修改显示为相对文件的行 */
    if (filerow >= numrows_) {
      if (i == rows_ / 3 && !numrows_) {
        std::string welcome("Textor editor -- version ");
        welcome += TEXTOR_VERSION;
        if (welcome.size() > cols_) welcome.resize(cols_);
        int padding = (cols_ - welcome.size()) / 2;
        if (padding) {
          append_buf__("~");
          padding--;
        }
        while (padding--) append_buf__(" ");
        append_buf__(welcome);
      } else {
        append_buf__("~");
      }
    } else {
      int len = text_[filerow].size() - coloff_;
      if (len < 0) {
        len += coloff_;    /* 更新文件中的列 */
        /* 更新文件中的行 */
        append_buf__(text_[filerow].substr(len, 0));
        return;
      }
      if (len > cols_) len = cols_;
      append_buf__(text_[filerow].substr(coloff_, len));
    }
    append_buf__("\x1b[K");
    append_buf__("\r\n");
  }
}

这里实现也相对比较简单,将之前的相对窗口的坐标改为文件的坐标即可,后面的字符串也 将随着修改。

总结

好了,今天的内容就到这结束了,工作不多,只是基本的编辑器的功能。目前仅实现了文件查看 器的功能,和 vim 的正常模式一样,只能查阅,不能修改或写入。后续继续更新。

看看今天的效果吧 :)

参考

https://viewsourcecode.org/snaptoken/kilo/04.aTextViewer.html