エディタを作ってみよう

この記事は約10分で読めます。

どうも、のどかはって言います

のどかはってなんだよ

はい

世の中には沢山のエディタがあり、どれが至高かなどと言い合っている訳ですが

折角なので作ってしまおうという考えもある訳です

もちろんものすごく大変なので、挫折します

その挫折を味わってみましょう

無茶をやる

という訳で初手でどれくらい無謀かを示すために今回の完成をバッと貼っちゃいます

// ed.c
#include <stdio.h>
#include <stdlib.h>
#include <ncurses.h>
#include <string.h>
#include <locale.h>
#define ctrl(x)           ((x) & 0x1f)
#ifndef SIZE_ALLOCATE
#define SIZE_ALLOCATE 1024
#endif

int access(const char*filename, int amode); // プロトタイプ宣言

int main(int argc, char **args) {
  // 変数宣言
  int h,w;
  bool help = true;
  char s[SIZE_ALLOCATE],
       *buffer = malloc(sizeof(char) * SIZE_ALLOCATE * 2048),
       c;
  memset(buffer, 0, sizeof(char) * SIZE_ALLOCATE * 2048);
  memset(s, 0, sizeof(char) * SIZE_ALLOCATE);
  FILE *fp;

  // 初期化
  setlocale(LC_ALL, "");
  echo();
  cbreak();
  raw();
  initscr();
  getmaxyx(stdscr, h, w);

  if (1 < argc){
    if (-1 == access(args[1], OK)) {
      perror("ファイルないですよ");
      endwin();
      exit(-1);
    }
    fp = fopen(args[1], "r");
    fscanf(fp, "%[^\EOF]c", buffer);
    fclose(fp);
  }

  do {
    // 更新前削除
    erase();

    // 描画関係
    move(0, 0);
    printw("start memory_size: %d, w: %d, h: %d\n", SIZE_ALLOCATE, w, h);
    if (help) {
      printw("help: Ctrl+x exit, Ctrl+o toggle Help\n");
      printw("      Ctrl+a save, Ctrl+l Input Enter Ctrl+k Kill Line (Please Key and Enter)\n");
      for (int i=0; i<w; i++) {
        printw("-");
      }
    }
    strcat(buffer, s);
    move(1 + (help ? 3 : 0), 0);
    addstr(buffer);
    move(h-1, 0);
    printw("type > ");

    // 再描画
    refresh();

    // 入力の後
    scanw("%[^\n]c", s);
    if (s[0] == ctrl('o')) {
      help = !help;
      s[0] = '\0';
    }
    if (s[0] == ctrl('a')) {
      clear();
      move(h/2, w/2);
      printw("file name: ");
      scanw("%s", s);
      fp = fopen(s, "w");
      fputs(buffer, fp);
      fclose(fp);
      endwin();
      return 0;
    }
    if (s[0] == ctrl('l')) {
      s[0] = '\n';
    }
    if (s[0] == ctrl('k')) {
      int i=strlen(buffer);
      if (buffer[i-1] == '\n') {
        buffer[i-1] = '\0';
      }
      while (buffer[i] != '\n' && i != -1) {
        buffer[i] = '\0';
        i--;
      };
      s[0] = '\0';
    }
  } while(s[0] != ctrl('x'));
  endwin();
  return 0;
}

これが1行ずつしか編集出来ない、削除機能とファイル保存機能

読み出ししかないエディタのコード量です

もう嫌になってきましたね

実はこのコードを書く準備も大変で、まずこの完成コードをコピーして動かせるかというのも重要です

windowsの方はWindowsキーを押したあとに、cmdと入力してみてコマンドプロンプトというものを開きWSLと打ってみてください

その状況までうまくいって

sudo apt install pkg-config libncurses-devと入力してインストール作業を行なう

などと書いて実行出来る人は、次に進んでみてください

厳しいなって思ったら、エディタは普通の使いましょう

さてついてきてる人は以下でコンパイル出来ます

gcc ed.c -o ed -DSIZE_ALLOCATE=1024 $(pkg-config --libs --cflags ncursesw)

そうするとこんな感じで動きます

動きましたかね

無謀は無茶とは違う

さて、これくらいすら自分で理解して作ろうとするのは案外難しいものです

それは無謀だからであって、何も計画を練らずに行動すると嫌になりがちです

でも無茶でもちゃんと簡単なとこから始めれば、いつの間にか乗り越えられているものです

エディタを小さくした今回のやつも、さらに小さく見れば単純なことをしています

まず、文字列を出力してみましょう

こうですね

#include <stdio.h>

int main(void)
{
  printf("hello_world");
  return 0;
}

じゃあこれを変数でやりましょう

そして変数において、mallocというちょっと特殊な確保の仕方をしましょう

さらに文字列を入力したものを返すようにします

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
  char *str = malloc(sizeof(char) * 100);
  scanf("%s", str);
  printf(str);
  return 0;
}

これがエディタの原型です

これが本質です

文字を入力してもらって、変数に入れて返します

さてここでライブラリってやつを使います

便利な機能をまとめたものですね

折角なので日本語も出力するようにしましょう

#include <stdio.h>
#include <stdlib.h>
#include <ncurses.h>
#include <string.h>
#include <locale.h>

int main(void)
{
  char *str = malloc(sizeof(char) * 100);
  int h, w; // 画面の最大サイズを高さと横で
  setlocale(LC_ALL, "");
  initscr();
  getmaxyx(stdscr, h, w);
  move(h/2, w/2);
  printw("こんにちは なんか入力してね > ");
  scanw("%s", str);
  printw(str);
  getch();
  endwin();
  return 0;
}

そしたらあとはファイルの扱い方を学び、文字列を操作してみます

#include <stdio.h>
#include <stdlib.h>
#include <ncurses.h>
#include <string.h>
#include <locale.h>
#ifndef SIZE_ALLOCATE
#define SIZE_ALLOCATE 1024 // サイズを決める
#endif

int main(int argc, char **argv)
{
  int h,w;
  char str[SIZE_ALLOCATE],
       *buffer = malloc(sizeof(char) * SIZE_ALLOCATE * 2048);
  memset(buffer, 0, sizeof(char) * SIZE_ALLOCATE * 2048); // 0で初期化
  memset(str, 0, sizeof(char) * SIZE_ALLOCATE); // 0で初期化
  FILE *fp;

  setlocale(LC_ALL, "");
  initscr();
  getmaxyx(stdscr, h, w);
  do {
    erase(); // 表示を消すよ

    move(0, 0);
    strcat(buffer, str);
    addstr(buffer);
    move(h/2, w/2);
    printw("こんにちは なんか入力してね > ");
    refresh(); // 表示を更新する

    scanw("%s", str); // ここで入力待ちになる
  } while (str[0] != 'q'); // 最初の文字がqなら終わる
  endwin();
  if (1 < argc){ // プログラムの引数にファイル名を渡したら保存する
    fp = open(argv[1], "w");
    fputs(buffer, fp);
    fclose(fp);
  }
  return 0;
}

あとはもう頑張るだけで一番上のやつになります

如何でしたか?

このステップを飛ばし読みした場合、逆に難しいし意味分かんねえよってなるかと思います

それはこの解説もまだまだ大雑把で、より小さく問題を分割する必要があるからです

  1. Cってどう実行するものだろう
  2. ライブラリってどう使うんだろう
  3. includeすることで関数を使ってみよう
  4. 文字列ってやつを宣言するってなんだろう、変数はどう使うんだろう
  5. *って書くとポインタになるってなんだろう、mallocってどう使うんだろう
  6. 文字列を出力するってなんだろう、文字列を連結したり文字列の一部を参照するってなんだろう
  7. ファイルってなんだろう、どうやったら開けて保存出来るだろう
  8. 日本語を入力出来るってなんだろう
  9. ループさせることでどうなるだろう
  10. 実行ファイルの引数ってやつをmainの関数で渡す方法ってなんだろう

簡易的に書いてもこれくらい分割されます

そして人によってどこをどの程度学べているかっていうのはまちまちです

だからこそ、この解説がすっと入ってくるのは稀なのです

どんな解説も学びも自分に合ったものはなかなかないです

ですから、無茶に挑むときに覚えておいてください

挑もうとした心意気が、まず小さな問題を見つける準備になる大きな足がかりなんだと

タイトルとURLをコピーしました